diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/library/LibraryRobot.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/library/LibraryRobot.kt index 2f50fbbdc..5c830c374 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/library/LibraryRobot.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/library/LibraryRobot.kt @@ -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 diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt index d5566eacc..0fe52e5dc 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt @@ -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) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt index 05e4f6c81..ab8255bbf 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt @@ -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::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 diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreen.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreen.kt index 04b09ddee..7bc997981 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreen.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreen.kt @@ -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) { + 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, + 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() + ) + } + } + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreenState.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreenState.kt index 751a6cbda..2da1a70db 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreenState.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreenState.kt @@ -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, + val fullScreenItem: Pair, /** * 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, + /** + * 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 ) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt index e65eb5229..40a6632a2 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt @@ -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 }