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