mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-08-03 10:46:53 -04:00
Refactored the TOC button functionality to handle cases where custom apps are configured to disable it.
* Fixed: Missing bottom margin in custom apps. * Fixed: Reader's bottom app bar not appearing after closing or selecting a tab. * Fixed: Menu not showing in the toolbar when the application is freshly launched in custom apps. * Refactored the scroll behavior of the toolbar and bottom app bar to sync with WebView scrolling in the Compose UI.
This commit is contained in:
parent
2608694442
commit
3ebb37d5cc
@ -95,10 +95,10 @@ fun LocalLibraryScreen(
|
|||||||
snackbarHost = { KiwixSnackbarHost(snackbarHostState = state.snackBarHostState) },
|
snackbarHost = { KiwixSnackbarHost(snackbarHostState = state.snackBarHostState) },
|
||||||
topBar = {
|
topBar = {
|
||||||
KiwixAppBar(
|
KiwixAppBar(
|
||||||
stringResource(R.string.library),
|
title = stringResource(R.string.library),
|
||||||
navigationIcon,
|
navigationIcon = navigationIcon,
|
||||||
state.actionMenuItems,
|
actionMenuItems = state.actionMenuItems,
|
||||||
scrollBehavior
|
topAppBarScrollBehavior = scrollBehavior
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = { SelectFileButton(fabButtonClick) },
|
floatingActionButton = { SelectFileButton(fabButtonClick) },
|
||||||
|
@ -100,10 +100,10 @@ fun OnlineLibraryScreen(
|
|||||||
snackbarHost = { KiwixSnackbarHost(snackbarHostState = state.snackBarHostState) },
|
snackbarHost = { KiwixSnackbarHost(snackbarHostState = state.snackBarHostState) },
|
||||||
topBar = {
|
topBar = {
|
||||||
KiwixAppBar(
|
KiwixAppBar(
|
||||||
stringResource(string.download),
|
title = stringResource(string.download),
|
||||||
navigationIcon,
|
navigationIcon = navigationIcon,
|
||||||
actionMenuItems,
|
actionMenuItems = actionMenuItems,
|
||||||
scrollBehavior,
|
topAppBarScrollBehavior = scrollBehavior,
|
||||||
searchBar = searchBarIfActive(state)
|
searchBar = searchBarIfActive(state)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -211,12 +211,12 @@ class KiwixReaderFragment : CoreReaderFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
// override fun onPause() {
|
||||||
super.onPause()
|
// super.onPause()
|
||||||
// ScrollingViewWithBottomNavigationBehavior changes the margin to the size of the nav bar,
|
// // ScrollingViewWithBottomNavigationBehavior changes the margin to the size of the nav bar,
|
||||||
// this resets the margin to zero, before fragment navigation.
|
// // this resets the margin to zero, before fragment navigation.
|
||||||
setBottomMarginToNavHostContainer(0)
|
// setBottomMarginToNavHostContainer(ZERO)
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
|
override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||||
@ -233,7 +233,6 @@ class KiwixReaderFragment : CoreReaderFragment() {
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
setFragmentContainerBottomMarginToSizeOfNavBar()
|
|
||||||
if (isFullScreenVideo || isInFullScreenMode()) {
|
if (isFullScreenVideo || isInFullScreenMode()) {
|
||||||
hideNavBar()
|
hideNavBar()
|
||||||
}
|
}
|
||||||
@ -306,11 +305,10 @@ class KiwixReaderFragment : CoreReaderFragment() {
|
|||||||
requireContext(),
|
requireContext(),
|
||||||
this,
|
this,
|
||||||
attrs ?: throw IllegalArgumentException("AttributeSet must not be null"),
|
attrs ?: throw IllegalArgumentException("AttributeSet must not be null"),
|
||||||
null,
|
|
||||||
requireNotNull(readerScreenState.value.fullScreenItem.second),
|
requireNotNull(readerScreenState.value.fullScreenItem.second),
|
||||||
CoreWebViewClient(this, requireNotNull(zimReaderContainer)),
|
CoreWebViewClient(this, requireNotNull(zimReaderContainer)),
|
||||||
// requireNotNull(toolbarContainer),
|
onToolbarOffsetChanged = { offsetY -> toolbarOffsetY.value = offsetY },
|
||||||
// requireNotNull(bottomToolbar),
|
onBottomAppBarOffsetChanged = { bottomOffsetY -> bottomAppBarOffsetY.value = bottomOffsetY },
|
||||||
sharedPreferenceUtil = requireNotNull(sharedPreferenceUtil),
|
sharedPreferenceUtil = requireNotNull(sharedPreferenceUtil),
|
||||||
parentNavigationBar = requireActivity().findViewById(R.id.bottom_nav_view)
|
parentNavigationBar = requireActivity().findViewById(R.id.bottom_nav_view)
|
||||||
)
|
)
|
||||||
|
@ -87,11 +87,20 @@ fun ZimHostScreen(
|
|||||||
) {
|
) {
|
||||||
KiwixTheme {
|
KiwixTheme {
|
||||||
Scaffold(topBar = {
|
Scaffold(topBar = {
|
||||||
KiwixAppBar(stringResource(R.string.menu_wifi_hotspot), navigationIcon)
|
KiwixAppBar(
|
||||||
|
title = stringResource(R.string.menu_wifi_hotspot),
|
||||||
|
navigationIcon = navigationIcon
|
||||||
|
)
|
||||||
}) { contentPadding ->
|
}) { contentPadding ->
|
||||||
Column(modifier = Modifier.fillMaxSize().padding(contentPadding)) {
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(contentPadding)
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(horizontal = SIXTEEN_DP),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = SIXTEEN_DP),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
ServerIpText(serverIpText, Modifier.weight(1f), LocalContext.current)
|
ServerIpText(serverIpText, Modifier.weight(1f), LocalContext.current)
|
||||||
|
@ -23,8 +23,7 @@
|
|||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/readerFragment"
|
android:id="@+id/readerFragment"
|
||||||
android:name="org.kiwix.kiwixmobile.nav.destination.reader.KiwixReaderFragment"
|
android:name="org.kiwix.kiwixmobile.nav.destination.reader.KiwixReaderFragment"
|
||||||
android:label="Reader"
|
android:label="Reader">
|
||||||
tools:layout="@layout/fragment_reader">
|
|
||||||
<argument
|
<argument
|
||||||
android:name="zimFileUri"
|
android:name="zimFileUri"
|
||||||
android:defaultValue=""
|
android:defaultValue=""
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<ID>LongParameterList:MainMenu.kt$MainMenu.Factory$( menu: Menu, webViews: MutableList<KiwixWebView>, urlIsValid: Boolean, menuClickListener: MenuClickListener, disableReadAloud: Boolean, disableTabs: Boolean )</ID>
|
<ID>LongParameterList:MainMenu.kt$MainMenu.Factory$( menu: Menu, webViews: MutableList<KiwixWebView>, urlIsValid: Boolean, menuClickListener: MenuClickListener, disableReadAloud: Boolean, disableTabs: Boolean )</ID>
|
||||||
<ID>LongParameterList:PageTestHelpers.kt$( bookmarkTitle: String = "bookmarkTitle", isSelected: Boolean = false, id: Long = 2, zimId: String = "zimId", zimName: String = "zimName", zimFilePath: String = "zimFilePath", bookmarkUrl: String = "bookmarkUrl", favicon: String = "favicon" )</ID>
|
<ID>LongParameterList:PageTestHelpers.kt$( bookmarkTitle: String = "bookmarkTitle", isSelected: Boolean = false, id: Long = 2, zimId: String = "zimId", zimName: String = "zimName", zimFilePath: String = "zimFilePath", bookmarkUrl: String = "bookmarkUrl", favicon: String = "favicon" )</ID>
|
||||||
<ID>LongParameterList:Repository.kt$Repository$( private val libkiwixBookOnDisk: LibkiwixBookOnDisk, private val libkiwixBookmarks: LibkiwixBookmarks, private val historyRoomDao: HistoryRoomDao, private val webViewHistoryRoomDao: WebViewHistoryRoomDao, private val notesRoomDao: NotesRoomDao, private val languageDao: NewLanguagesDao, private val recentSearchRoomDao: RecentSearchRoomDao, private val zimReaderContainer: ZimReaderContainer )</ID>
|
<ID>LongParameterList:Repository.kt$Repository$( private val libkiwixBookOnDisk: LibkiwixBookOnDisk, private val libkiwixBookmarks: LibkiwixBookmarks, private val historyRoomDao: HistoryRoomDao, private val webViewHistoryRoomDao: WebViewHistoryRoomDao, private val notesRoomDao: NotesRoomDao, private val languageDao: NewLanguagesDao, private val recentSearchRoomDao: RecentSearchRoomDao, private val zimReaderContainer: ZimReaderContainer )</ID>
|
||||||
<ID>LongParameterList:ToolbarScrollingKiwixWebView.kt$ToolbarScrollingKiwixWebView$( context: Context, callback: WebViewCallback, attrs: AttributeSet, nonVideoView: ViewGroup?, videoView: ViewGroup?, webViewClient: CoreWebViewClient, sharedPreferenceUtil: SharedPreferenceUtil, private val parentNavigationBar: View? = null )</ID>
|
<ID>LongParameterList:ToolbarScrollingKiwixWebView.kt$ToolbarScrollingKiwixWebView$( context: Context, callback: WebViewCallback, attrs: AttributeSet, videoView: ViewGroup?, webViewClient: CoreWebViewClient, private val onToolbarOffsetChanged: ((Float) -> Unit)? = null, private val onBottomAppBarOffsetChanged: ((Float) -> Unit)? = null, sharedPreferenceUtil: SharedPreferenceUtil, private val parentNavigationBar: View? = null )</ID>
|
||||||
<ID>MagicNumber:ArticleCount.kt$ArticleCount$3</ID>
|
<ID>MagicNumber:ArticleCount.kt$ArticleCount$3</ID>
|
||||||
<ID>MagicNumber:CompatFindActionModeCallback.kt$CompatFindActionModeCallback$100</ID>
|
<ID>MagicNumber:CompatFindActionModeCallback.kt$CompatFindActionModeCallback$100</ID>
|
||||||
<ID>MagicNumber:DownloadItem.kt$DownloadItem$1000L</ID>
|
<ID>MagicNumber:DownloadItem.kt$DownloadItem$1000L</ID>
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Kiwix Android
|
|
||||||
* Copyright (c) 2019 Kiwix <android.kiwix.org>
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.kiwix.kiwixmobile.core.extensions
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.TextView
|
|
||||||
|
|
||||||
fun TextView.setTextAndVisibility(nullableText: String?) =
|
|
||||||
if (nullableText?.isNotEmpty() == true) {
|
|
||||||
text = nullableText
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
} else {
|
|
||||||
visibility = View.GONE
|
|
||||||
}
|
|
@ -73,7 +73,7 @@ fun HelpScreen(
|
|||||||
KiwixTheme {
|
KiwixTheme {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
KiwixAppBar(stringResource(R.string.menu_help), navigationIcon)
|
KiwixAppBar(title = stringResource(R.string.menu_help), navigationIcon = navigationIcon)
|
||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Column(modifier = Modifier.padding(innerPadding)) {
|
Column(modifier = Modifier.padding(innerPadding)) {
|
||||||
|
@ -72,7 +72,13 @@ fun AddNoteDialogScreen(
|
|||||||
KiwixDialogTheme {
|
KiwixDialogTheme {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
snackbarHost = { KiwixSnackbarHost(snackbarHostState = snackBarHostState) },
|
snackbarHost = { KiwixSnackbarHost(snackbarHostState = snackBarHostState) },
|
||||||
topBar = { KiwixAppBar(stringResource(R.string.note), navigationIcon, actionMenuItems) }
|
topBar = {
|
||||||
|
KiwixAppBar(
|
||||||
|
title = stringResource(R.string.note),
|
||||||
|
navigationIcon = navigationIcon,
|
||||||
|
actionMenuItems = actionMenuItems
|
||||||
|
)
|
||||||
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -23,10 +23,9 @@ import org.kiwix.videowebview.VideoEnabledWebChromeClient
|
|||||||
|
|
||||||
class KiwixWebChromeClient(
|
class KiwixWebChromeClient(
|
||||||
private val callback: WebViewCallback,
|
private val callback: WebViewCallback,
|
||||||
nonVideoView: ViewGroup?,
|
|
||||||
videoView: ViewGroup?,
|
videoView: ViewGroup?,
|
||||||
webView: KiwixWebView?
|
webView: KiwixWebView?
|
||||||
) : VideoEnabledWebChromeClient(nonVideoView, videoView, null, webView) {
|
) : VideoEnabledWebChromeClient(videoView, null, webView) {
|
||||||
override fun onProgressChanged(view: WebView, progress: Int) {
|
override fun onProgressChanged(view: WebView, progress: Int) {
|
||||||
callback.webViewProgressChanged(progress, view)
|
callback.webViewProgressChanged(progress, view)
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,6 @@ open class KiwixWebView @SuppressLint("SetJavaScriptEnabled") constructor(
|
|||||||
context: Context,
|
context: Context,
|
||||||
private val callback: WebViewCallback,
|
private val callback: WebViewCallback,
|
||||||
attrs: AttributeSet,
|
attrs: AttributeSet,
|
||||||
private var nonVideoView: ViewGroup?,
|
|
||||||
videoView: ViewGroup?,
|
videoView: ViewGroup?,
|
||||||
private val webViewClient: CoreWebViewClient,
|
private val webViewClient: CoreWebViewClient,
|
||||||
val sharedPreferenceUtil: SharedPreferenceUtil
|
val sharedPreferenceUtil: SharedPreferenceUtil
|
||||||
@ -102,7 +101,7 @@ open class KiwixWebView @SuppressLint("SetJavaScriptEnabled") constructor(
|
|||||||
clearCache(true)
|
clearCache(true)
|
||||||
setWebViewClient(webViewClient)
|
setWebViewClient(webViewClient)
|
||||||
webChromeClient =
|
webChromeClient =
|
||||||
KiwixWebChromeClient(callback, nonVideoView, videoView, this).apply {
|
KiwixWebChromeClient(callback, videoView, this).apply {
|
||||||
setOnToggledFullscreen(
|
setOnToggledFullscreen(
|
||||||
object : ToggledFullscreenCallback {
|
object : ToggledFullscreenCallback {
|
||||||
override fun toggledFullscreen(fullscreen: Boolean) {
|
override fun toggledFullscreen(fullscreen: Boolean) {
|
||||||
@ -154,7 +153,6 @@ open class KiwixWebView @SuppressLint("SetJavaScriptEnabled") constructor(
|
|||||||
|
|
||||||
override fun onDetachedFromWindow() {
|
override fun onDetachedFromWindow() {
|
||||||
super.onDetachedFromWindow()
|
super.onDetachedFromWindow()
|
||||||
nonVideoView = null
|
|
||||||
textZoomJob?.cancel()
|
textZoomJob?.cancel()
|
||||||
textZoomJob = null
|
textZoomJob = null
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,12 @@ import android.util.AttributeSet
|
|||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import org.kiwix.kiwixmobile.core.utils.DimenUtils.getToolbarHeight
|
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.COMPOSE_BOTTOM_APP_BAR_DEFAULT_HEIGHT
|
||||||
|
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.COMPOSE_TOOLBAR_DEFAULT_HEIGHT
|
||||||
|
import org.kiwix.kiwixmobile.core.utils.DimenUtils.dpToPx
|
||||||
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
@Suppress("UnusedPrivateProperty")
|
@Suppress("UnusedPrivateProperty")
|
||||||
@ -32,28 +36,72 @@ class ToolbarScrollingKiwixWebView @JvmOverloads constructor(
|
|||||||
context: Context,
|
context: Context,
|
||||||
callback: WebViewCallback,
|
callback: WebViewCallback,
|
||||||
attrs: AttributeSet,
|
attrs: AttributeSet,
|
||||||
nonVideoView: ViewGroup?,
|
|
||||||
videoView: ViewGroup?,
|
videoView: ViewGroup?,
|
||||||
webViewClient: CoreWebViewClient,
|
webViewClient: CoreWebViewClient,
|
||||||
|
private val onToolbarOffsetChanged: ((Float) -> Unit)? = null,
|
||||||
|
private val onBottomAppBarOffsetChanged: ((Float) -> Unit)? = null,
|
||||||
sharedPreferenceUtil: SharedPreferenceUtil,
|
sharedPreferenceUtil: SharedPreferenceUtil,
|
||||||
private val parentNavigationBar: View? = null
|
private val parentNavigationBar: View? = null
|
||||||
) : KiwixWebView(
|
) : KiwixWebView(
|
||||||
context,
|
context,
|
||||||
callback,
|
callback,
|
||||||
attrs,
|
attrs,
|
||||||
nonVideoView,
|
|
||||||
videoView,
|
videoView,
|
||||||
webViewClient,
|
webViewClient,
|
||||||
sharedPreferenceUtil
|
sharedPreferenceUtil
|
||||||
) {
|
) {
|
||||||
private val toolbarHeight = context.getToolbarHeight()
|
private val toolbarHeight = context.dpToPx(COMPOSE_TOOLBAR_DEFAULT_HEIGHT)
|
||||||
|
private val bottomAppBarHeightPx = context.dpToPx(COMPOSE_BOTTOM_APP_BAR_DEFAULT_HEIGHT)
|
||||||
|
|
||||||
private var startY = 0f
|
private var startY = 0f
|
||||||
|
private var currentOffset = 0f
|
||||||
|
|
||||||
init {
|
init {
|
||||||
fixInitalScrollingIssue()
|
fixInitalScrollingIssue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts the internal offset of the WebView based on scroll delta.
|
||||||
|
*
|
||||||
|
* Positive scrollDelta = user scrolling down (hide UI)
|
||||||
|
* Negative scrollDelta = user scrolling up (show UI)
|
||||||
|
*/
|
||||||
|
|
||||||
|
private fun moveToolbar(scrollDelta: Int): Boolean {
|
||||||
|
val newOffset = when {
|
||||||
|
scrollDelta > 0 -> max(-toolbarHeight.toFloat(), currentOffset - scrollDelta)
|
||||||
|
else -> min(0f, currentOffset - scrollDelta)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newOffset != currentOffset) {
|
||||||
|
currentOffset = newOffset
|
||||||
|
notifyOffsetChanged(newOffset)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies Compose UI about toolbar offset.
|
||||||
|
*/
|
||||||
|
private fun notifyOffsetChanged(offset: Float) {
|
||||||
|
onToolbarOffsetChanged?.invoke(offset)
|
||||||
|
|
||||||
|
// Compute offset for bottomAppBar using height ratio
|
||||||
|
val bottomOffset = offset * -1 * (bottomAppBarHeightPx.toFloat() / toolbarHeight)
|
||||||
|
onBottomAppBarOffsetChanged?.invoke(bottomOffset)
|
||||||
|
|
||||||
|
// Optional: Animate parent navigation bar (if still using it)
|
||||||
|
parentNavigationBar?.let { view ->
|
||||||
|
val offsetFactor = view.height / toolbarHeight.toFloat()
|
||||||
|
view.translationY = offset * -1 * offsetFactor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust WebView position to prevent layout jump
|
||||||
|
this.translationY = offset
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The webview needs to be scrolled with 0 to not be slightly hidden on startup.
|
* The webview needs to be scrolled with 0 to not be slightly hidden on startup.
|
||||||
* See https://github.com/kiwix/kiwix-android/issues/2304 for issue description.
|
* See https://github.com/kiwix/kiwix-android/issues/2304 for issue description.
|
||||||
@ -62,68 +110,27 @@ class ToolbarScrollingKiwixWebView @JvmOverloads constructor(
|
|||||||
moveToolbar(0)
|
moveToolbar(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("FunctionOnlyReturningConstant", "UnusedParameter")
|
|
||||||
private fun moveToolbar(scrollDelta: Int): Boolean {
|
|
||||||
// val originalTranslation = toolbarView.translationY
|
|
||||||
// val newTranslation =
|
|
||||||
// if (scrollDelta > 0) {
|
|
||||||
// // scroll down
|
|
||||||
// max(-toolbarHeight.toFloat(), originalTranslation - scrollDelta)
|
|
||||||
// } else {
|
|
||||||
// // scroll up
|
|
||||||
// min(0f, originalTranslation - scrollDelta)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// toolbarView.translationY = newTranslation
|
|
||||||
// bottomBarView.translationY =
|
|
||||||
// newTranslation * -1 * (bottomBarView.height / toolbarHeight.toFloat())
|
|
||||||
// parentNavigationBar?.let {
|
|
||||||
// it.translationY = newTranslation * -1 * (it.height / toolbarHeight.toFloat())
|
|
||||||
// }
|
|
||||||
// this.translationY = newTranslation + toolbarHeight
|
|
||||||
// return toolbarHeight + newTranslation != 0f && newTranslation != 0f
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||||
// val transY = toolbarView.translationY.toInt()
|
if (sharedPreferenceUtil.prefFullScreen) return super.onTouchEvent(event)
|
||||||
// when (event.actionMasked) {
|
|
||||||
// MotionEvent.ACTION_DOWN -> startY = event.rawY
|
when (event.actionMasked) {
|
||||||
// MotionEvent.ACTION_MOVE -> {
|
MotionEvent.ACTION_DOWN -> {
|
||||||
// // If we are in fullscreen don't scroll bar
|
startY = event.rawY
|
||||||
// if (sharedPreferenceUtil.prefFullScreen) {
|
}
|
||||||
// return super.onTouchEvent(event)
|
|
||||||
// }
|
MotionEvent.ACTION_MOVE -> {
|
||||||
// // Filter out zooms since we don't want to affect the toolbar when zooming
|
if (event.pointerCount == 1) {
|
||||||
// if (event.pointerCount == 1) {
|
val diffY = (event.rawY - startY).toInt()
|
||||||
// val diffY = (event.rawY - startY).toInt()
|
startY = event.rawY
|
||||||
// startY = event.rawY
|
if (moveToolbar(-diffY)) {
|
||||||
// if (moveToolbar(-diffY)) {
|
event.offsetLocation(0f, -diffY.toFloat())
|
||||||
// event.offsetLocation(0f, -diffY.toFloat())
|
return super.onTouchEvent(event)
|
||||||
// return super.onTouchEvent(event)
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// // If the toolbar is half-visible,
|
|
||||||
// // either open or close it entirely depending on how far it is visible
|
|
||||||
// MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL ->
|
|
||||||
// if (transY != 0 && transY > -toolbarHeight) {
|
|
||||||
// if (transY > -toolbarHeight / 2) {
|
|
||||||
// ensureToolbarDisplayed()
|
|
||||||
// } else {
|
|
||||||
// ensureToolbarHidden()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
return super.onTouchEvent(event)
|
return super.onTouchEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ensureToolbarDisplayed() {
|
|
||||||
moveToolbar(-toolbarHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ensureToolbarHidden() {
|
|
||||||
moveToolbar(toolbarHeight)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,6 @@ 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.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Menu
|
import androidx.compose.material.icons.filled.Menu
|
||||||
import androidx.compose.material3.SnackbarDuration
|
import androidx.compose.material3.SnackbarDuration
|
||||||
@ -171,7 +170,6 @@ 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.components.NavigationIcon
|
||||||
import org.kiwix.kiwixmobile.core.ui.components.rememberBottomNavigationVisibility
|
|
||||||
import org.kiwix.kiwixmobile.core.ui.models.IconItem
|
import org.kiwix.kiwixmobile.core.ui.models.IconItem
|
||||||
import org.kiwix.kiwixmobile.core.ui.theme.White
|
import org.kiwix.kiwixmobile.core.ui.theme.White
|
||||||
import org.kiwix.kiwixmobile.core.utils.DimenUtils.getWindowWidth
|
import org.kiwix.kiwixmobile.core.utils.DimenUtils.getWindowWidth
|
||||||
@ -320,6 +318,8 @@ 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
|
||||||
|
val toolbarOffsetY = mutableStateOf(0f)
|
||||||
|
val bottomAppBarOffsetY = mutableStateOf(0f)
|
||||||
|
|
||||||
protected var readerMenuState: ReaderMenuState? = null
|
protected var readerMenuState: ReaderMenuState? = null
|
||||||
private var composeView: ComposeView? = null
|
private var composeView: ComposeView? = null
|
||||||
@ -348,7 +348,7 @@ abstract class CoreReaderFragment :
|
|||||||
previousPageButtonItem = Triple({ goBack() }, { showBackwardHistory() }, false),
|
previousPageButtonItem = Triple({ goBack() }, { showBackwardHistory() }, false),
|
||||||
onHomeButtonClick = { openMainPage() },
|
onHomeButtonClick = { openMainPage() },
|
||||||
nextPageButtonItem = Triple({ goForward() }, { showForwardHistory() }, false),
|
nextPageButtonItem = Triple({ goForward() }, { showForwardHistory() }, false),
|
||||||
onTocClick = { openToc() },
|
tocButtonItem = false to { },
|
||||||
onCloseAllTabs = { closeAllTabs() },
|
onCloseAllTabs = { closeAllTabs() },
|
||||||
bottomNavigationHeight = ZERO,
|
bottomNavigationHeight = ZERO,
|
||||||
shouldShowBottomAppBar = true,
|
shouldShowBottomAppBar = true,
|
||||||
@ -473,11 +473,6 @@ abstract class CoreReaderFragment :
|
|||||||
readerMenuState = createMainMenu()
|
readerMenuState = createMainMenu()
|
||||||
composeView?.apply {
|
composeView?.apply {
|
||||||
setContent {
|
setContent {
|
||||||
val lazyListState = rememberLazyListState()
|
|
||||||
val isBottomNavVisible = rememberBottomNavigationVisibility(lazyListState)
|
|
||||||
LaunchedEffect(isBottomNavVisible) {
|
|
||||||
(activity as? CoreMainActivity)?.toggleBottomNavigation(isBottomNavVisible)
|
|
||||||
}
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
snapshotFlow { webViewList.size }
|
snapshotFlow { webViewList.size }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
@ -491,7 +486,8 @@ abstract class CoreReaderFragment :
|
|||||||
bottomNavigationHeight = getBottomNavigationHeight(),
|
bottomNavigationHeight = getBottomNavigationHeight(),
|
||||||
readerScreenTitle = context.getString(R.string.reader),
|
readerScreenTitle = context.getString(R.string.reader),
|
||||||
darkModeViewPainter = darkModeViewPainter,
|
darkModeViewPainter = darkModeViewPainter,
|
||||||
fullScreenItem = fullScreenItem.first to getVideoView()
|
fullScreenItem = fullScreenItem.first to getVideoView(),
|
||||||
|
tocButtonItem = getTocButtonStateAndAction()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -514,7 +510,8 @@ abstract class CoreReaderFragment :
|
|||||||
iconTint = navigationIconTint()
|
iconTint = navigationIconTint()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
listState = lazyListState
|
toolbarOffsetY = toolbarOffsetY,
|
||||||
|
bottomAppBarOffsetY = bottomAppBarOffsetY
|
||||||
)
|
)
|
||||||
DialogHost(alertDialogShower as AlertDialogShower)
|
DialogHost(alertDialogShower as AlertDialogShower)
|
||||||
}
|
}
|
||||||
@ -629,6 +626,20 @@ abstract class CoreReaderFragment :
|
|||||||
|
|
||||||
private fun getBottomNavigationHeight(): Int = getBottomNavigationView()?.measuredHeight ?: ZERO
|
private fun getBottomNavigationHeight(): Int = getBottomNavigationView()?.measuredHeight ?: ZERO
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the visibility state and click action for the TOC (Table of Contents) button
|
||||||
|
* shown in the reader's bottom app bar.
|
||||||
|
*
|
||||||
|
* @return A [Pair] containing:
|
||||||
|
* - [Boolean]: Indicates whether the TOC button should be enabled (e.g., can be disabled
|
||||||
|
* in certain custom app configurations where the sidebar is turned off).
|
||||||
|
* - [() -> Unit]: The action to perform when the TOC button is clicked.
|
||||||
|
*
|
||||||
|
* Note: If modifying this method, ensure it is thoroughly tested in custom app variants
|
||||||
|
* where sidebar behavior may differ.
|
||||||
|
*/
|
||||||
|
open fun getTocButtonStateAndAction(): Pair<Boolean, () -> Unit> = true to { openToc() }
|
||||||
|
|
||||||
private fun navigationIconContentDescription() =
|
private fun navigationIconContentDescription() =
|
||||||
if (readerMenuState?.isInTabSwitcher == true) {
|
if (readerMenuState?.isInTabSwitcher == true) {
|
||||||
R.string.search_open_in_new_tab
|
R.string.search_open_in_new_tab
|
||||||
@ -949,7 +960,6 @@ abstract class CoreReaderFragment :
|
|||||||
protected open fun hideTabSwitcher(shouldCloseZimBook: Boolean = true) {
|
protected open fun hideTabSwitcher(shouldCloseZimBook: Boolean = true) {
|
||||||
setUpDrawerToggle()
|
setUpDrawerToggle()
|
||||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
|
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
|
||||||
selectTab(currentWebViewIndex)
|
|
||||||
readerScreenState.update {
|
readerScreenState.update {
|
||||||
copy(
|
copy(
|
||||||
shouldShowBottomAppBar = true,
|
shouldShowBottomAppBar = true,
|
||||||
@ -958,6 +968,7 @@ abstract class CoreReaderFragment :
|
|||||||
}
|
}
|
||||||
showSearchPlaceHolderInToolbar(false)
|
showSearchPlaceHolderInToolbar(false)
|
||||||
readerMenuState?.showWebViewOptions(urlIsValid())
|
readerMenuState?.showWebViewOptions(urlIsValid())
|
||||||
|
selectTab(currentWebViewIndex)
|
||||||
// Reset the top margin of web views to 0 to remove any previously set margin
|
// Reset the top margin of web views to 0 to remove any previously set margin
|
||||||
// This ensures that the web views are displayed without any additional top margin for kiwix custom apps.
|
// This ensures that the web views are displayed without any additional top margin for kiwix custom apps.
|
||||||
// setTopMarginToWebViews(0)
|
// setTopMarginToWebViews(0)
|
||||||
@ -1077,7 +1088,7 @@ abstract class CoreReaderFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openToc() {
|
protected fun openToc() {
|
||||||
drawerLayout?.openDrawer(GravityCompat.END)
|
drawerLayout?.openDrawer(GravityCompat.END)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1413,14 +1424,13 @@ abstract class CoreReaderFragment :
|
|||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
protected open fun createWebView(attrs: AttributeSet?): ToolbarScrollingKiwixWebView? {
|
protected open fun createWebView(attrs: AttributeSet?): ToolbarScrollingKiwixWebView? {
|
||||||
return ToolbarScrollingKiwixWebView(
|
return ToolbarScrollingKiwixWebView(
|
||||||
requireActivity(),
|
requireContext(),
|
||||||
this,
|
this,
|
||||||
attrs ?: throw IllegalArgumentException("AttributeSet must not be null"),
|
attrs ?: throw IllegalArgumentException("AttributeSet must not be null"),
|
||||||
null,
|
|
||||||
requireNotNull(readerScreenState.value.fullScreenItem.second),
|
requireNotNull(readerScreenState.value.fullScreenItem.second),
|
||||||
CoreWebViewClient(this, requireNotNull(zimReaderContainer)),
|
CoreWebViewClient(this, requireNotNull(zimReaderContainer)),
|
||||||
// requireNotNull(toolbarContainer),
|
onToolbarOffsetChanged = { offsetY -> toolbarOffsetY.value = offsetY },
|
||||||
// requireNotNull(bottomToolbar),
|
onBottomAppBarOffsetChanged = { bottomOffsetY -> bottomAppBarOffsetY.value = bottomOffsetY },
|
||||||
requireNotNull(sharedPreferenceUtil)
|
requireNotNull(sharedPreferenceUtil)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1821,7 +1831,6 @@ abstract class CoreReaderFragment :
|
|||||||
// Show content if there is `Open Library` button showing
|
// Show content if there is `Open Library` button showing
|
||||||
// and we are opening the ZIM file
|
// and we are opening the ZIM file
|
||||||
hideNoBookOpenViews()
|
hideNoBookOpenViews()
|
||||||
contentFrame?.visibility = VISIBLE
|
|
||||||
openAndSetInContainer(zimReaderSource)
|
openAndSetInContainer(zimReaderSource)
|
||||||
updateTitle()
|
updateTitle()
|
||||||
} else {
|
} else {
|
||||||
@ -2203,7 +2212,7 @@ abstract class CoreReaderFragment :
|
|||||||
|
|
||||||
private fun updateBottomToolbarVisibility() {
|
private fun updateBottomToolbarVisibility() {
|
||||||
readerScreenState.update {
|
readerScreenState.update {
|
||||||
copy(shouldShowBottomAppBar = !showTabSwitcher && !isInFullScreenMode())
|
copy(shouldShowBottomAppBar = readerMenuState?.isInTabSwitcher == false && !isInFullScreenMode())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2882,6 +2891,7 @@ abstract class CoreReaderFragment :
|
|||||||
}
|
}
|
||||||
selectTab(currentTab)
|
selectTab(currentTab)
|
||||||
onComplete.invoke()
|
onComplete.invoke()
|
||||||
|
readerMenuState?.showWebViewOptions(urlIsValid())
|
||||||
} catch (ignore: Exception) {
|
} catch (ignore: Exception) {
|
||||||
Log.w(TAG_KIWIX, "Kiwix shared preferences corrupted", ignore)
|
Log.w(TAG_KIWIX, "Kiwix shared preferences corrupted", ignore)
|
||||||
activity.toast(R.string.could_not_restore_tabs, Toast.LENGTH_LONG)
|
activity.toast(R.string.could_not_restore_tabs, Toast.LENGTH_LONG)
|
||||||
|
@ -61,7 +61,7 @@ const val TAB_MENU_ITEM_TESTING_TAG = "tabMenuItemTestingTag"
|
|||||||
@Stable
|
@Stable
|
||||||
class ReaderMenuState(
|
class ReaderMenuState(
|
||||||
private val menuClickListener: MenuClickListener,
|
private val menuClickListener: MenuClickListener,
|
||||||
private val isUrlValidInitially: Boolean,
|
isUrlValidInitially: Boolean,
|
||||||
private val disableReadAloud: Boolean = false,
|
private val disableReadAloud: Boolean = false,
|
||||||
private val disableTabs: Boolean = false,
|
private val disableTabs: Boolean = false,
|
||||||
private val disableSearch: Boolean = false
|
private val disableSearch: Boolean = false
|
||||||
|
@ -22,6 +22,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
@ -36,9 +37,11 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.PaddingValues
|
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.fillMaxHeight
|
||||||
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.offset
|
||||||
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.systemBarsPadding
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
@ -94,6 +97,7 @@ 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.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@ -144,12 +148,12 @@ const val CONTENT_LOADING_PROGRESSBAR_TESTING_TAG = "contentLoadingProgressBarTe
|
|||||||
@Composable
|
@Composable
|
||||||
fun ReaderScreen(
|
fun ReaderScreen(
|
||||||
state: ReaderScreenState,
|
state: ReaderScreenState,
|
||||||
listState: LazyListState,
|
|
||||||
actionMenuItems: List<ActionMenuItem>,
|
actionMenuItems: List<ActionMenuItem>,
|
||||||
|
toolbarOffsetY: MutableState<Float>,
|
||||||
|
bottomAppBarOffsetY: MutableState<Float>,
|
||||||
navigationIcon: @Composable () -> Unit
|
navigationIcon: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val (bottomNavHeight, lazyListState) =
|
val bottomNavHeightInDp = with(LocalDensity.current) { state.bottomNavigationHeight.toDp() }
|
||||||
rememberScrollBehavior(state.bottomNavigationHeight, listState)
|
|
||||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||||
KiwixDialogTheme {
|
KiwixDialogTheme {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@ -158,6 +162,7 @@ fun ReaderScreen(
|
|||||||
ReaderTopBar(
|
ReaderTopBar(
|
||||||
state,
|
state,
|
||||||
actionMenuItems,
|
actionMenuItems,
|
||||||
|
toolbarOffsetY,
|
||||||
scrollBehavior,
|
scrollBehavior,
|
||||||
navigationIcon
|
navigationIcon
|
||||||
)
|
)
|
||||||
@ -166,9 +171,13 @@ fun ReaderScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.systemBarsPadding()
|
.systemBarsPadding()
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
.padding(bottom = bottomNavHeight.value)
|
.padding(bottom = bottomNavHeightInDp)
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
ReaderContentLayout(state, Modifier.padding(paddingValues))
|
ReaderContentLayout(
|
||||||
|
state,
|
||||||
|
Modifier.padding(paddingValues),
|
||||||
|
bottomAppBarOffsetY
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -179,23 +188,33 @@ fun ReaderScreen(
|
|||||||
private fun ReaderTopBar(
|
private fun ReaderTopBar(
|
||||||
state: ReaderScreenState,
|
state: ReaderScreenState,
|
||||||
actionMenuItems: List<ActionMenuItem>,
|
actionMenuItems: List<ActionMenuItem>,
|
||||||
|
toolbarOffsetY: MutableState<Float>,
|
||||||
scrollBehavior: TopAppBarScrollBehavior,
|
scrollBehavior: TopAppBarScrollBehavior,
|
||||||
navigationIcon: @Composable () -> Unit,
|
navigationIcon: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
if (!state.shouldShowFullScreenMode && !state.fullScreenItem.first) {
|
if (!state.shouldShowFullScreenMode && !state.fullScreenItem.first) {
|
||||||
|
val animatedOffsetY by animateDpAsState(
|
||||||
|
targetValue = with(LocalDensity.current) { toolbarOffsetY.value.toDp() },
|
||||||
|
label = "ToolbarScrollOffset"
|
||||||
|
)
|
||||||
KiwixAppBar(
|
KiwixAppBar(
|
||||||
title = if (state.showTabSwitcher) "" else state.readerScreenTitle,
|
title = if (state.showTabSwitcher) "" else state.readerScreenTitle,
|
||||||
navigationIcon = navigationIcon,
|
navigationIcon = navigationIcon,
|
||||||
actionMenuItems = actionMenuItems,
|
actionMenuItems = actionMenuItems,
|
||||||
topAppBarScrollBehavior = scrollBehavior,
|
topAppBarScrollBehavior = scrollBehavior,
|
||||||
searchBar =
|
searchBar =
|
||||||
searchPlaceHolderIfActive(state.searchPlaceHolderItemForCustomApps)
|
searchPlaceHolderIfActive(state.searchPlaceHolderItemForCustomApps),
|
||||||
|
modifier = Modifier.offset { IntOffset(x = ZERO, y = animatedOffsetY.roundToPx()) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ReaderContentLayout(state: ReaderScreenState, modifier: Modifier = Modifier) {
|
private fun ReaderContentLayout(
|
||||||
|
state: ReaderScreenState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
bottomAppBarOffsetY: MutableState<Float>
|
||||||
|
) {
|
||||||
Box(modifier = modifier.fillMaxSize()) {
|
Box(modifier = modifier.fillMaxSize()) {
|
||||||
when {
|
when {
|
||||||
state.showTabSwitcher -> TabSwitcherView(
|
state.showTabSwitcher -> TabSwitcherView(
|
||||||
@ -210,7 +229,7 @@ private fun ReaderContentLayout(state: ReaderScreenState, modifier: Modifier = M
|
|||||||
state.fullScreenItem.first -> ShowFullScreenView(state)
|
state.fullScreenItem.first -> ShowFullScreenView(state)
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
ShowZIMFileContent(state)
|
ShowZIMFileContent(state, bottomAppBarOffsetY)
|
||||||
ShowProgressBarIfZIMFilePageIsLoading(state)
|
ShowProgressBarIfZIMFilePageIsLoading(state)
|
||||||
Column(Modifier.align(Alignment.BottomCenter)) {
|
Column(Modifier.align(Alignment.BottomCenter)) {
|
||||||
TtsControls(state)
|
TtsControls(state)
|
||||||
@ -219,8 +238,9 @@ private fun ReaderContentLayout(state: ReaderScreenState, modifier: Modifier = M
|
|||||||
state.previousPageButtonItem,
|
state.previousPageButtonItem,
|
||||||
state.onHomeButtonClick,
|
state.onHomeButtonClick,
|
||||||
state.nextPageButtonItem,
|
state.nextPageButtonItem,
|
||||||
state.onTocClick,
|
state.tocButtonItem,
|
||||||
state.shouldShowBottomAppBar
|
state.shouldShowBottomAppBar,
|
||||||
|
bottomAppBarOffsetY
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
CloseFullScreenImageButton(
|
CloseFullScreenImageButton(
|
||||||
@ -305,7 +325,15 @@ private fun BoxScope.CloseFullScreenImageButton(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ShowZIMFileContent(state: ReaderScreenState) {
|
private fun ShowZIMFileContent(
|
||||||
|
state: ReaderScreenState,
|
||||||
|
bottomAppBarOffsetY: MutableState<Float>
|
||||||
|
) {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
|
||||||
|
val bottomNavHeightDp = with(density) { state.bottomNavigationHeight.toDp() }
|
||||||
|
val bottomAppBarOffsetDp = with(density) { -bottomAppBarOffsetY.value.toDp() }
|
||||||
|
val totalBottomPadding = (bottomNavHeightDp + bottomAppBarOffsetDp).coerceAtLeast(ZERO.dp)
|
||||||
state.selectedWebView?.let { selectedWebView ->
|
state.selectedWebView?.let { selectedWebView ->
|
||||||
key(selectedWebView) {
|
key(selectedWebView) {
|
||||||
AndroidView(
|
AndroidView(
|
||||||
@ -317,7 +345,10 @@ private fun ShowZIMFileContent(state: ReaderScreenState) {
|
|||||||
addView(selectedWebView)
|
addView(selectedWebView)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(bottom = totalBottomPadding)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -425,14 +456,20 @@ private fun BottomAppBarOfReaderScreen(
|
|||||||
previousPageButtonItem: Triple<() -> Unit, () -> Unit, Boolean>,
|
previousPageButtonItem: Triple<() -> Unit, () -> Unit, Boolean>,
|
||||||
onHomeButtonClick: () -> Unit,
|
onHomeButtonClick: () -> Unit,
|
||||||
nextPageButtonItem: Triple<() -> Unit, () -> Unit, Boolean>,
|
nextPageButtonItem: Triple<() -> Unit, () -> Unit, Boolean>,
|
||||||
onTocClick: () -> Unit,
|
tocButtonItem: Pair<Boolean, () -> Unit>,
|
||||||
shouldShowBottomAppBar: Boolean
|
shouldShowBottomAppBar: Boolean,
|
||||||
|
bottomAppBarOffsetY: MutableState<Float>
|
||||||
) {
|
) {
|
||||||
if (!shouldShowBottomAppBar) return
|
if (!shouldShowBottomAppBar) return
|
||||||
|
val animatedOffsetY by animateDpAsState(
|
||||||
|
targetValue = with(LocalDensity.current) { bottomAppBarOffsetY.value.toDp() },
|
||||||
|
label = "BottomAppBarOffset"
|
||||||
|
)
|
||||||
BottomAppBar(
|
BottomAppBar(
|
||||||
containerColor = Black,
|
containerColor = Black,
|
||||||
contentColor = White,
|
contentColor = White,
|
||||||
scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
|
scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior(),
|
||||||
|
modifier = Modifier.offset { IntOffset(ZERO, animatedOffsetY.roundToPx()) }
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -472,7 +509,8 @@ private fun BottomAppBarOfReaderScreen(
|
|||||||
)
|
)
|
||||||
// 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(
|
||||||
onClick = onTocClick,
|
shouldEnable = tocButtonItem.first,
|
||||||
|
onClick = tocButtonItem.second,
|
||||||
buttonIcon = Drawable(R.drawable.ic_toc_24dp),
|
buttonIcon = Drawable(R.drawable.ic_toc_24dp),
|
||||||
contentDescription = stringResource(R.string.table_of_contents)
|
contentDescription = stringResource(R.string.table_of_contents)
|
||||||
)
|
)
|
||||||
|
@ -132,8 +132,12 @@ data class ReaderScreenState(
|
|||||||
val nextPageButtonItem: Triple<() -> Unit, () -> Unit, Boolean>,
|
val nextPageButtonItem: Triple<() -> Unit, () -> Unit, Boolean>,
|
||||||
/**
|
/**
|
||||||
* Handles the click to open right sidebar button click in reader bottom toolbar.
|
* Handles the click to open right sidebar button click in reader bottom toolbar.
|
||||||
|
*
|
||||||
|
* A [Pair] containing:
|
||||||
|
* - [Boolean]: Handles the button should enable or not(Specially for custom apps).
|
||||||
|
* - [Unit]: Handles the click of button.
|
||||||
*/
|
*/
|
||||||
val onTocClick: () -> Unit,
|
val tocButtonItem: Pair<Boolean, () -> Unit>,
|
||||||
val onCloseAllTabs: () -> Unit,
|
val onCloseAllTabs: () -> Unit,
|
||||||
/**
|
/**
|
||||||
* Stores the height of the bottom navigation bar in pixels.
|
* Stores the height of the bottom navigation bar in pixels.
|
||||||
|
@ -70,7 +70,13 @@ fun NavigationHistoryDialogScreen(
|
|||||||
) {
|
) {
|
||||||
KiwixDialogTheme {
|
KiwixDialogTheme {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { KiwixAppBar(stringResource(titleId), navigationIcon, actionMenuItems) }
|
topBar = {
|
||||||
|
KiwixAppBar(
|
||||||
|
title = stringResource(titleId),
|
||||||
|
navigationIcon = navigationIcon,
|
||||||
|
actionMenuItems = actionMenuItems
|
||||||
|
)
|
||||||
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -72,6 +72,7 @@ const val TOOLBAR_TITLE_TESTING_TAG = "toolbarTitle"
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun KiwixAppBar(
|
fun KiwixAppBar(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
title: String,
|
title: String,
|
||||||
navigationIcon: @Composable () -> Unit,
|
navigationIcon: @Composable () -> Unit,
|
||||||
actionMenuItems: List<ActionMenuItem> = emptyList(),
|
actionMenuItems: List<ActionMenuItem> = emptyList(),
|
||||||
@ -92,7 +93,8 @@ fun KiwixAppBar(
|
|||||||
// Edge-to-Edge mode is already enabled in our application,
|
// Edge-to-Edge mode is already enabled in our application,
|
||||||
// so we don't need to apply additional top insets.
|
// so we don't need to apply additional top insets.
|
||||||
// This prevents unwanted extra margin at the top.
|
// This prevents unwanted extra margin at the top.
|
||||||
windowInsets = WindowInsets.statusBars.only(WindowInsetsSides.Horizontal)
|
windowInsets = WindowInsets.statusBars.only(WindowInsetsSides.Horizontal),
|
||||||
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,4 +193,6 @@ object ComposeDimens {
|
|||||||
val BACK_TO_TOP_BUTTON_BOTTOM_MARGIN = 80.dp
|
val BACK_TO_TOP_BUTTON_BOTTOM_MARGIN = 80.dp
|
||||||
const val READER_BOTTOM_APP_BAR_DISABLE_BUTTON_ALPHA = 0.38f
|
const val READER_BOTTOM_APP_BAR_DISABLE_BUTTON_ALPHA = 0.38f
|
||||||
val SEARCH_PLACEHOLDER_TEXT_SIZE = 12.sp
|
val SEARCH_PLACEHOLDER_TEXT_SIZE = 12.sp
|
||||||
|
val COMPOSE_TOOLBAR_DEFAULT_HEIGHT = 64.dp
|
||||||
|
val COMPOSE_BOTTOM_APP_BAR_DEFAULT_HEIGHT = 80.dp
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import android.os.Build
|
|||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import androidx.appcompat.R
|
import androidx.appcompat.R
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
|
||||||
object DimenUtils {
|
object DimenUtils {
|
||||||
@JvmStatic fun Context.getToolbarHeight(): Int {
|
@JvmStatic fun Context.getToolbarHeight(): Int {
|
||||||
@ -33,6 +34,14 @@ object DimenUtils {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Context.dpToPx(dp: Dp): Int {
|
||||||
|
return TypedValue.applyDimension(
|
||||||
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
|
dp.value,
|
||||||
|
resources.displayMetrics
|
||||||
|
).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic fun Activity.getWindowHeight(): Int =
|
@JvmStatic fun Activity.getWindowHeight(): Int =
|
||||||
computedDisplayMetric.heightPixels
|
computedDisplayMetric.heightPixels
|
||||||
|
|
||||||
|
@ -55,7 +55,6 @@ open class VideoEnabledWebChromeClient :
|
|||||||
fun toggledFullscreen(fullscreen: Boolean)
|
fun toggledFullscreen(fullscreen: Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var activityNonVideoView: View? = null
|
|
||||||
private var activityVideoView: ViewGroup? = null
|
private var activityVideoView: ViewGroup? = null
|
||||||
private var loadingView: View? = null
|
private var loadingView: View? = null
|
||||||
private var webView: VideoEnabledWebView? = null
|
private var webView: VideoEnabledWebView? = null
|
||||||
@ -84,14 +83,11 @@ open class VideoEnabledWebChromeClient :
|
|||||||
/**
|
/**
|
||||||
* Builds a video enabled WebChromeClient.
|
* Builds a video enabled WebChromeClient.
|
||||||
*
|
*
|
||||||
* @param activityNonVideoView A View in the activity's layout that contains every other view that
|
|
||||||
* should be hidden when the video goes full-screen.
|
|
||||||
* @param activityVideoView A ViewGroup in the activity's layout that will display the video.
|
* @param activityVideoView A ViewGroup in the activity's layout that will display the video.
|
||||||
* Typically you would like this to fill the whole layout.
|
* Typically you would like this to fill the whole layout.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
constructor(activityNonVideoView: View?, activityVideoView: ViewGroup?) {
|
constructor(activityVideoView: ViewGroup?) {
|
||||||
this.activityNonVideoView = activityNonVideoView
|
|
||||||
this.activityVideoView = activityVideoView
|
this.activityVideoView = activityVideoView
|
||||||
loadingView = null
|
loadingView = null
|
||||||
webView = null
|
webView = null
|
||||||
@ -101,8 +97,6 @@ open class VideoEnabledWebChromeClient :
|
|||||||
/**
|
/**
|
||||||
* Builds a video enabled WebChromeClient.
|
* Builds a video enabled WebChromeClient.
|
||||||
*
|
*
|
||||||
* @param activityNonVideoView A View in the activity's layout that contains every other view that
|
|
||||||
* should be hidden when the video goes full-screen.
|
|
||||||
* @param activityVideoView A ViewGroup in the activity's layout that will display the video.
|
* @param activityVideoView A ViewGroup in the activity's layout that will display the video.
|
||||||
* Typically you would like this to fill the whole layout.
|
* Typically you would like this to fill the whole layout.
|
||||||
* @param loadingView A View to be shown while the video is loading (typically only used in API
|
* @param loadingView A View to be shown while the video is loading (typically only used in API
|
||||||
@ -110,11 +104,9 @@ open class VideoEnabledWebChromeClient :
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
constructor(
|
constructor(
|
||||||
activityNonVideoView: View?,
|
|
||||||
activityVideoView: ViewGroup?,
|
activityVideoView: ViewGroup?,
|
||||||
loadingView: View?
|
loadingView: View?
|
||||||
) {
|
) {
|
||||||
this.activityNonVideoView = activityNonVideoView
|
|
||||||
this.activityVideoView = activityVideoView
|
this.activityVideoView = activityVideoView
|
||||||
this.loadingView = loadingView
|
this.loadingView = loadingView
|
||||||
webView = null
|
webView = null
|
||||||
@ -124,8 +116,6 @@ open class VideoEnabledWebChromeClient :
|
|||||||
/**
|
/**
|
||||||
* Builds a video enabled WebChromeClient.
|
* Builds a video enabled WebChromeClient.
|
||||||
*
|
*
|
||||||
* @param activityNonVideoView A View in the activity's layout that contains every other view that
|
|
||||||
* should be hidden when the video goes full-screen.
|
|
||||||
* @param activityVideoView A ViewGroup in the activity's layout that will display the video.
|
* @param activityVideoView A ViewGroup in the activity's layout that will display the video.
|
||||||
* Typically you would like this to fill the whole layout.
|
* Typically you would like this to fill the whole layout.
|
||||||
* @param loadingView A View to be shown while the video is loading (typically only used in API
|
* @param loadingView A View to be shown while the video is loading (typically only used in API
|
||||||
@ -137,12 +127,10 @@ open class VideoEnabledWebChromeClient :
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
constructor(
|
constructor(
|
||||||
activityNonVideoView: View?,
|
|
||||||
activityVideoView: ViewGroup?,
|
activityVideoView: ViewGroup?,
|
||||||
loadingView: View?,
|
loadingView: View?,
|
||||||
webView: VideoEnabledWebView?
|
webView: VideoEnabledWebView?
|
||||||
) {
|
) {
|
||||||
this.activityNonVideoView = activityNonVideoView
|
|
||||||
this.activityVideoView = activityVideoView
|
this.activityVideoView = activityVideoView
|
||||||
this.loadingView = loadingView
|
this.loadingView = loadingView
|
||||||
this.webView = webView
|
this.webView = webView
|
||||||
@ -177,9 +165,6 @@ open class VideoEnabledWebChromeClient :
|
|||||||
isVideoFullscreen = true
|
isVideoFullscreen = true
|
||||||
videoViewContainer = view
|
videoViewContainer = view
|
||||||
videoViewCallback = callback
|
videoViewCallback = callback
|
||||||
|
|
||||||
// Hide the non-video view, add the video view, and show it
|
|
||||||
activityNonVideoView?.visibility = View.INVISIBLE
|
|
||||||
activityVideoView?.addView(
|
activityVideoView?.addView(
|
||||||
videoViewContainer,
|
videoViewContainer,
|
||||||
ViewGroup.LayoutParams(
|
ViewGroup.LayoutParams(
|
||||||
@ -248,7 +233,6 @@ open class VideoEnabledWebChromeClient :
|
|||||||
// Hide the video view, remove it, and show the non-video view
|
// Hide the video view, remove it, and show the non-video view
|
||||||
activityVideoView?.visibility = View.INVISIBLE
|
activityVideoView?.visibility = View.INVISIBLE
|
||||||
activityVideoView?.removeView(videoViewContainer)
|
activityVideoView?.removeView(videoViewContainer)
|
||||||
activityNonVideoView?.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
// Call back (only in API level <19, because in API level 19+ with chromium webview it crashes)
|
// Call back (only in API level <19, because in API level 19+ with chromium webview it crashes)
|
||||||
videoViewCallback?.let {
|
videoViewCallback?.let {
|
||||||
|
@ -23,7 +23,6 @@ import android.os.Bundle
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Menu
|
import androidx.compose.material.icons.filled.Menu
|
||||||
@ -78,12 +77,6 @@ class CustomReaderFragment : CoreReaderFragment() {
|
|||||||
|
|
||||||
if (isAdded) {
|
if (isAdded) {
|
||||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
|
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
|
||||||
if (BuildConfig.DISABLE_SIDEBAR) {
|
|
||||||
val toolbarToc =
|
|
||||||
activity?.findViewById<ImageView>(org.kiwix.kiwixmobile.core.R.id.bottom_toolbar_toc)
|
|
||||||
toolbarToc?.isEnabled = false
|
|
||||||
// TODO refactor this with compose UI.
|
|
||||||
}
|
|
||||||
with(activity as AppCompatActivity) {
|
with(activity as AppCompatActivity) {
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
setUpDrawerToggle()
|
setUpDrawerToggle()
|
||||||
@ -98,6 +91,20 @@ class CustomReaderFragment : CoreReaderFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the TOC (Table of Contents) button's enabled state and click action.
|
||||||
|
*
|
||||||
|
* In this custom app variant, the TOC button is disabled if [BuildConfig.DISABLE_SIDEBAR] is `true`.
|
||||||
|
* This is typically used when the sidebar functionality is intentionally turned off.
|
||||||
|
*
|
||||||
|
* @return A [Pair] containing:
|
||||||
|
* - [Boolean]: `true` if the TOC button should be enabled (i.e., sidebar is allowed),
|
||||||
|
* `false` if it should be disabled (i.e., [DISABLE_SIDEBAR] is `true`).
|
||||||
|
* - [() -> Unit]: Action to execute when the button is clicked. This will only be invoked if enabled.
|
||||||
|
*/
|
||||||
|
override fun getTocButtonStateAndAction(): Pair<Boolean, () -> Unit> =
|
||||||
|
!BuildConfig.DISABLE_SIDEBAR to { openToc() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the tint color for the navigation icon.
|
* Returns the tint color for the navigation icon.
|
||||||
*
|
*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user