mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-07 22:31:17 -04:00
Fixed: WebView was not occupying full height when the BottomAppBar was hidden.
* Moved the scrolling logic of the WebView into the Compose UI, as custom scroll handling in the WebView was causing issues. * Removed ToolbarScrollingKiwixWebView since it's no longer needed—scrolling is now managed entirely within Compose. * Added animations for opening and closing the tab switcher view.
This commit is contained in:
parent
3ebb37d5cc
commit
db22e245b7
@ -21,7 +21,6 @@ package org.kiwix.kiwixmobile.nav.destination.reader
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@ -48,8 +47,6 @@ import org.kiwix.kiwixmobile.core.extensions.snack
|
|||||||
import org.kiwix.kiwixmobile.core.extensions.toast
|
import org.kiwix.kiwixmobile.core.extensions.toast
|
||||||
import org.kiwix.kiwixmobile.core.extensions.update
|
import org.kiwix.kiwixmobile.core.extensions.update
|
||||||
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
|
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
|
||||||
import org.kiwix.kiwixmobile.core.main.CoreWebViewClient
|
|
||||||
import org.kiwix.kiwixmobile.core.main.ToolbarScrollingKiwixWebView
|
|
||||||
import org.kiwix.kiwixmobile.core.main.reader.CoreReaderFragment
|
import org.kiwix.kiwixmobile.core.main.reader.CoreReaderFragment
|
||||||
import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin
|
import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin
|
||||||
import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin.FromExternalLaunch
|
import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin.FromExternalLaunch
|
||||||
@ -57,6 +54,7 @@ import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin.FromSearchScreen
|
|||||||
import org.kiwix.kiwixmobile.core.page.history.adapter.WebViewHistoryItem
|
import org.kiwix.kiwixmobile.core.page.history.adapter.WebViewHistoryItem
|
||||||
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
|
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
|
||||||
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource.Companion.fromDatabaseValue
|
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource.Companion.fromDatabaseValue
|
||||||
|
import org.kiwix.kiwixmobile.core.utils.DimenUtils.getToolbarHeight
|
||||||
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
||||||
import org.kiwix.kiwixmobile.core.utils.TAG_CURRENT_FILE
|
import org.kiwix.kiwixmobile.core.utils.TAG_CURRENT_FILE
|
||||||
import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX
|
import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX
|
||||||
@ -299,19 +297,14 @@ class KiwixReaderFragment : CoreReaderFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IllegalArgumentException::class)
|
override fun updateNavigationBarHeight(toolbarOffset: Float) {
|
||||||
override fun createWebView(attrs: AttributeSet?): ToolbarScrollingKiwixWebView? {
|
// if no activity exist simply return.
|
||||||
return ToolbarScrollingKiwixWebView(
|
if (activity == null) return
|
||||||
requireContext(),
|
activity?.findViewById<BottomNavigationView>(R.id.bottom_nav_view)?.let { view ->
|
||||||
this,
|
val toolbarHeightPx = activity?.getToolbarHeight() ?: 0f
|
||||||
attrs ?: throw IllegalArgumentException("AttributeSet must not be null"),
|
val offsetFactor = view.height / toolbarHeightPx.toFloat()
|
||||||
requireNotNull(readerScreenState.value.fullScreenItem.second),
|
view.translationY = -1 * toolbarOffset * offsetFactor
|
||||||
CoreWebViewClient(this, requireNotNull(zimReaderContainer)),
|
}
|
||||||
onToolbarOffsetChanged = { offsetY -> toolbarOffsetY.value = offsetY },
|
|
||||||
onBottomAppBarOffsetChanged = { bottomOffsetY -> bottomAppBarOffsetY.value = bottomOffsetY },
|
|
||||||
sharedPreferenceUtil = requireNotNull(sharedPreferenceUtil),
|
|
||||||
parentNavigationBar = requireActivity().findViewById(R.id.bottom_nav_view)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFullscreenVideoToggled(isFullScreen: Boolean) {
|
override fun onFullscreenVideoToggled(isFullScreen: Boolean) {
|
||||||
|
@ -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, 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>LongParameterList:ToolbarScrollingKiwixWebView.kt$ToolbarScrollingKiwixWebView$( context: Context, callback: WebViewCallback, attrs: AttributeSet, videoView: ViewGroup?, webViewClient: CoreWebViewClient, sharedPreferenceUtil: SharedPreferenceUtil )</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,136 +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.main
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
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 kotlin.math.max
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
|
||||||
@Suppress("UnusedPrivateProperty")
|
|
||||||
class ToolbarScrollingKiwixWebView @JvmOverloads constructor(
|
|
||||||
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
|
|
||||||
) : KiwixWebView(
|
|
||||||
context,
|
|
||||||
callback,
|
|
||||||
attrs,
|
|
||||||
videoView,
|
|
||||||
webViewClient,
|
|
||||||
sharedPreferenceUtil
|
|
||||||
) {
|
|
||||||
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 currentOffset = 0f
|
|
||||||
|
|
||||||
init {
|
|
||||||
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.
|
|
||||||
* See https://github.com/kiwix/kiwix-android/issues/2304 for issue description.
|
|
||||||
*/
|
|
||||||
private fun fixInitalScrollingIssue() {
|
|
||||||
moveToolbar(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
||||||
if (sharedPreferenceUtil.prefFullScreen) return super.onTouchEvent(event)
|
|
||||||
|
|
||||||
when (event.actionMasked) {
|
|
||||||
MotionEvent.ACTION_DOWN -> {
|
|
||||||
startY = event.rawY
|
|
||||||
}
|
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
|
||||||
if (event.pointerCount == 1) {
|
|
||||||
val diffY = (event.rawY - startY).toInt()
|
|
||||||
startY = event.rawY
|
|
||||||
if (moveToolbar(-diffY)) {
|
|
||||||
event.offsetLocation(0f, -diffY.toFloat())
|
|
||||||
return super.onTouchEvent(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onTouchEvent(event)
|
|
||||||
}
|
|
||||||
}
|
|
@ -148,7 +148,6 @@ import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter
|
|||||||
import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter.DocumentSection
|
import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter.DocumentSection
|
||||||
import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter.TableClickListener
|
import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter.TableClickListener
|
||||||
import org.kiwix.kiwixmobile.core.main.TabsAdapter
|
import org.kiwix.kiwixmobile.core.main.TabsAdapter
|
||||||
import org.kiwix.kiwixmobile.core.main.ToolbarScrollingKiwixWebView
|
|
||||||
import org.kiwix.kiwixmobile.core.main.UNINITIALISER_ADDRESS
|
import org.kiwix.kiwixmobile.core.main.UNINITIALISER_ADDRESS
|
||||||
import org.kiwix.kiwixmobile.core.main.WebViewCallback
|
import org.kiwix.kiwixmobile.core.main.WebViewCallback
|
||||||
import org.kiwix.kiwixmobile.core.main.WebViewProvider
|
import org.kiwix.kiwixmobile.core.main.WebViewProvider
|
||||||
@ -226,9 +225,6 @@ abstract class CoreReaderFragment :
|
|||||||
|
|
||||||
var drawerLayout: DrawerLayout? = null
|
var drawerLayout: DrawerLayout? = null
|
||||||
protected var tableDrawerRightContainer: NavigationView? = null
|
protected var tableDrawerRightContainer: NavigationView? = null
|
||||||
|
|
||||||
var contentFrame: FrameLayout? = null
|
|
||||||
|
|
||||||
var tabSwitcherRoot: View? = null
|
var tabSwitcherRoot: View? = null
|
||||||
|
|
||||||
var activityMainRoot: View? = null
|
var activityMainRoot: View? = null
|
||||||
@ -318,8 +314,6 @@ 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
|
||||||
@ -502,6 +496,9 @@ abstract class CoreReaderFragment :
|
|||||||
ReaderScreen(
|
ReaderScreen(
|
||||||
state = readerScreenState.value,
|
state = readerScreenState.value,
|
||||||
actionMenuItems = readerMenuState?.menuItems.orEmpty(),
|
actionMenuItems = readerMenuState?.menuItems.orEmpty(),
|
||||||
|
onBottomScrollOffsetChanged = { offset ->
|
||||||
|
updateNavigationBarHeight(offset)
|
||||||
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
NavigationIcon(
|
NavigationIcon(
|
||||||
iconItem = navigationIcon(),
|
iconItem = navigationIcon(),
|
||||||
@ -509,9 +506,7 @@ abstract class CoreReaderFragment :
|
|||||||
onClick = { navigationIconClick() },
|
onClick = { navigationIconClick() },
|
||||||
iconTint = navigationIconTint()
|
iconTint = navigationIconTint()
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
toolbarOffsetY = toolbarOffsetY,
|
|
||||||
bottomAppBarOffsetY = bottomAppBarOffsetY
|
|
||||||
)
|
)
|
||||||
DialogHost(alertDialogShower as AlertDialogShower)
|
DialogHost(alertDialogShower as AlertDialogShower)
|
||||||
}
|
}
|
||||||
@ -615,6 +610,17 @@ abstract class CoreReaderFragment :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is for hiding the KiwixMainActivity bottomNavigationView.
|
||||||
|
* In custom apps we do not have the bottomnavigationView so that's why this method is empty here.
|
||||||
|
*
|
||||||
|
* See the implementation in KiwixReaderFragment.
|
||||||
|
* TODO refactore this when migrating the KiwixMainActivity in compose.
|
||||||
|
*/
|
||||||
|
open fun updateNavigationBarHeight(toolbarOffset: Float) {
|
||||||
|
// Do nothing since in custom apps we do not have the bottomNavigationView.
|
||||||
|
}
|
||||||
|
|
||||||
private fun getVideoView() = context?.let {
|
private fun getVideoView() = context?.let {
|
||||||
FrameLayout(it).apply {
|
FrameLayout(it).apply {
|
||||||
layoutParams = ViewGroup.LayoutParams(
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
@ -698,7 +704,6 @@ abstract class CoreReaderFragment :
|
|||||||
fragmentReaderBinding?.let { readerBinding ->
|
fragmentReaderBinding?.let { readerBinding ->
|
||||||
with(readerBinding.root) {
|
with(readerBinding.root) {
|
||||||
activityMainRoot = findViewById(R.id.activity_main_root)
|
activityMainRoot = findViewById(R.id.activity_main_root)
|
||||||
contentFrame = findViewById(R.id.activity_main_content_frame)
|
|
||||||
toolbar = findViewById(R.id.toolbar)
|
toolbar = findViewById(R.id.toolbar)
|
||||||
tabSwitcherRoot = findViewById(R.id.activity_main_tab_switcher)
|
tabSwitcherRoot = findViewById(R.id.activity_main_tab_switcher)
|
||||||
tabRecyclerView = findViewById(R.id.tab_switcher_recycler_view)
|
tabRecyclerView = findViewById(R.id.tab_switcher_recycler_view)
|
||||||
@ -921,29 +926,6 @@ abstract class CoreReaderFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a top margin to the web views.
|
|
||||||
*
|
|
||||||
* @param topMargin The top margin to be applied to the web views.
|
|
||||||
* Use 0 to remove the margin.
|
|
||||||
*/
|
|
||||||
protected open fun setTopMarginToWebViews(topMargin: Int) {
|
|
||||||
for (webView in webViewList) {
|
|
||||||
if (webView.parent == null) {
|
|
||||||
// Ensure that the web view has a parent before modifying its layout parameters
|
|
||||||
// This check is necessary to prevent adding the margin when the web view is not attached to a layout
|
|
||||||
// Adding the margin without a parent can cause unintended layout issues or empty
|
|
||||||
// space on top of the webView in the tabs adapter.
|
|
||||||
val frameLayout = FrameLayout(requireActivity())
|
|
||||||
// Add the web view to the frame layout
|
|
||||||
frameLayout.addView(webView)
|
|
||||||
}
|
|
||||||
val layoutParams = webView.layoutParams as FrameLayout.LayoutParams?
|
|
||||||
layoutParams?.topMargin = topMargin
|
|
||||||
webView.requestLayout()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun startAnimation(
|
protected fun startAnimation(
|
||||||
view: View?,
|
view: View?,
|
||||||
@AnimRes anim: Int
|
@AnimRes anim: Int
|
||||||
@ -969,9 +951,6 @@ abstract class CoreReaderFragment :
|
|||||||
showSearchPlaceHolderInToolbar(false)
|
showSearchPlaceHolderInToolbar(false)
|
||||||
readerMenuState?.showWebViewOptions(urlIsValid())
|
readerMenuState?.showWebViewOptions(urlIsValid())
|
||||||
selectTab(currentWebViewIndex)
|
selectTab(currentWebViewIndex)
|
||||||
// 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.
|
|
||||||
// setTopMarginToWebViews(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun setUpDrawerToggle() {
|
open fun setUpDrawerToggle() {
|
||||||
@ -1360,7 +1339,6 @@ abstract class CoreReaderFragment :
|
|||||||
activityMainRoot = null
|
activityMainRoot = null
|
||||||
tabRecyclerView = null
|
tabRecyclerView = null
|
||||||
tabSwitcherRoot = null
|
tabSwitcherRoot = null
|
||||||
contentFrame = null
|
|
||||||
compatCallback?.finish()
|
compatCallback?.finish()
|
||||||
compatCallback = null
|
compatCallback = null
|
||||||
toolbar?.setOnTouchListener(null)
|
toolbar?.setOnTouchListener(null)
|
||||||
@ -1422,15 +1400,13 @@ abstract class CoreReaderFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
protected open fun createWebView(attrs: AttributeSet?): ToolbarScrollingKiwixWebView? {
|
protected open fun createWebView(attrs: AttributeSet?): KiwixWebView? {
|
||||||
return ToolbarScrollingKiwixWebView(
|
return KiwixWebView(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
this,
|
this,
|
||||||
attrs ?: throw IllegalArgumentException("AttributeSet must not be null"),
|
attrs ?: throw IllegalArgumentException("AttributeSet must not be null"),
|
||||||
requireNotNull(readerScreenState.value.fullScreenItem.second),
|
requireNotNull(readerScreenState.value.fullScreenItem.second),
|
||||||
CoreWebViewClient(this, requireNotNull(zimReaderContainer)),
|
CoreWebViewClient(this, requireNotNull(zimReaderContainer)),
|
||||||
onToolbarOffsetChanged = { offsetY -> toolbarOffsetY.value = offsetY },
|
|
||||||
onBottomAppBarOffsetChanged = { bottomOffsetY -> bottomAppBarOffsetY.value = bottomOffsetY },
|
|
||||||
requireNotNull(sharedPreferenceUtil)
|
requireNotNull(sharedPreferenceUtil)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1505,7 +1481,6 @@ abstract class CoreReaderFragment :
|
|||||||
|
|
||||||
private fun reopenBook() {
|
private fun reopenBook() {
|
||||||
hideNoBookOpenViews()
|
hideNoBookOpenViews()
|
||||||
contentFrame?.visibility = VISIBLE
|
|
||||||
readerMenuState?.showBookSpecificMenuItems()
|
readerMenuState?.showBookSpecificMenuItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1517,7 +1492,6 @@ abstract class CoreReaderFragment :
|
|||||||
readerScreenTitle = context?.getString(R.string.reader).orEmpty()
|
readerScreenTitle = context?.getString(R.string.reader).orEmpty()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
contentFrame?.visibility = GONE
|
|
||||||
hideProgressBar()
|
hideProgressBar()
|
||||||
readerMenuState?.hideBookSpecificMenuItems()
|
readerMenuState?.hideBookSpecificMenuItems()
|
||||||
if (shouldCloseZimBook) {
|
if (shouldCloseZimBook) {
|
||||||
@ -1796,16 +1770,16 @@ abstract class CoreReaderFragment :
|
|||||||
setUpDrawerToggle()
|
setUpDrawerToggle()
|
||||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
|
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
|
||||||
sharedPreferenceUtil?.putPrefFullScreen(false)
|
sharedPreferenceUtil?.putPrefFullScreen(false)
|
||||||
|
updateBottomToolbarVisibility()
|
||||||
|
val window = requireActivity().window
|
||||||
|
window.decorView.closeFullScreenMode(window)
|
||||||
|
getCurrentWebView()?.requestLayout()
|
||||||
readerScreenState.update {
|
readerScreenState.update {
|
||||||
copy(
|
copy(
|
||||||
shouldShowBottomAppBar = true,
|
shouldShowBottomAppBar = true,
|
||||||
shouldShowFullScreenMode = false
|
shouldShowFullScreenMode = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
updateBottomToolbarVisibility()
|
|
||||||
val window = requireActivity().window
|
|
||||||
window.decorView.closeFullScreenMode(window)
|
|
||||||
getCurrentWebView()?.requestLayout()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openExternalUrl(intent: Intent) {
|
override fun openExternalUrl(intent: Intent) {
|
||||||
|
@ -22,9 +22,14 @@ 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.AnimatedVisibility
|
||||||
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.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
@ -37,11 +42,9 @@ 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
|
||||||
@ -56,6 +59,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.BottomAppBar
|
import androidx.compose.material3.BottomAppBar
|
||||||
import androidx.compose.material3.BottomAppBarDefaults
|
import androidx.compose.material3.BottomAppBarDefaults
|
||||||
|
import androidx.compose.material3.BottomAppBarScrollBehavior
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
@ -97,9 +101,9 @@ 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 androidx.compose.ui.zIndex
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import org.kiwix.kiwixmobile.core.R
|
import org.kiwix.kiwixmobile.core.R
|
||||||
import org.kiwix.kiwixmobile.core.downloader.downloadManager.HUNDERED
|
import org.kiwix.kiwixmobile.core.downloader.downloadManager.HUNDERED
|
||||||
@ -149,12 +153,15 @@ const val CONTENT_LOADING_PROGRESSBAR_TESTING_TAG = "contentLoadingProgressBarTe
|
|||||||
fun ReaderScreen(
|
fun ReaderScreen(
|
||||||
state: ReaderScreenState,
|
state: ReaderScreenState,
|
||||||
actionMenuItems: List<ActionMenuItem>,
|
actionMenuItems: List<ActionMenuItem>,
|
||||||
toolbarOffsetY: MutableState<Float>,
|
onBottomScrollOffsetChanged: (Float) -> Unit,
|
||||||
bottomAppBarOffsetY: MutableState<Float>,
|
|
||||||
navigationIcon: @Composable () -> Unit
|
navigationIcon: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val bottomNavHeightInDp = with(LocalDensity.current) { state.bottomNavigationHeight.toDp() }
|
val bottomNavHeightInDp = with(LocalDensity.current) { state.bottomNavigationHeight.toDp() }
|
||||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||||
|
val bottomAppBarScrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
|
||||||
|
LaunchedEffect(bottomAppBarScrollBehavior.state.heightOffset) {
|
||||||
|
onBottomScrollOffsetChanged(scrollBehavior.state.heightOffset)
|
||||||
|
}
|
||||||
KiwixDialogTheme {
|
KiwixDialogTheme {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
snackbarHost = { KiwixSnackbarHost(snackbarHostState = state.snackBarHostState) },
|
snackbarHost = { KiwixSnackbarHost(snackbarHostState = state.snackBarHostState) },
|
||||||
@ -162,7 +169,6 @@ fun ReaderScreen(
|
|||||||
ReaderTopBar(
|
ReaderTopBar(
|
||||||
state,
|
state,
|
||||||
actionMenuItems,
|
actionMenuItems,
|
||||||
toolbarOffsetY,
|
|
||||||
scrollBehavior,
|
scrollBehavior,
|
||||||
navigationIcon
|
navigationIcon
|
||||||
)
|
)
|
||||||
@ -171,12 +177,13 @@ fun ReaderScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.systemBarsPadding()
|
.systemBarsPadding()
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
|
.nestedScroll(bottomAppBarScrollBehavior.nestedScrollConnection)
|
||||||
.padding(bottom = bottomNavHeightInDp)
|
.padding(bottom = bottomNavHeightInDp)
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
ReaderContentLayout(
|
ReaderContentLayout(
|
||||||
state,
|
state,
|
||||||
Modifier.padding(paddingValues),
|
Modifier.padding(paddingValues),
|
||||||
bottomAppBarOffsetY
|
bottomAppBarScrollBehavior
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -188,69 +195,87 @@ 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()) }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun ReaderContentLayout(
|
private fun ReaderContentLayout(
|
||||||
state: ReaderScreenState,
|
state: ReaderScreenState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
bottomAppBarOffsetY: MutableState<Float>
|
bottomAppBarScrollBehavior: BottomAppBarScrollBehavior
|
||||||
) {
|
) {
|
||||||
Box(modifier = modifier.fillMaxSize()) {
|
Box(modifier = modifier.fillMaxSize()) {
|
||||||
when {
|
TabSwitcherAnimated(state)
|
||||||
state.showTabSwitcher -> TabSwitcherView(
|
if (!state.showTabSwitcher) {
|
||||||
state.kiwixWebViewList,
|
when {
|
||||||
state.currentWebViewPosition,
|
state.isNoBookOpenInReader -> NoBookOpenView(state.onOpenLibraryButtonClicked)
|
||||||
state.onTabClickListener,
|
state.fullScreenItem.first -> ShowFullScreenView(state)
|
||||||
state.onCloseAllTabs,
|
|
||||||
state.darkModeViewPainter
|
|
||||||
)
|
|
||||||
|
|
||||||
state.isNoBookOpenInReader -> NoBookOpenView(state.onOpenLibraryButtonClicked)
|
else -> {
|
||||||
state.fullScreenItem.first -> ShowFullScreenView(state)
|
ShowZIMFileContent(state)
|
||||||
|
ShowProgressBarIfZIMFilePageIsLoading(state)
|
||||||
else -> {
|
Column(Modifier.align(Alignment.BottomCenter)) {
|
||||||
ShowZIMFileContent(state, bottomAppBarOffsetY)
|
TtsControls(state)
|
||||||
ShowProgressBarIfZIMFilePageIsLoading(state)
|
BottomAppBarOfReaderScreen(
|
||||||
Column(Modifier.align(Alignment.BottomCenter)) {
|
state.bookmarkButtonItem,
|
||||||
TtsControls(state)
|
state.previousPageButtonItem,
|
||||||
BottomAppBarOfReaderScreen(
|
state.onHomeButtonClick,
|
||||||
state.bookmarkButtonItem,
|
state.nextPageButtonItem,
|
||||||
state.previousPageButtonItem,
|
state.tocButtonItem,
|
||||||
state.onHomeButtonClick,
|
state.shouldShowBottomAppBar,
|
||||||
state.nextPageButtonItem,
|
bottomAppBarScrollBehavior
|
||||||
state.tocButtonItem,
|
)
|
||||||
state.shouldShowBottomAppBar,
|
}
|
||||||
bottomAppBarOffsetY
|
CloseFullScreenImageButton(
|
||||||
|
state.shouldShowFullScreenMode,
|
||||||
|
state.onExitFullscreenClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
CloseFullScreenImageButton(
|
|
||||||
state.shouldShowFullScreenMode,
|
|
||||||
state.onExitFullscreenClick
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
ShowDonationLayout(state)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ShowDonationLayout(state)
|
@Composable
|
||||||
|
private fun TabSwitcherAnimated(state: ReaderScreenState) {
|
||||||
|
val transitionSpec = remember {
|
||||||
|
slideInVertically(
|
||||||
|
initialOffsetY = { -it },
|
||||||
|
animationSpec = tween(durationMillis = 300)
|
||||||
|
) + fadeIn() togetherWith
|
||||||
|
slideOutVertically(
|
||||||
|
targetOffsetY = { -it },
|
||||||
|
animationSpec = tween(durationMillis = 300)
|
||||||
|
) + fadeOut()
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = state.showTabSwitcher,
|
||||||
|
enter = transitionSpec.targetContentEnter,
|
||||||
|
exit = transitionSpec.initialContentExit,
|
||||||
|
modifier = Modifier.zIndex(1f),
|
||||||
|
) {
|
||||||
|
TabSwitcherView(
|
||||||
|
state.kiwixWebViewList,
|
||||||
|
state.currentWebViewPosition,
|
||||||
|
state.onTabClickListener,
|
||||||
|
state.onCloseAllTabs,
|
||||||
|
state.darkModeViewPainter
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,15 +350,7 @@ private fun BoxScope.CloseFullScreenImageButton(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ShowZIMFileContent(
|
private fun ShowZIMFileContent(state: ReaderScreenState) {
|
||||||
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(
|
||||||
@ -346,10 +363,10 @@ private fun ShowZIMFileContent(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxSize()
|
||||||
.fillMaxHeight()
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(bottom = totalBottomPadding)
|
|
||||||
)
|
)
|
||||||
|
// TODO handle the unnecessary vertical scroll when webView page chnaged.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -458,18 +475,13 @@ private fun BottomAppBarOfReaderScreen(
|
|||||||
nextPageButtonItem: Triple<() -> Unit, () -> Unit, Boolean>,
|
nextPageButtonItem: Triple<() -> Unit, () -> Unit, Boolean>,
|
||||||
tocButtonItem: Pair<Boolean, () -> Unit>,
|
tocButtonItem: Pair<Boolean, () -> Unit>,
|
||||||
shouldShowBottomAppBar: Boolean,
|
shouldShowBottomAppBar: Boolean,
|
||||||
bottomAppBarOffsetY: MutableState<Float>
|
bottomAppBarScrollBehavior: BottomAppBarScrollBehavior
|
||||||
) {
|
) {
|
||||||
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 = bottomAppBarScrollBehavior,
|
||||||
modifier = Modifier.offset { IntOffset(ZERO, animatedOffsetY.roundToPx()) }
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
Loading…
x
Reference in New Issue
Block a user