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.Findable.ViewId
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.files.Log
import org.kiwix.kiwixmobile.localFileTransfer.LocalFileTransferRobot
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.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.NO_FILE_TEXT_TESTING_TAG
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.snack
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.reader.CoreReaderFragment
import org.kiwix.kiwixmobile.core.main.CoreWebViewClient
@ -79,10 +80,12 @@ class KiwixReaderFragment : CoreReaderFragment() {
super.onViewCreated(view, savedInstanceState)
val activity = activity as CoreMainActivity
noOpenBookButton?.setOnClickListener {
activity.navigate(
KiwixReaderFragmentDirections.actionNavigationReaderToNavigationLibrary()
)
readerScreenState.update {
copy(onOpenLibraryButtonClicked = {
activity.navigate(
KiwixReaderFragmentDirections.actionNavigationReaderToNavigationLibrary()
)
})
}
activity.supportActionBar?.setDisplayHomeAsUpEnabled(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.AppCompatActivity
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.Group
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.DarkModeConfig
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.R.string
import org.kiwix.kiwixmobile.core.StorageObserver
import org.kiwix.kiwixmobile.core.base.BaseFragment
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.ViewGroupExtensions.findFirstTextView
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.setToolTipWithContentDescription
import org.kiwix.kiwixmobile.core.extensions.showFullScreenMode
import org.kiwix.kiwixmobile.core.extensions.snack
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.CompatFindActionModeCallback
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.ServiceWorkerUninitialiser
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.TableClickListener
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.WebViewCallback
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.page.bookmark.adapter.LibkiwixBookmarkItem
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.ZimReaderSource
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.DimenUtils.getToolbarHeight
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_KIWIX
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.KiwixDialog
import org.kiwix.kiwixmobile.core.utils.dialog.UnsupportedMimeTypeHandler
@ -361,6 +370,38 @@ abstract class CoreReaderFragment :
private var isReadSelection = false
private var isReadAloudServiceRunning = false
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
val coreReaderLifeCycleScope: CoroutineScope?
@ -449,11 +490,28 @@ abstract class CoreReaderFragment :
}
@SuppressLint("ClickableViewAccessibility")
@Suppress("LongMethod")
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
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()
setupMenu()
donationDialogHandler?.setDonationDialogCallBack(this)
@ -555,10 +613,21 @@ abstract class CoreReaderFragment :
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() {
fragmentReaderBinding?.root?.addView(
requireContext().getDialogHostComposeView(alertDialogShower as AlertDialogShower)
)
externalLinkOpener?.setAlertDialogShower(alertDialogShower as AlertDialogShower)
unsupportedMimeTypeHandler?.setAlertDialogShower(alertDialogShower as AlertDialogShower)
}
@ -704,9 +773,8 @@ abstract class CoreReaderFragment :
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentReaderBinding = FragmentReaderBinding.inflate(inflater, container, false)
return fragmentReaderBinding?.root
): View? = ComposeView(requireContext()).also {
composeView = it
}
private fun handleIntentExtras(intent: Intent) {
@ -1251,11 +1319,15 @@ abstract class CoreReaderFragment :
tts?.currentTTSTask?.let {
if (it.paused) {
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)
} else {
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)
}
}
@ -1321,6 +1393,8 @@ abstract class CoreReaderFragment :
storagePermissionForNotesLauncher = null
donationDialogHandler?.setDonationDialogCallBack(null)
donationDialogHandler = null
composeView?.disposeComposition()
composeView = null
}
@SuppressLint("ClickableViewAccessibility")
@ -1927,14 +2001,23 @@ abstract class CoreReaderFragment :
List<String?>::contains
).collect { isBookmarked ->
this@CoreReaderFragment.isBookmarked = isBookmarked
bottomToolbarBookmark?.setImageResource(
if (isBookmarked) R.drawable.ic_bookmark_24dp else R.drawable.ic_bookmark_border_24dp
)
readerScreenState.update {
copy(
bookmarkButtonItem = bookmarkButtonItem.copy(third = getBookMarkButtonIcon(isBookmarked))
)
}
}
}
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() {
bookmarkingJob?.cancel()
bookmarkingJob = null

View File

@ -18,22 +18,34 @@
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.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
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.verticalScroll
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.FloatingActionButtonDefaults
@ -43,14 +55,24 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
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.main.DarkModeViewPainter
import org.kiwix.kiwixmobile.core.main.KiwixWebView
import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
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.KiwixDialogTheme
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.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_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.TWO_DP
import org.kiwix.kiwixmobile.core.utils.StyleUtils.fromHtml
const val CONTENT_LOADING_PROGRESSBAR_TESTING_TAG = "contentLoadingProgressBarTestingTag"
@ -90,25 +117,34 @@ fun ReaderScreen(
.fillMaxSize()
.padding(paddingValues)
) {
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
if (state.isNoBookOpenInReader) {
NoBookOpenView(state.onOpenLibraryButtonClicked)
} else {
ShowProgressBarIfZIMFilePageIsLoading(state)
if (state.isNoBookOpenInReader) {
NoBookOpenView(state.onOpenLibraryButtonClicked)
}
ShowZIMFileContent(state.kiwixWebViewList)
TtsControls(state)
BottomAppBarOfReaderScreen(
state.bookmarkButtonItem,
state.previousPageButtonItem,
state.onHomeButtonClick,
state.nextPageButtonItem,
state.onTocClick
)
ShowFullScreenView(state)
}
TtsControls(state)
ShowFullScreenView(state)
ShowDonationLayout(state)
}
}
}
}
@Composable
private fun ShowZIMFileContent(kiwixWebViewList: List<KiwixWebView>) {
if (kiwixWebViewList.isNotEmpty()) {
AndroidView({ kiwixWebViewList[0] }, modifier = Modifier.fillMaxSize())
}
}
@Composable
private fun ShowFullScreenView(state: ReaderScreenState) {
if (state.fullScreenItem.first) {
@ -117,10 +153,12 @@ private fun ShowFullScreenView(state: ReaderScreenState) {
}
@Composable
private fun ShowProgressBarIfZIMFilePageIsLoading(state: ReaderScreenState) {
private fun BoxScope.ShowProgressBarIfZIMFilePageIsLoading(state: ReaderScreenState) {
if (state.pageLoadingItem.first) {
ContentLoadingProgressBar(
modifier = Modifier.testTag(CONTENT_LOADING_PROGRESSBAR_TESTING_TAG),
modifier = Modifier
.testTag(CONTENT_LOADING_PROGRESSBAR_TESTING_TAG)
.align(Alignment.CenterEnd),
progressBarStyle = ProgressBarStyle.HORIZONTAL,
progress = state.pageLoadingItem.second
)
@ -155,7 +193,7 @@ private fun NoBookOpenView(
@Composable
private fun BoxScope.TtsControls(state: ReaderScreenState) {
if (state.showTtsControls) {
Row(modifier = Modifier.align(Alignment.TopCenter)) {
Row(modifier = Modifier.align(Alignment.BottomCenter)) {
Button(
onClick = state.onPauseTtsClick,
modifier = Modifier
@ -163,7 +201,7 @@ private fun BoxScope.TtsControls(state: ReaderScreenState) {
.alpha(TTS_BUTTONS_CONTROL_ALPHA)
) {
Text(
text = stringResource(R.string.tts_pause),
text = state.pauseTtsButtonText,
fontWeight = FontWeight.Bold
)
}
@ -204,10 +242,10 @@ private fun BackToTopFab(state: ReaderScreenState) {
@Composable
private fun BottomAppBarOfReaderScreen(
onBookmarkClick: () -> Unit,
onBackClick: () -> Unit,
onHomeClick: () -> Unit,
onForwardClick: () -> Unit,
bookmarkButtonItem: Triple<() -> Unit, () -> Unit, Drawable>,
previousPageButtonItem: Pair<() -> Unit, () -> Unit>,
onHomeButtonClick: () -> Unit,
nextPageButtonItem: Pair<() -> Unit, () -> Unit>,
onTocClick: () -> Unit
) {
BottomAppBar(
@ -223,33 +261,36 @@ private fun BottomAppBarOfReaderScreen(
) {
// Bookmark Icon
BottomAppBarButtonIcon(
onBookmarkClick,
Drawable(R.drawable.ic_bookmark_border_24dp),
stringResource(R.string.bookmarks)
onClick = bookmarkButtonItem.first,
onLongClick = bookmarkButtonItem.second,
buttonIcon = bookmarkButtonItem.third,
contentDescription = stringResource(R.string.bookmarks)
)
// Back Icon(for going to previous page)
BottomAppBarButtonIcon(
onBackClick,
Drawable(R.drawable.ic_keyboard_arrow_left_24dp),
stringResource(R.string.go_to_previous_page)
onClick = previousPageButtonItem.first,
onLongClick = previousPageButtonItem.second,
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)
BottomAppBarButtonIcon(
onHomeClick,
Drawable(R.drawable.action_home),
stringResource(R.string.menu_home)
onClick = onHomeButtonClick,
buttonIcon = Drawable(R.drawable.action_home),
contentDescription = stringResource(R.string.menu_home)
)
// Forward Icon(for going to next page)
BottomAppBarButtonIcon(
onForwardClick,
Drawable(R.drawable.ic_keyboard_arrow_right_24dp),
stringResource(R.string.go_to_next_page)
onClick = nextPageButtonItem.first,
onLongClick = nextPageButtonItem.second,
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)
BottomAppBarButtonIcon(
onTocClick,
Drawable(R.drawable.ic_toc_24dp),
stringResource(R.string.table_of_contents)
onClick = onTocClick,
buttonIcon = Drawable(R.drawable.ic_toc_24dp),
contentDescription = stringResource(R.string.table_of_contents)
)
}
}
@ -258,10 +299,14 @@ private fun BottomAppBarOfReaderScreen(
@Composable
private fun BottomAppBarButtonIcon(
onClick: () -> Unit,
onLongClick: (() -> Unit)? = null,
buttonIcon: IconItem,
contentDescription: String
) {
IconButton(onClick = onClick) {
IconButton(
onClick = onClick,
modifier = Modifier.combinedClickable(onClick = onClick, onLongClick = onLongClick)
) {
Icon(
buttonIcon.toPainter(),
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.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.
@ -59,7 +61,7 @@ data class ReaderScreenState(
* - [Boolean]: Whether to show/hide full screen mode.
* - [ComposeView]: full screen view.
*/
val fullScreenItem: Pair<Boolean, ComposeView>,
val fullScreenItem: Pair<Boolean, ComposeView?>,
/**
* Manages the showing of "BackToTop" fab button.
*/
@ -72,5 +74,47 @@ data class ReaderScreenState(
val onExitFullscreenClick: () -> Unit = {},
val showTtsControls: Boolean = false,
val onPauseTtsClick: () -> Unit = {},
/**
* Manages the showing of TTS button text(Pause/Resume).
*/
val pauseTtsButtonText: String,
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_BUTTON_ICON_SIZE = 30.dp
const val TTS_BUTTONS_CONTROL_ALPHA = 0.6f
val CLOSE_ALL_TAB_BUTTON_BOTTOM_PADDING = 24.dp
}