Created the TabsView to display tabs on the Reader screen.

* Improved the UI of the `Reader screen` to display the tabs view properly.
* Refactored functionalities in `CoreReaderFragment`, including bottom toolbar interactions, runtime UI updates, and handling of the "No Book Open" button, etc.
This commit is contained in:
MohitMaliFtechiz 2025-06-13 22:06:59 +05:30
parent 39baa985fb
commit 6ea560407d
6 changed files with 357 additions and 54 deletions

View File

@ -31,12 +31,12 @@ import com.adevinta.android.barista.interaction.BaristaSleepInteractions
import org.kiwix.kiwixmobile.BaseRobot import org.kiwix.kiwixmobile.BaseRobot
import org.kiwix.kiwixmobile.Findable.ViewId import org.kiwix.kiwixmobile.Findable.ViewId
import org.kiwix.kiwixmobile.R import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.main.reader.CONTENT_LOADING_PROGRESSBAR_TESTING_TAG
import org.kiwix.kiwixmobile.core.utils.dialog.ALERT_DIALOG_CONFIRM_BUTTON_TESTING_TAG import org.kiwix.kiwixmobile.core.utils.dialog.ALERT_DIALOG_CONFIRM_BUTTON_TESTING_TAG
import org.kiwix.kiwixmobile.core.utils.files.Log import org.kiwix.kiwixmobile.core.utils.files.Log
import org.kiwix.kiwixmobile.localFileTransfer.LocalFileTransferRobot import org.kiwix.kiwixmobile.localFileTransfer.LocalFileTransferRobot
import org.kiwix.kiwixmobile.localFileTransfer.localFileTransfer import org.kiwix.kiwixmobile.localFileTransfer.localFileTransfer
import org.kiwix.kiwixmobile.nav.destination.library.local.BOOK_LIST_TESTING_TAG import org.kiwix.kiwixmobile.nav.destination.library.local.BOOK_LIST_TESTING_TAG
import org.kiwix.kiwixmobile.nav.destination.library.local.CONTENT_LOADING_PROGRESSBAR_TESTING_TAG
import org.kiwix.kiwixmobile.nav.destination.library.local.LOCAL_FILE_TRANSFER_MENU_BUTTON_TESTING_TAG import org.kiwix.kiwixmobile.nav.destination.library.local.LOCAL_FILE_TRANSFER_MENU_BUTTON_TESTING_TAG
import org.kiwix.kiwixmobile.nav.destination.library.local.NO_FILE_TEXT_TESTING_TAG import org.kiwix.kiwixmobile.nav.destination.library.local.NO_FILE_TEXT_TESTING_TAG
import org.kiwix.kiwixmobile.testutils.TestUtils import org.kiwix.kiwixmobile.testutils.TestUtils

View File

@ -49,6 +49,7 @@ import org.kiwix.kiwixmobile.core.extensions.setBottomMarginToFragmentContainerV
import org.kiwix.kiwixmobile.core.extensions.setImageDrawableCompat import org.kiwix.kiwixmobile.core.extensions.setImageDrawableCompat
import org.kiwix.kiwixmobile.core.extensions.snack import org.kiwix.kiwixmobile.core.extensions.snack
import org.kiwix.kiwixmobile.core.extensions.toast import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.extensions.update
import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.main.reader.CoreReaderFragment import org.kiwix.kiwixmobile.core.main.reader.CoreReaderFragment
import org.kiwix.kiwixmobile.core.main.CoreWebViewClient import org.kiwix.kiwixmobile.core.main.CoreWebViewClient
@ -79,10 +80,12 @@ class KiwixReaderFragment : CoreReaderFragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val activity = activity as CoreMainActivity val activity = activity as CoreMainActivity
noOpenBookButton?.setOnClickListener { readerScreenState.update {
activity.navigate( copy(onOpenLibraryButtonClicked = {
KiwixReaderFragmentDirections.actionNavigationReaderToNavigationLibrary() activity.navigate(
) KiwixReaderFragmentDirections.actionNavigationReaderToNavigationLibrary()
)
})
} }
activity.supportActionBar?.setDisplayHomeAsUpEnabled(true) activity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
toolbar?.let { activity.setupDrawerToggle(it, true) } toolbar?.let { activity.setupDrawerToggle(it, true) }

