diff --git a/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivityScreen.kt b/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivityScreen.kt index 3a9a1c90b..0c14f77da 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivityScreen.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivityScreen.kt @@ -32,6 +32,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -181,7 +182,7 @@ fun BottomNavigationBar( val currentDestinationRoute = navBackStackEntry?.destination?.route BottomAppBar( containerColor = Black, - contentColor = White, + contentColor = White.copy(alpha = 0.5f), scrollBehavior = bottomAppBarScrollBehaviour ) { bottomNavItems.forEach { item -> @@ -198,11 +199,14 @@ fun BottomNavigationBar( icon = { Icon( painter = painterResource(id = item.iconRes), - contentDescription = item.title + contentDescription = item.title, + tint = White ) }, - label = { Text(item.title) }, - modifier = Modifier.semantics { testTag = item.testingTag } + label = { Text(item.title, color = White) }, + modifier = Modifier.semantics { testTag = item.testingTag }, + colors = NavigationBarItemDefaults.colors() + .copy(selectedIndicatorColor = White.copy(alpha = 0.3f)) ) } } 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 8b6af2fc1..430614c6b 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 @@ -34,7 +34,6 @@ import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -83,13 +82,13 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -117,7 +116,6 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.zIndex import androidx.navigation.NavHostController import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions import org.kiwix.kiwixmobile.core.downloader.downloadManager.HUNDERED @@ -192,10 +190,14 @@ fun ReaderScreen( mainActivityBottomAppBarScrollBehaviour: BottomAppBarScrollBehavior?, navigationIcon: @Composable () -> Unit ) { - val localWebViewScrollState: MutableState = - remember { mutableStateOf(ScrollState(0)) } + // For managing the scroll event handling of webView. + val shouldUpdateTopAppBarAndBottomAppBarOnScrolling = remember { mutableStateOf(true) } val topAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val bottomAppBarScrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior() + LaunchedEffect(bottomAppBarScrollBehavior.state.heightOffset) { + mainActivityBottomAppBarScrollBehaviour?.state?.heightOffset = + bottomAppBarScrollBehavior.state.heightOffset + } KiwixDialogTheme { Box(Modifier.fillMaxSize()) { Scaffold( @@ -213,11 +215,6 @@ fun ReaderScreen( .systemBarsPadding() .nestedScroll(topAppBarScrollBehavior.nestedScrollConnection) .nestedScroll(bottomAppBarScrollBehavior.nestedScrollConnection) - .let { baseModifier -> - mainActivityBottomAppBarScrollBehaviour?.let { - baseModifier.nestedScroll(it.nestedScrollConnection) - } ?: baseModifier - } .semantics { testTag = READER_SCREEN_TESTING_TAG } ) { paddingValues -> OnBackPressed(onUserBackPressed, navHostController) @@ -225,9 +222,13 @@ fun ReaderScreen( state, Modifier.padding(paddingValues), bottomAppBarScrollBehavior, - localWebViewScrollState.value + topAppBarScrollBehavior, + shouldUpdateTopAppBarAndBottomAppBarOnScrolling ) } + LaunchedEffect(showTableOfContentDrawer.value) { + shouldUpdateTopAppBarAndBottomAppBarOnScrolling.value = !showTableOfContentDrawer.value + } if (showTableOfContentDrawer.value) { // Showing the background color on screen so that it look same as navigation drawer. Box( @@ -248,7 +249,6 @@ fun ReaderScreen( TableDrawerSheet( title = state.tableOfContentTitle, sections = documentSections.orEmpty(), - localWebViewScrollState, state.selectedWebView, showTableOfContentDrawer ) @@ -297,7 +297,8 @@ private fun ReaderContentLayout( state: ReaderScreenState, modifier: Modifier = Modifier, bottomAppBarScrollBehavior: BottomAppBarScrollBehavior, - webViewScrollState: ScrollState? + topAppBarScrollBehavior: TopAppBarScrollBehavior, + shouldUpdateTopAppBarAndBottomAppBarOnScrolling: MutableState, ) { Box(modifier = modifier.fillMaxSize()) { TabSwitcherAnimated(state) @@ -307,7 +308,12 @@ private fun ReaderContentLayout( state.fullScreenItem.first -> ShowFullScreenView(state) else -> { - ShowZIMFileContent(state, webViewScrollState) + ShowZIMFileContent( + state, + bottomAppBarScrollBehavior, + topAppBarScrollBehavior, + shouldUpdateTopAppBarAndBottomAppBarOnScrolling + ) ShowProgressBarIfZIMFilePageIsLoading(state) Column(Modifier.align(Alignment.BottomCenter)) { TtsControls(state) @@ -332,12 +338,11 @@ private fun ReaderContentLayout( } } -@Suppress("LongMethod", "UnsafeCallOnNullableType") +@Suppress("LongMethod") @Composable fun TableDrawerSheet( title: String, sections: List, - webViewScrollState: MutableState, selectedWebView: KiwixWebView?, showTableOfContentDrawer: MutableState ) { @@ -346,13 +351,6 @@ fun TableDrawerSheet( } else { White } - var scrollToSectionIndex by remember { mutableStateOf(null) } - val coroutineScope = rememberCoroutineScope() - LaunchedEffect(scrollToSectionIndex) { - scrollToSectionIndex?.let { - webViewScrollState.value = null - } - } ModalDrawerSheet( modifier = Modifier.width(NAVIGATION_DRAWER_WIDTH), drawerShape = RectangleShape, @@ -366,10 +364,10 @@ fun TableDrawerSheet( modifier = Modifier .fillMaxWidth() .clickable { - coroutineScope.launch { - webViewScrollState.value?.animateScrollTo(ZERO) - } - showTableOfContentDrawer.update { false } + onTableOfContentHeaderClick( + selectedWebView, + showTableOfContentDrawer + ) } .padding(horizontal = SIXTEEN_DP, vertical = TWELVE_DP) ) @@ -384,28 +382,43 @@ fun TableDrawerSheet( ), modifier = Modifier .fillMaxWidth() - .clickable { scrollToSectionIndex = index } + .clickable { + onTableOfContentSectionClick( + selectedWebView, + index, + sections, + showTableOfContentDrawer + ) + } .padding(start = paddingStart.dp, top = EIGHT_DP, bottom = EIGHT_DP, end = SIXTEEN_DP) ) } } } - LaunchedEffect(webViewScrollState.value) { - if (webViewScrollState.value == null && - scrollToSectionIndex != null && - hasItemForPositionInDocumentSectionsList(scrollToSectionIndex!!, sections) - ) { - val targetId = sections[scrollToSectionIndex!!].id.replace("'", "\\'") - selectedWebView?.evaluateJavascript( - "document.getElementById('$targetId')?.scrollIntoView();", - null - ) - delay(HUNDERED.toLong()) - webViewScrollState.value = ScrollState(selectedWebView?.scrollY ?: ZERO) - scrollToSectionIndex = null - showTableOfContentDrawer.update { false } - } +} + +private fun onTableOfContentHeaderClick( + selectedWebView: KiwixWebView?, + showTableOfContentDrawer: MutableState +) { + selectedWebView?.scrollY = ZERO + showTableOfContentDrawer.update { false } +} + +private fun onTableOfContentSectionClick( + selectedWebView: KiwixWebView?, + position: Int, + sections: List, + showTableOfContentDrawer: MutableState +) { + if (hasItemForPositionInDocumentSectionsList(position, sections)) { + val targetId = sections[position].id.replace("'", "\\'") + selectedWebView?.evaluateJavascript( + "document.getElementById('$targetId')?.scrollIntoView();", + null + ) } + showTableOfContentDrawer.update { false } } private fun hasItemForPositionInDocumentSectionsList( @@ -523,49 +536,50 @@ private fun BoxScope.CloseFullScreenImageButton( @OptIn(ExperimentalMaterial3Api::class) @Composable -private fun ShowZIMFileContent(state: ReaderScreenState, webViewScrollState: ScrollState?) { +private fun ShowZIMFileContent( + state: ReaderScreenState, + bottomAppBarScrollBehavior: BottomAppBarScrollBehavior, + topAppBarScrollBehavior: TopAppBarScrollBehavior, + shouldUpdateTopAppBarAndBottomAppBarOnScrolling: MutableState +) { state.selectedWebView?.let { selectedWebView -> key(selectedWebView) { - ScrollableWebViewWithNestedScroll( - selectedWebView = selectedWebView, - modifier = Modifier.fillMaxSize(), - webViewScrollState = webViewScrollState - ) - } - } -} + DisposableEffect(Unit) { + val listener = View.OnScrollChangeListener { _, _, scrollY, _, oldScrollY -> + val deltaY = (scrollY - oldScrollY).toFloat() + if (deltaY == 0f || !shouldUpdateTopAppBarAndBottomAppBarOnScrolling.value) return@OnScrollChangeListener + val topLimit = topAppBarScrollBehavior.state.heightOffsetLimit + val bottomLimit = bottomAppBarScrollBehavior.state.heightOffsetLimit -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun ScrollableWebViewWithNestedScroll( - selectedWebView: KiwixWebView, - modifier: Modifier = Modifier, - webViewScrollState: ScrollState? -) { - Box( - modifier = modifier - .fillMaxSize() - .let { baseModifier -> - webViewScrollState?.let { - baseModifier.verticalScroll(it) - } ?: run { - baseModifier + topAppBarScrollBehavior.state.heightOffset = + (topAppBarScrollBehavior.state.heightOffset - deltaY) + .coerceIn(topLimit, 0f) + + bottomAppBarScrollBehavior.state.heightOffset = + (bottomAppBarScrollBehavior.state.heightOffset - deltaY) + .coerceIn(bottomLimit, 0f) + } + + selectedWebView.setOnScrollChangeListener(listener) + + onDispose { + selectedWebView.setOnScrollChangeListener(null) } } - ) { - AndroidView( - factory = { context -> - FrameLayout(context).apply { - (selectedWebView.parent as? ViewGroup)?.removeView(selectedWebView) - selectedWebView.layoutParams = FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT - ) - addView(selectedWebView) - } - }, - modifier = Modifier.fillMaxSize(), - ) + AndroidView( + factory = { context -> + FrameLayout(context).apply { + (selectedWebView.parent as? ViewGroup)?.removeView(selectedWebView) + selectedWebView.layoutParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT + ) + addView(selectedWebView) + } + }, + modifier = Modifier.fillMaxSize(), + ) + } } }