View File

@ -67,6 +67,11 @@ import androidx.annotation.AnimRes
import androidx.appcompat.app.ActionBar import androidx.appcompat.app.ActionBar
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.ComposeView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.Group import androidx.constraintlayout.widget.Group
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
@ -111,6 +116,7 @@ import org.kiwix.kiwixmobile.core.BuildConfig
import org.kiwix.kiwixmobile.core.CoreApp import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.DarkModeConfig import org.kiwix.kiwixmobile.core.DarkModeConfig
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.R.string
import org.kiwix.kiwixmobile.core.StorageObserver import org.kiwix.kiwixmobile.core.StorageObserver
import org.kiwix.kiwixmobile.core.base.BaseFragment import org.kiwix.kiwixmobile.core.base.BaseFragment
import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions
@ -125,12 +131,12 @@ import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.observeNavigatio
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.requestNotificationPermission import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.requestNotificationPermission
import org.kiwix.kiwixmobile.core.extensions.ViewGroupExtensions.findFirstTextView import org.kiwix.kiwixmobile.core.extensions.ViewGroupExtensions.findFirstTextView
import org.kiwix.kiwixmobile.core.extensions.closeFullScreenMode import org.kiwix.kiwixmobile.core.extensions.closeFullScreenMode
import org.kiwix.kiwixmobile.core.extensions.getDialogHostComposeView
import org.kiwix.kiwixmobile.core.extensions.getToolbarNavigationIcon import org.kiwix.kiwixmobile.core.extensions.getToolbarNavigationIcon
import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription
import org.kiwix.kiwixmobile.core.extensions.showFullScreenMode import org.kiwix.kiwixmobile.core.extensions.showFullScreenMode
import org.kiwix.kiwixmobile.core.extensions.snack import org.kiwix.kiwixmobile.core.extensions.snack
import org.kiwix.kiwixmobile.core.extensions.toast import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.extensions.update
import org.kiwix.kiwixmobile.core.main.AddNoteDialog import org.kiwix.kiwixmobile.core.main.AddNoteDialog
import org.kiwix.kiwixmobile.core.main.CompatFindActionModeCallback import org.kiwix.kiwixmobile.core.main.CompatFindActionModeCallback
import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.main.CoreMainActivity
@ -150,7 +156,6 @@ import org.kiwix.kiwixmobile.core.main.MainRepositoryActions
import org.kiwix.kiwixmobile.core.main.OnSwipeTouchListener import org.kiwix.kiwixmobile.core.main.OnSwipeTouchListener
import org.kiwix.kiwixmobile.core.main.ServiceWorkerUninitialiser import org.kiwix.kiwixmobile.core.main.ServiceWorkerUninitialiser
import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter
import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin.FromExternalLaunch
import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter.DocumentSection import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter.DocumentSection
import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter.TableClickListener import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter.TableClickListener
import org.kiwix.kiwixmobile.core.main.TabsAdapter import org.kiwix.kiwixmobile.core.main.TabsAdapter
@ -158,6 +163,7 @@ import org.kiwix.kiwixmobile.core.main.ToolbarScrollingKiwixWebView
import org.kiwix.kiwixmobile.core.main.UNINITIALISER_ADDRESS import org.kiwix.kiwixmobile.core.main.UNINITIALISER_ADDRESS
import org.kiwix.kiwixmobile.core.main.WebViewCallback import org.kiwix.kiwixmobile.core.main.WebViewCallback
import org.kiwix.kiwixmobile.core.main.WebViewProvider import org.kiwix.kiwixmobile.core.main.WebViewProvider
import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin.FromExternalLaunch
import org.kiwix.kiwixmobile.core.navigateToAppSettings import org.kiwix.kiwixmobile.core.navigateToAppSettings
import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem
import org.kiwix.kiwixmobile.core.page.history.NavigationHistoryClickListener import org.kiwix.kiwixmobile.core.page.history.NavigationHistoryClickListener
@ -174,6 +180,8 @@ import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Companion.CONTENT_PREFIX
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchItemToOpen import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchItemToOpen
import org.kiwix.kiwixmobile.core.ui.components.NavigationIcon
import org.kiwix.kiwixmobile.core.ui.models.IconItem
import org.kiwix.kiwixmobile.core.utils.AnimationUtils.rotate import org.kiwix.kiwixmobile.core.utils.AnimationUtils.rotate
import org.kiwix.kiwixmobile.core.utils.DimenUtils.getToolbarHeight import org.kiwix.kiwixmobile.core.utils.DimenUtils.getToolbarHeight
import org.kiwix.kiwixmobile.core.utils.DimenUtils.getWindowWidth import org.kiwix.kiwixmobile.core.utils.DimenUtils.getWindowWidth
@ -193,6 +201,7 @@ import org.kiwix.kiwixmobile.core.utils.TAG_FILE_SEARCHED
import org.kiwix.kiwixmobile.core.utils.TAG_FILE_SEARCHED_NEW_TAB import org.kiwix.kiwixmobile.core.utils.TAG_FILE_SEARCHED_NEW_TAB
import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX
import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower
import org.kiwix.kiwixmobile.core.utils.dialog.DialogHost
import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower
import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog
import org.kiwix.kiwixmobile.core.utils.dialog.UnsupportedMimeTypeHandler import org.kiwix.kiwixmobile.core.utils.dialog.UnsupportedMimeTypeHandler
@ -361,6 +370,38 @@ abstract class CoreReaderFragment :
private var isReadSelection = false private var isReadSelection = false
private var isReadAloudServiceRunning = false private var isReadAloudServiceRunning = false
private var libkiwixBook: Book? = null private var libkiwixBook: Book? = null
private var composeView: ComposeView? = null
protected val readerScreenState = mutableStateOf(
ReaderScreenState(
snackBarHostState = SnackbarHostState(),
isNoBookOpenInReader = false,
onOpenLibraryButtonClicked = {},
pageLoadingItem = false to ZERO,
shouldShowDonationPopup = false,
// TODO set in onViewCreated.
fullScreenItem = false to null,
showBackToTopButton = false,
backToTopButtonClick = { backToTop() },
showFullscreenButton = false,
onExitFullscreenClick = { closeFullScreen() },
showTtsControls = false,
onPauseTtsClick = { pauseTts() },
pauseTtsButtonText = "",
onStopTtsClick = { stopTts() },
kiwixWebViewList = webViewList,
bookmarkButtonItem = Triple(
{ toggleBookmark() },
{ goToBookmarks() },
IconItem.Drawable(R.drawable.ic_bookmark_border_24dp)
),
previousPageButtonItem = { goBack() } to { showBackwardHistory() },
onHomeButtonClick = { openMainPage() },
nextPageButtonItem = { goForward() } to { showForwardHistory() },
onTocClick = { openToc() },
onCloseAllTabs = { closeAllTabs() }
)
)
private var readerLifeCycleScope: CoroutineScope? = null private var readerLifeCycleScope: CoroutineScope? = null
val coreReaderLifeCycleScope: CoroutineScope? val coreReaderLifeCycleScope: CoroutineScope?
@ -449,11 +490,28 @@ abstract class CoreReaderFragment :
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
@Suppress("LongMethod")
override fun onViewCreated( override fun onViewCreated(
view: View, view: View,
savedInstanceState: Bundle? savedInstanceState: Bundle?
) { ) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
composeView?.apply {
setContent {
ReaderScreen(
state = readerScreenState.value,
actionMenuItems = emptyList(),
navigationIcon = {
NavigationIcon(
iconItem = IconItem.Vector(Icons.Filled.Menu),
contentDescription = string.open_drawer,
onClick = { navigationIconClick() }
)
}
)
DialogHost(alertDialogShower as AlertDialogShower)
}
}
addAlertDialogToDialogHost() addAlertDialogToDialogHost()
setupMenu() setupMenu()
donationDialogHandler?.setDonationDialogCallBack(this) donationDialogHandler?.setDonationDialogCallBack(this)
@ -555,10 +613,21 @@ abstract class CoreReaderFragment :
handleClicks() handleClicks()
} }
private fun navigationIconClick() {
// Manually handle the navigation open/close.
// Since currently we are using the view based navigation drawer in other screens.
// Once we fully migrate to jetpack compose we will refactor this code to use the
// compose navigation.
// TODO Replace with compose based navigation when migration is done.
val activity = activity as CoreMainActivity
if (activity.navigationDrawerIsOpen()) {
activity.closeNavigationDrawer()
} else {
activity.openNavigationDrawer()
}
}
private fun addAlertDialogToDialogHost() { private fun addAlertDialogToDialogHost() {
fragmentReaderBinding?.root?.addView(
requireContext().getDialogHostComposeView(alertDialogShower as AlertDialogShower)
)
externalLinkOpener?.setAlertDialogShower(alertDialogShower as AlertDialogShower) externalLinkOpener?.setAlertDialogShower(alertDialogShower as AlertDialogShower)
unsupportedMimeTypeHandler?.setAlertDialogShower(alertDialogShower as AlertDialogShower) unsupportedMimeTypeHandler?.setAlertDialogShower(alertDialogShower as AlertDialogShower)
} }
@ -704,9 +773,8 @@ abstract class CoreReaderFragment :
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? = ComposeView(requireContext()).also {
fragmentReaderBinding = FragmentReaderBinding.inflate(inflater, container, false) composeView = it
return fragmentReaderBinding?.root
} }
private fun handleIntentExtras(intent: Intent) { private fun handleIntentExtras(intent: Intent) {
@ -1251,11 +1319,15 @@ abstract class CoreReaderFragment :
tts?.currentTTSTask?.let { tts?.currentTTSTask?.let {
if (it.paused) { if (it.paused) {
tts?.pauseOrResume() tts?.pauseOrResume()
pauseTTSButton?.setText(R.string.tts_pause) readerScreenState.update {
copy(pauseTtsButtonText = context?.getString(R.string.tts_pause).orEmpty())
}
setActionAndStartTTSService(ACTION_PAUSE_OR_RESUME_TTS, false) setActionAndStartTTSService(ACTION_PAUSE_OR_RESUME_TTS, false)
} else { } else {
tts?.pauseOrResume() tts?.pauseOrResume()
pauseTTSButton?.setText(R.string.tts_resume) readerScreenState.update {
copy(pauseTtsButtonText = context?.getString(R.string.tts_resume).orEmpty())
}
setActionAndStartTTSService(ACTION_PAUSE_OR_RESUME_TTS, true) setActionAndStartTTSService(ACTION_PAUSE_OR_RESUME_TTS, true)
} }
} }
@ -1321,6 +1393,8 @@ abstract class CoreReaderFragment :
storagePermissionForNotesLauncher = null storagePermissionForNotesLauncher = null
donationDialogHandler?.setDonationDialogCallBack(null) donationDialogHandler?.setDonationDialogCallBack(null)
donationDialogHandler = null donationDialogHandler = null
composeView?.disposeComposition()
composeView = null
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
@ -1927,14 +2001,23 @@ abstract class CoreReaderFragment :
List<String?>::contains List<String?>::contains
).collect { isBookmarked -> ).collect { isBookmarked ->
this@CoreReaderFragment.isBookmarked = isBookmarked this@CoreReaderFragment.isBookmarked = isBookmarked
bottomToolbarBookmark?.setImageResource( readerScreenState.update {
if (isBookmarked) R.drawable.ic_bookmark_24dp else R.drawable.ic_bookmark_border_24dp copy(
) bookmarkButtonItem = bookmarkButtonItem.copy(third = getBookMarkButtonIcon(isBookmarked))
)
}
} }
} }
updateUrlFlow() updateUrlFlow()
} }
private fun getBookMarkButtonIcon(isBookmarked: Boolean) =
if (isBookmarked) {
IconItem.Drawable(R.drawable.ic_bookmark_24dp)
} else {
IconItem.Drawable(R.drawable.ic_bookmark_border_24dp)
}
private fun safelyCancelBookmarkJob() { private fun safelyCancelBookmarkJob() {
bookmarkingJob?.cancel() bookmarkingJob?.cancel()
bookmarkingJob = null bookmarkingJob = null

View File

@ -18,22 +18,34 @@
package org.kiwix.kiwixmobile.core.main.reader package org.kiwix.kiwixmobile.core.main.reader
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.BottomAppBar import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.FloatingActionButtonDefaults
@ -43,14 +55,24 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.main.DarkModeViewPainter
import org.kiwix.kiwixmobile.core.main.KiwixWebView
import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
import org.kiwix.kiwixmobile.core.ui.components.KiwixButton import org.kiwix.kiwixmobile.core.ui.components.KiwixButton
@ -63,11 +85,16 @@ import org.kiwix.kiwixmobile.core.ui.models.toPainter
import org.kiwix.kiwixmobile.core.ui.theme.Black import org.kiwix.kiwixmobile.core.ui.theme.Black
import org.kiwix.kiwixmobile.core.ui.theme.KiwixDialogTheme import org.kiwix.kiwixmobile.core.ui.theme.KiwixDialogTheme
import org.kiwix.kiwixmobile.core.ui.theme.White import org.kiwix.kiwixmobile.core.ui.theme.White
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.CLOSE_ALL_TAB_BUTTON_BOTTOM_PADDING
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOUR_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOUR_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.ONE_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_LAYOUT_HEIGHT import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_LAYOUT_HEIGHT
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TTS_BUTTONS_CONTROL_ALPHA import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TTS_BUTTONS_CONTROL_ALPHA
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP
import org.kiwix.kiwixmobile.core.utils.StyleUtils.fromHtml
const val CONTENT_LOADING_PROGRESSBAR_TESTING_TAG = "contentLoadingProgressBarTestingTag" const val CONTENT_LOADING_PROGRESSBAR_TESTING_TAG = "contentLoadingProgressBarTestingTag"
@ -90,25 +117,34 @@ fun ReaderScreen(
.fillMaxSize() .fillMaxSize()
.padding(paddingValues) .padding(paddingValues)
) { ) {
Column( if (state.isNoBookOpenInReader) {
modifier = Modifier NoBookOpenView(state.onOpenLibraryButtonClicked)
.fillMaxSize(), } else {
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
ShowProgressBarIfZIMFilePageIsLoading(state) ShowProgressBarIfZIMFilePageIsLoading(state)
if (state.isNoBookOpenInReader) { ShowZIMFileContent(state.kiwixWebViewList)
NoBookOpenView(state.onOpenLibraryButtonClicked) TtsControls(state)
} BottomAppBarOfReaderScreen(
state.bookmarkButtonItem,
state.previousPageButtonItem,
state.onHomeButtonClick,
state.nextPageButtonItem,
state.onTocClick
)
ShowFullScreenView(state)
} }
TtsControls(state)
ShowFullScreenView(state)
ShowDonationLayout(state) ShowDonationLayout(state)
} }
} }
} }
} }
@Composable
private fun ShowZIMFileContent(kiwixWebViewList: List<KiwixWebView>) {
if (kiwixWebViewList.isNotEmpty()) {
AndroidView({ kiwixWebViewList[0] }, modifier = Modifier.fillMaxSize())
}
}
@Composable @Composable
private fun ShowFullScreenView(state: ReaderScreenState) { private fun ShowFullScreenView(state: ReaderScreenState) {
if (state.fullScreenItem.first) { if (state.fullScreenItem.first) {
@ -117,10 +153,12 @@ private fun ShowFullScreenView(state: ReaderScreenState) {
} }
@Composable @Composable
private fun ShowProgressBarIfZIMFilePageIsLoading(state: ReaderScreenState) { private fun BoxScope.ShowProgressBarIfZIMFilePageIsLoading(state: ReaderScreenState) {
if (state.pageLoadingItem.first) { if (state.pageLoadingItem.first) {
ContentLoadingProgressBar( ContentLoadingProgressBar(
modifier = Modifier.testTag(CONTENT_LOADING_PROGRESSBAR_TESTING_TAG), modifier = Modifier
.testTag(CONTENT_LOADING_PROGRESSBAR_TESTING_TAG)
.align(Alignment.CenterEnd),
progressBarStyle = ProgressBarStyle.HORIZONTAL, progressBarStyle = ProgressBarStyle.HORIZONTAL,
progress = state.pageLoadingItem.second progress = state.pageLoadingItem.second
) )
@ -155,7 +193,7 @@ private fun NoBookOpenView(
@Composable @Composable
private fun BoxScope.TtsControls(state: ReaderScreenState) { private fun BoxScope.TtsControls(state: ReaderScreenState) {
if (state.showTtsControls) { if (state.showTtsControls) {
Row(modifier = Modifier.align(Alignment.TopCenter)) { Row(modifier = Modifier.align(Alignment.BottomCenter)) {
Button( Button(
onClick = state.onPauseTtsClick, onClick = state.onPauseTtsClick,
modifier = Modifier modifier = Modifier
@ -163,7 +201,7 @@ private fun BoxScope.TtsControls(state: ReaderScreenState) {
.alpha(TTS_BUTTONS_CONTROL_ALPHA) .alpha(TTS_BUTTONS_CONTROL_ALPHA)
) { ) {
Text( Text(
text = stringResource(R.string.tts_pause), text = state.pauseTtsButtonText,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
} }
@ -204,10 +242,10 @@ private fun BackToTopFab(state: ReaderScreenState) {
@Composable @Composable
private fun BottomAppBarOfReaderScreen( private fun BottomAppBarOfReaderScreen(
onBookmarkClick: () -> Unit, bookmarkButtonItem: Triple<() -> Unit, () -> Unit, Drawable>,
onBackClick: () -> Unit, previousPageButtonItem: Pair<() -> Unit, () -> Unit>,
onHomeClick: () -> Unit, onHomeButtonClick: () -> Unit,
onForwardClick: () -> Unit, nextPageButtonItem: Pair<() -> Unit, () -> Unit>,
onTocClick: () -> Unit onTocClick: () -> Unit
) { ) {
BottomAppBar( BottomAppBar(
@ -223,33 +261,36 @@ private fun BottomAppBarOfReaderScreen(
) { ) {
// Bookmark Icon // Bookmark Icon
BottomAppBarButtonIcon( BottomAppBarButtonIcon(
onBookmarkClick, onClick = bookmarkButtonItem.first,
Drawable(R.drawable.ic_bookmark_border_24dp), onLongClick = bookmarkButtonItem.second,
stringResource(R.string.bookmarks) buttonIcon = bookmarkButtonItem.third,
contentDescription = stringResource(R.string.bookmarks)
) )
// Back Icon(for going to previous page) // Back Icon(for going to previous page)
BottomAppBarButtonIcon( BottomAppBarButtonIcon(
onBackClick, onClick = previousPageButtonItem.first,
Drawable(R.drawable.ic_keyboard_arrow_left_24dp), onLongClick = previousPageButtonItem.second,
stringResource(R.string.go_to_previous_page) buttonIcon = Drawable(R.drawable.ic_keyboard_arrow_left_24dp),
contentDescription = stringResource(R.string.go_to_previous_page)
) )
// Home Icon(to open the home page of ZIM file) // Home Icon(to open the home page of ZIM file)
BottomAppBarButtonIcon( BottomAppBarButtonIcon(
onHomeClick, onClick = onHomeButtonClick,
Drawable(R.drawable.action_home), buttonIcon = Drawable(R.drawable.action_home),
stringResource(R.string.menu_home) contentDescription = stringResource(R.string.menu_home)
) )
// Forward Icon(for going to next page) // Forward Icon(for going to next page)
BottomAppBarButtonIcon( BottomAppBarButtonIcon(
onForwardClick, onClick = nextPageButtonItem.first,
Drawable(R.drawable.ic_keyboard_arrow_right_24dp), onLongClick = nextPageButtonItem.second,
stringResource(R.string.go_to_next_page) buttonIcon = Drawable(R.drawable.ic_keyboard_arrow_right_24dp),
contentDescription = stringResource(R.string.go_to_next_page)
) )
// Toggle Icon(to open the table of content in right side bar) // Toggle Icon(to open the table of content in right side bar)
BottomAppBarButtonIcon( BottomAppBarButtonIcon(
onTocClick, onClick = onTocClick,
Drawable(R.drawable.ic_toc_24dp), buttonIcon = Drawable(R.drawable.ic_toc_24dp),
stringResource(R.string.table_of_contents) contentDescription = stringResource(R.string.table_of_contents)
) )
} }
} }
@ -258,10 +299,14 @@ private fun BottomAppBarOfReaderScreen(
@Composable @Composable
private fun BottomAppBarButtonIcon( private fun BottomAppBarButtonIcon(
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: (() -> Unit)? = null,
buttonIcon: IconItem, buttonIcon: IconItem,
contentDescription: String contentDescription: String
) { ) {
IconButton(onClick = onClick) { IconButton(
onClick = onClick,
modifier = Modifier.combinedClickable(onClick = onClick, onLongClick = onLongClick)
) {
Icon( Icon(
buttonIcon.toPainter(), buttonIcon.toPainter(),
contentDescription, contentDescription,
@ -282,3 +327,130 @@ private fun BoxScope.ShowDonationLayout(state: ReaderScreenState) {
} }
} }
} }
@Composable
fun TabSwitcherView(
webViews: List<KiwixWebView>,
selectedIndex: Int,
onSelectTab: (Int) -> Unit,
onCloseTab: (Int) -> Unit,
onCloseAllTabs: () -> Unit,
painter: DarkModeViewPainter
) {
Box(modifier = Modifier.fillMaxSize()) {
LazyRow(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.TopCenter)
.padding(top = SIXTEEN_DP),
contentPadding = PaddingValues(horizontal = SIXTEEN_DP, vertical = EIGHT_DP),
horizontalArrangement = Arrangement.spacedBy(EIGHT_DP)
) {
itemsIndexed(webViews, key = { _, item -> item.hashCode() }) { index, webView ->
val context = LocalContext.current
val title = remember(webView) {
webView.title?.fromHtml()?.toString()
?: context.getString(R.string.menu_home)
}
LaunchedEffect(webView) {
if (title != context.getString(R.string.menu_home)) {
painter.update(webView)
}
}
TabItemView(
title = title,
isSelected = index == selectedIndex,
webView = webView,
onSelectTab = { onSelectTab(index) },
onCloseTab = { onCloseTab(index) }
)
}
}
CloseAllTabButton(onCloseAllTabs)
}
}
@Composable
private fun BoxScope.CloseAllTabButton(onCloseAllTabs: () -> Unit) {
FloatingActionButton(
onClick = onCloseAllTabs,
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = CLOSE_ALL_TAB_BUTTON_BOTTOM_PADDING)
) {
Icon(
painter = painterResource(R.drawable.ic_close_black_24dp),
contentDescription = stringResource(R.string.close_all_tabs)
)
}
}
@Suppress("MagicNumber")
@Composable
fun TabItemView(
title: String,
isSelected: Boolean,
webView: KiwixWebView,
modifier: Modifier = Modifier,
onSelectTab: () -> Unit,
onCloseTab: () -> Unit
) {
val cardElevation = if (isSelected) EIGHT_DP else TWO_DP
val borderColor = if (isSelected) MaterialTheme.colorScheme.primary else Color.Transparent
Box(modifier = modifier) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(horizontal = EIGHT_DP, vertical = FOUR_DP)
.widthIn(min = 200.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = FOUR_DP),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = title,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.weight(1f)
.padding(end = EIGHT_DP),
style = MaterialTheme.typography.labelLarge
)
IconButton(onClick = onCloseTab) {
Icon(
painter = painterResource(id = R.drawable.ic_clear_white_24dp),
contentDescription = stringResource(R.string.close_tab)
)
}
}
// Card with WebView (non-interactive with overlay)
Card(
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation),
border = BorderStroke(ONE_DP, borderColor),
shape = MaterialTheme.shapes.medium,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1.6f) // approximate height logic
.clickable { onSelectTab() }
) {
AndroidView(
factory = { context ->
// Detach if needed to avoid WebView already has a parent issue
(webView.parent as? ViewGroup)?.removeView(webView)
FrameLayout(context).apply {
addView(webView)
}
},
modifier = Modifier.fillMaxSize()
)
}
}
}
}

View File

@ -20,6 +20,8 @@ package org.kiwix.kiwixmobile.core.main.reader
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import org.kiwix.kiwixmobile.core.main.KiwixWebView
import org.kiwix.kiwixmobile.core.ui.models.IconItem.Drawable
/** /**
* Represents the UI state for the Reader Screen. * Represents the UI state for the Reader Screen.
@ -59,7 +61,7 @@ data class ReaderScreenState(
* - [Boolean]: Whether to show/hide full screen mode. * - [Boolean]: Whether to show/hide full screen mode.
* - [ComposeView]: full screen view. * - [ComposeView]: full screen view.
*/ */
val fullScreenItem: Pair<Boolean, ComposeView>, val fullScreenItem: Pair<Boolean, ComposeView?>,
/** /**
* Manages the showing of "BackToTop" fab button. * Manages the showing of "BackToTop" fab button.
*/ */
@ -72,5 +74,47 @@ data class ReaderScreenState(
val onExitFullscreenClick: () -> Unit = {}, val onExitFullscreenClick: () -> Unit = {},
val showTtsControls: Boolean = false, val showTtsControls: Boolean = false,
val onPauseTtsClick: () -> Unit = {}, val onPauseTtsClick: () -> Unit = {},
/**
* Manages the showing of TTS button text(Pause/Resume).
*/
val pauseTtsButtonText: String,
val onStopTtsClick: () -> Unit = {}, val onStopTtsClick: () -> Unit = {},
/**
* To show in the tabs view.
*/
val kiwixWebViewList: List<KiwixWebView>,
/**
* Handles the (UI, and clicks) for bookmark button in reader bottom toolbar.
*
* A [Triple] containing:
* - [Unit]: Handles the normal click of button.
* - [Unit]: Handles the long click of button.
* - [Drawable]: Handles the Icon of button.
*/
val bookmarkButtonItem: Triple<() -> Unit, () -> Unit, Drawable>,
/**
* Handles the clicks of previous page button in reader bottom toolbar.
*
* A [Pair] containing:
* - [Unit]: Handles the normal click of button(For going to previous page).
* - [Unit]: Handles the long click of button(For showing the previous pages history).
*/
val previousPageButtonItem: Pair<() -> Unit, () -> Unit>,
/**
* Handles the click to open home page of ZIM file button click in reader bottom toolbar.
*/
val onHomeButtonClick: () -> Unit,
/**
* Handles the clicks of next page button in reader bottom toolbar.
*
* A [Pair] containing:
* - [Unit]: Handles the normal click of button(For going to next page).
* - [Unit]: Handles the long click of button(For showing the next pages history).
*/
val nextPageButtonItem: Pair<() -> Unit, () -> Unit>,
/**
* Handles the click to open right sidebar button click in reader bottom toolbar.
*/
val onTocClick: () -> Unit,
val onCloseAllTabs: () -> Unit
) )

View File

@ -184,4 +184,5 @@ object ComposeDimens {
val READER_BOTTOM_APP_BAR_LAYOUT_HEIGHT = 48.dp val READER_BOTTOM_APP_BAR_LAYOUT_HEIGHT = 48.dp
val READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE = 30.dp val READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE = 30.dp
const val TTS_BUTTONS_CONTROL_ALPHA = 0.6f const val TTS_BUTTONS_CONTROL_ALPHA = 0.6f
val CLOSE_ALL_TAB_BUTTON_BOTTOM_PADDING = 24.dp
} }