Fixed: Tabs were being automatically restored after closing them, navigating away from the reader screen, and then returning.

* Removed unused code from the reader screen.
* Refactored fullscreen mode functionality to align with Compose UI.
* Improved the `CloseAllTabs` button styling to match the app theme.
* Refactored the "Back to Top" button functionality using Compose, and enhanced its UI.
* Fixed: `Long-press` on bottom app bar buttons was not working due to `IconButton` consuming the touch event.
* Removed the unused `AnimationUtils` file.
* Fixed: Navigation history (forward and backward) was not being displayed.
This commit is contained in:
MohitMaliFtechiz 2025-06-19 23:58:38 +05:30
parent b694ae3170
commit 234e9169f6
6 changed files with 115 additions and 327 deletions

View File

@ -29,15 +29,12 @@ import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.view.isVisible
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.R import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.cachedComponent import org.kiwix.kiwixmobile.cachedComponent
import org.kiwix.kiwixmobile.core.R.anim
import org.kiwix.kiwixmobile.core.R.drawable
import org.kiwix.kiwixmobile.core.R.string import org.kiwix.kiwixmobile.core.R.string
import org.kiwix.kiwixmobile.core.base.BaseActivity import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions.Super import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions.Super
@ -47,7 +44,6 @@ import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.setupDrawerToggl
import org.kiwix.kiwixmobile.core.extensions.coreMainActivity import org.kiwix.kiwixmobile.core.extensions.coreMainActivity
import org.kiwix.kiwixmobile.core.extensions.isFileExist import org.kiwix.kiwixmobile.core.extensions.isFileExist
import org.kiwix.kiwixmobile.core.extensions.setBottomMarginToFragmentContainerView import org.kiwix.kiwixmobile.core.extensions.setBottomMarginToFragmentContainerView
import org.kiwix.kiwixmobile.core.extensions.setImageDrawableCompat
import org.kiwix.kiwixmobile.core.extensions.snack 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
@ -184,32 +180,6 @@ class KiwixReaderFragment : CoreReaderFragment() {
* @see closeAllTabs * @see closeAllTabs
*/ */
override fun hideTabSwitcher(shouldCloseZimBook: Boolean) { override fun hideTabSwitcher(shouldCloseZimBook: Boolean) {
actionBar?.let { actionBar ->
actionBar.setDisplayShowTitleEnabled(true)
toolbar?.let { activity?.setupDrawerToggle(it, true) }
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
closeAllTabsButton?.setImageDrawableCompat(drawable.ic_close_black_24dp)
if (tabSwitcherRoot?.isVisible == true) {
tabSwitcherRoot?.visibility = GONE
startAnimation(tabSwitcherRoot, anim.slide_up)
progressBar?.visibility = View.GONE
progressBar?.progress = 0
contentFrame?.visibility = View.VISIBLE
}
readerMenuState?.showWebViewOptions(true)
if (webViewList.isEmpty()) {
exitBook(shouldCloseZimBook)
} else {
// 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 main app.
setTopMarginToWebViews(0)
selectTab(currentWebViewIndex)
}
}
actionBar?.setDisplayShowTitleEnabled(true)
toolbar?.let { activity?.setupDrawerToggle(it, true) } toolbar?.let { activity?.setupDrawerToggle(it, true) }
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
if (webViewList.isEmpty()) { if (webViewList.isEmpty()) {

View File

@ -52,10 +52,7 @@ import android.view.ViewGroup
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import android.webkit.WebBackForwardList import android.webkit.WebBackForwardList
import android.webkit.WebView import android.webkit.WebView
import android.widget.Button
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
@ -79,16 +76,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.Group
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.ContentLoadingProgressBar
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -96,10 +89,8 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -134,8 +125,6 @@ import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.observeNavigatio
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.requestNotificationPermission import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.requestNotificationPermission
import org.kiwix.kiwixmobile.core.extensions.ViewGroupExtensions.findFirstTextView import org.kiwix.kiwixmobile.core.extensions.ViewGroupExtensions.findFirstTextView
import org.kiwix.kiwixmobile.core.extensions.closeFullScreenMode import org.kiwix.kiwixmobile.core.extensions.closeFullScreenMode
import org.kiwix.kiwixmobile.core.extensions.getToolbarNavigationIcon
import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription
import org.kiwix.kiwixmobile.core.extensions.showFullScreenMode import org.kiwix.kiwixmobile.core.extensions.showFullScreenMode
import org.kiwix.kiwixmobile.core.extensions.snack import org.kiwix.kiwixmobile.core.extensions.snack
import org.kiwix.kiwixmobile.core.extensions.toast import org.kiwix.kiwixmobile.core.extensions.toast
@ -185,7 +174,6 @@ 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.components.rememberBottomNavigationVisibility
import org.kiwix.kiwixmobile.core.ui.models.IconItem import org.kiwix.kiwixmobile.core.ui.models.IconItem
import org.kiwix.kiwixmobile.core.utils.AnimationUtils.rotate
import org.kiwix.kiwixmobile.core.utils.DimenUtils.getWindowWidth import org.kiwix.kiwixmobile.core.utils.DimenUtils.getWindowWidth
import org.kiwix.kiwixmobile.core.utils.DonationDialogHandler import org.kiwix.kiwixmobile.core.utils.DonationDialogHandler
import org.kiwix.kiwixmobile.core.utils.DonationDialogHandler.ShowDonationDialogCallback import org.kiwix.kiwixmobile.core.utils.DonationDialogHandler.ShowDonationDialogCallback
@ -237,24 +225,16 @@ abstract class CoreReaderFragment :
private var fragmentReaderBinding: FragmentReaderBinding? = null private var fragmentReaderBinding: FragmentReaderBinding? = null
var toolbar: Toolbar? = null var toolbar: Toolbar? = null
var toolbarContainer: AppBarLayout? = null
var progressBar: ContentLoadingProgressBar? = null
var drawerLayout: DrawerLayout? = null var drawerLayout: DrawerLayout? = null
protected var tableDrawerRightContainer: NavigationView? = null protected var tableDrawerRightContainer: NavigationView? = null
var contentFrame: FrameLayout? = null var contentFrame: FrameLayout? = null
var bottomToolbar: BottomAppBar? = null
var tabSwitcherRoot: View? = null var tabSwitcherRoot: View? = null
var closeAllTabsButton: FloatingActionButton? = null
var videoView: ViewGroup? = null var videoView: ViewGroup? = null
var noOpenBookButton: Button? = null
var activityMainRoot: View? = null var activityMainRoot: View? = null
@JvmField @JvmField
@ -273,10 +253,6 @@ abstract class CoreReaderFragment :
@Inject @Inject
var darkModeConfig: DarkModeConfig? = null var darkModeConfig: DarkModeConfig? = null
@JvmField
@Inject
var menuFactory: MainMenu.Factory? = null
@JvmField @JvmField
@Inject @Inject
var libkiwixBookmarks: LibkiwixBookmarks? = null var libkiwixBookmarks: LibkiwixBookmarks? = null
@ -299,29 +275,7 @@ abstract class CoreReaderFragment :
var toolbarWithSearchPlaceholder: ConstraintLayout? = null var toolbarWithSearchPlaceholder: ConstraintLayout? = null
var backToTopButton: FloatingActionButton? = null
private var stopTTSButton: Button? = null
var pauseTTSButton: Button? = null
var ttsControls: Group? = null
private var exitFullscreenButton: ImageButton? = null
private var bottomToolbarBookmark: ImageView? = null
private var bottomToolbarArrowBack: ImageView? = null
private var bottomToolbarArrowForward: ImageView? = null
private var bottomToolbarHome: ImageView? = null
private var tabRecyclerView: RecyclerView? = null private var tabRecyclerView: RecyclerView? = null
private var noOpenBookText: TextView? = null
private var bottomToolbarToc: ImageView? = null
private var isFirstTimeMainPageLoaded = true private var isFirstTimeMainPageLoaded = true
private var isFromManageExternalLaunch = false private var isFromManageExternalLaunch = false
private val savingTabsMutex = Mutex() private val savingTabsMutex = Mutex()
@ -384,7 +338,6 @@ abstract class CoreReaderFragment :
fullScreenItem = false to null, fullScreenItem = false to null,
showBackToTopButton = false, showBackToTopButton = false,
backToTopButtonClick = { backToTop() }, backToTopButtonClick = { backToTop() },
showFullscreenButton = false,
onExitFullscreenClick = { closeFullScreen() }, onExitFullscreenClick = { closeFullScreen() },
showTtsControls = false, showTtsControls = false,
onPauseTtsClick = { pauseTts() }, onPauseTtsClick = { pauseTts() },
@ -420,7 +373,8 @@ abstract class CoreReaderFragment :
override fun onCloseTab(position: Int) { override fun onCloseTab(position: Int) {
closeTab(position) closeTab(position)
} }
} },
shouldShowFullScreenMode = false
) )
) )
private var readerLifeCycleScope: CoroutineScope? = null private var readerLifeCycleScope: CoroutineScope? = null
@ -699,29 +653,13 @@ abstract class CoreReaderFragment :
private fun prepareViews() { private fun prepareViews() {
fragmentReaderBinding?.let { readerBinding -> fragmentReaderBinding?.let { readerBinding ->
videoView = readerBinding.fullscreenVideoContainer videoView = readerBinding.fullscreenVideoContainer
noOpenBookButton = readerBinding.goToLibraryButtonNoOpenBook
noOpenBookText = readerBinding.noOpenBookText
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) contentFrame = findViewById(R.id.activity_main_content_frame)
toolbar = findViewById(R.id.toolbar) toolbar = findViewById(R.id.toolbar)
toolbarContainer = findViewById(R.id.fragment_main_app_bar)
progressBar = findViewById(R.id.main_fragment_progress_view)
bottomToolbar = findViewById(R.id.bottom_toolbar)
tabSwitcherRoot = findViewById(R.id.activity_main_tab_switcher) tabSwitcherRoot = findViewById(R.id.activity_main_tab_switcher)
closeAllTabsButton = findViewById(R.id.tab_switcher_close_all_tabs)
toolbarWithSearchPlaceholder = findViewById(R.id.toolbarWithSearchPlaceholder) toolbarWithSearchPlaceholder = findViewById(R.id.toolbarWithSearchPlaceholder)
backToTopButton = findViewById(R.id.activity_main_back_to_top_fab)
stopTTSButton = findViewById(R.id.activity_main_button_stop_tts)
pauseTTSButton = findViewById(R.id.activity_main_button_pause_tts)
ttsControls = findViewById(R.id.activity_main_tts_controls)
exitFullscreenButton = findViewById(R.id.activity_main_fullscreen_button)
bottomToolbarBookmark = findViewById(R.id.bottom_toolbar_bookmark)
bottomToolbarArrowBack = findViewById(R.id.bottom_toolbar_arrow_back)
bottomToolbarArrowForward = findViewById(R.id.bottom_toolbar_arrow_forward)
bottomToolbarHome = findViewById(R.id.bottom_toolbar_home)
tabRecyclerView = findViewById(R.id.tab_switcher_recycler_view) tabRecyclerView = findViewById(R.id.tab_switcher_recycler_view)
bottomToolbarToc = findViewById(R.id.bottom_toolbar_toc)
donationLayout = findViewById(R.id.donation_layout) donationLayout = findViewById(R.id.donation_layout)
} }
} }
@ -731,53 +669,6 @@ abstract class CoreReaderFragment :
toolbarWithSearchPlaceholder?.setOnClickListener { toolbarWithSearchPlaceholder?.setOnClickListener {
openSearch(searchString = "", isOpenedFromTabView = false, false) openSearch(searchString = "", isOpenedFromTabView = false, false)
} }
backToTopButton?.setOnClickListener {
backToTop()
}
stopTTSButton?.setOnClickListener {
stopTts()
}
pauseTTSButton?.setOnClickListener {
pauseTts()
}
exitFullscreenButton?.setOnClickListener {
closeFullScreen()
}
bottomToolbarBookmark?.apply {
setOnClickListener {
toggleBookmark()
}
setOnLongClickListener {
goToBookmarks()
}
}
bottomToolbarArrowBack?.apply {
setOnClickListener {
goBack()
}
setOnLongClickListener {
showBackwardHistory()
true
}
}
bottomToolbarArrowForward?.apply {
setOnClickListener {
goForward()
}
setOnLongClickListener {
showForwardHistory()
true
}
}
bottomToolbarToc?.setOnClickListener {
openToc()
}
closeAllTabsButton?.setOnClickListener {
closeAllTabs()
}
bottomToolbarHome?.setOnClickListener {
openMainPage()
}
} }
private fun initTabCallback() { private fun initTabCallback() {
@ -820,7 +711,7 @@ abstract class CoreReaderFragment :
} }
override fun onFinish() { override fun onFinish() {
backToTopButton?.hide() hideBackToTopButton()
} }
} }
} }
@ -956,21 +847,6 @@ abstract class CoreReaderFragment :
private fun showTabSwitcher() { private fun showTabSwitcher() {
(requireActivity() as CoreMainActivity).disableDrawer() (requireActivity() as CoreMainActivity).disableDrawer()
actionBar?.apply {
setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(
ContextCompat.getDrawable(requireActivity(), R.drawable.ic_round_add_white_36dp)
)
// set the contentDescription to UpIndicator icon.
toolbar?.getToolbarNavigationIcon()?.setToolTipWithContentDescription(
getString(R.string.search_open_in_new_tab)
)
setDisplayShowTitleEnabled(false)
}
closeAllTabsButton?.setToolTipWithContentDescription(
resources.getString(R.string.close_all_tabs)
)
setIsCloseAllTabButtonClickable(true)
// Set a negative top margin to the web views to remove // Set a negative top margin to the web views to remove
// the unwanted blank space caused by the toolbar. // the unwanted blank space caused by the toolbar.
// setTopMarginToWebViews(-requireActivity().getToolbarHeight()) // setTopMarginToWebViews(-requireActivity().getToolbarHeight())
@ -979,13 +855,10 @@ abstract class CoreReaderFragment :
copy( copy(
shouldShowBottomAppBar = false, shouldShowBottomAppBar = false,
pageLoadingItem = false to ZERO, pageLoadingItem = false to ZERO,
readerScreenTitle = "" readerScreenTitle = "",
showBackToTopButton = false
) )
} }
contentFrame?.visibility = GONE
progressBar?.visibility = GONE
backToTopButton?.hide()
setTabSwitcherVisibility(VISIBLE)
startAnimation(tabSwitcherRoot, R.anim.slide_down) startAnimation(tabSwitcherRoot, R.anim.slide_down)
tabsAdapter?.let { tabsAdapter -> tabsAdapter?.let { tabsAdapter ->
tabRecyclerView?.let { recyclerView -> tabRecyclerView?.let { recyclerView ->
@ -1053,23 +926,8 @@ abstract class CoreReaderFragment :
* as closing the ZIM book would require reloading the ZIM file, which can be a resource-intensive operation. * as closing the ZIM book would require reloading the ZIM file, which can be a resource-intensive operation.
*/ */
protected open fun hideTabSwitcher(shouldCloseZimBook: Boolean = true) { protected open fun hideTabSwitcher(shouldCloseZimBook: Boolean = true) {
actionBar?.apply {
setDisplayShowTitleEnabled(true)
}
toolbar?.let(::setUpDrawerToggle) toolbar?.let(::setUpDrawerToggle)
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
closeAllTabsButton?.setImageDrawable(
ContextCompat.getDrawable(requireActivity(), R.drawable.ic_close_black_24dp)
)
tabSwitcherRoot?.let {
if (it.isVisible) {
setTabSwitcherVisibility(GONE)
startAnimation(it, R.anim.slide_up)
progressBar?.visibility = VISIBLE
contentFrame?.visibility = VISIBLE
}
}
progressBar?.hide()
selectTab(currentWebViewIndex) selectTab(currentWebViewIndex)
readerScreenState.update { readerScreenState.update {
copy( copy(
@ -1477,32 +1335,16 @@ abstract class CoreReaderFragment :
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun unBindViewsAndBinding() { private fun unBindViewsAndBinding() {
activityMainRoot = null activityMainRoot = null
noOpenBookButton = null
toolbarWithSearchPlaceholder = null toolbarWithSearchPlaceholder = null
backToTopButton = null
stopTTSButton = null
pauseTTSButton = null
ttsControls = null
exitFullscreenButton = null
bottomToolbarBookmark = null
bottomToolbarArrowBack = null
bottomToolbarArrowForward = null
bottomToolbarHome = null
tabRecyclerView = null tabRecyclerView = null
noOpenBookText = null
bottomToolbarToc = null
bottomToolbar = null
tabSwitcherRoot = null tabSwitcherRoot = null
videoView = null videoView = null
contentFrame = null contentFrame = null
toolbarContainer = null
compatCallback?.finish() compatCallback?.finish()
compatCallback = null compatCallback = null
toolbar?.setOnTouchListener(null) toolbar?.setOnTouchListener(null)
toolbar = null toolbar = null
progressBar = null
drawerLayout = null drawerLayout = null
closeAllTabsButton = null
tableDrawerRightContainer = null tableDrawerRightContainer = null
fragmentReaderBinding?.root?.removeAllViews() fragmentReaderBinding?.root?.removeAllViews()
fragmentReaderBinding = null fragmentReaderBinding = null
@ -1627,18 +1469,17 @@ abstract class CoreReaderFragment :
if (index <= currentWebViewIndex && currentWebViewIndex > 0) { if (index <= currentWebViewIndex && currentWebViewIndex > 0) {
currentWebViewIndex-- currentWebViewIndex--
} }
tabsAdapter?.apply {
notifyItemRemoved(index)
notifyDataSetChanged()
}
readerScreenState.value.snackBarHostState.snack( readerScreenState.value.snackBarHostState.snack(
requireActivity().getString(R.string.tab_closed), requireActivity().getString(R.string.tab_closed),
actionLabel = requireActivity().getString(R.string.undo), actionLabel = requireActivity().getString(R.string.undo),
actionClick = { restoreDeletedTab(index) }, actionClick = { restoreDeletedTab(index) },
lifecycleScope = lifecycleScope, lifecycleScope = lifecycleScope,
snackBarResult = { result -> snackBarResult = { result ->
if (result == SnackbarResult.Dismissed && webViewList.isEmpty() && isAdded) { if (result == SnackbarResult.Dismissed && isAdded) {
closeZimBook() saveTabStates()
if (webViewList.isEmpty()) {
closeZimBook()
}
} }
} }
) )
@ -1774,14 +1615,13 @@ abstract class CoreReaderFragment :
if (readerScreenState.value.showTtsControls) { if (readerScreenState.value.showTtsControls) {
// currently TTS is running // currently TTS is running
if (isBackToTopEnabled) { if (isBackToTopEnabled) {
readerScreenState.update { copy(showBackToTopButton = true) } showBackToTopButton()
backToTopButton?.show()
} }
tts?.stop() tts?.stop()
} else { } else {
// TTS is not running. // TTS is not running.
if (isBackToTopEnabled) { if (isBackToTopEnabled) {
readerScreenState.update { copy(showBackToTopButton = false) } hideBackToTopButton()
} }
readerScreenState.update { readerScreenState.update {
copy(pauseTtsButtonText = context?.getString(R.string.tts_pause).orEmpty()) copy(pauseTtsButtonText = context?.getString(R.string.tts_pause).orEmpty())
@ -1911,12 +1751,12 @@ abstract class CoreReaderFragment :
@Suppress("MagicNumber") @Suppress("MagicNumber")
protected open fun openFullScreen() { protected open fun openFullScreen() {
(requireActivity() as CoreMainActivity).disableDrawer(false) (requireActivity() as CoreMainActivity).disableDrawer(false)
toolbarContainer?.visibility = GONE
readerScreenState.update { readerScreenState.update {
copy(shouldShowBottomAppBar = false) copy(
shouldShowBottomAppBar = false,
shouldShowFullScreenMode = true
)
} }
exitFullscreenButton?.visibility = VISIBLE
exitFullscreenButton?.background?.alpha = 153
val window = requireActivity().window val window = requireActivity().window
window.decorView.showFullScreenMode(window) window.decorView.showFullScreenMode(window)
getCurrentWebView()?.apply { getCurrentWebView()?.apply {
@ -1931,10 +1771,13 @@ abstract class CoreReaderFragment :
toolbar?.let(::setUpDrawerToggle) toolbar?.let(::setUpDrawerToggle)
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
sharedPreferenceUtil?.putPrefFullScreen(false) sharedPreferenceUtil?.putPrefFullScreen(false)
toolbarContainer?.visibility = VISIBLE readerScreenState.update {
copy(
shouldShowBottomAppBar = true,
shouldShowFullScreenMode = false
)
}
updateBottomToolbarVisibility() updateBottomToolbarVisibility()
exitFullscreenButton?.visibility = GONE
exitFullscreenButton?.background?.alpha = 255
val window = requireActivity().window val window = requireActivity().window
window.decorView.closeFullScreenMode(window) window.decorView.closeFullScreenMode(window)
getCurrentWebView()?.requestLayout() getCurrentWebView()?.requestLayout()
@ -2132,17 +1975,12 @@ abstract class CoreReaderFragment :
private fun closeAllTabs() { private fun closeAllTabs() {
onReadAloudStop() onReadAloudStop()
closeAllTabsButton?.apply {
rotate()
setIsCloseAllTabButtonClickable(false)
}
tempZimSourceForUndo = zimReaderContainer?.zimReaderSource tempZimSourceForUndo = zimReaderContainer?.zimReaderSource
tempWebViewListForUndo.apply { tempWebViewListForUndo.apply {
clear() clear()
addAll(webViewList) addAll(webViewList)
} }
webViewList.clear() webViewList.clear()
tabsAdapter?.notifyDataSetChanged()
openHomeScreen() openHomeScreen()
readerScreenState.value.snackBarHostState.snack( readerScreenState.value.snackBarHostState.snack(
context?.getString(R.string.tabs_closed).orEmpty(), context?.getString(R.string.tabs_closed).orEmpty(),
@ -2150,17 +1988,16 @@ abstract class CoreReaderFragment :
actionClick = { restoreDeletedTabs() }, actionClick = { restoreDeletedTabs() },
lifecycleScope = lifecycleScope, lifecycleScope = lifecycleScope,
snackBarResult = { result -> snackBarResult = { result ->
if (result == SnackbarResult.Dismissed && webViewList.isEmpty() && isAdded) { if (result == SnackbarResult.Dismissed && isAdded) {
closeZimBook() saveTabStates()
if (webViewList.isEmpty()) {
closeZimBook()
}
} }
} }
) )
} }
private fun setIsCloseAllTabButtonClickable(isClickable: Boolean) {
closeAllTabsButton?.isClickable = isClickable
}
private fun restoreDeletedTabs() { private fun restoreDeletedTabs() {
if (tempWebViewListForUndo.isNotEmpty()) { if (tempWebViewListForUndo.isNotEmpty()) {
webViewList.addAll(tempWebViewListForUndo) webViewList.addAll(tempWebViewListForUndo)
@ -2652,12 +2489,20 @@ abstract class CoreReaderFragment :
isBackToTopEnabled = sharedPreferenceUtil?.prefBackToTop == true isBackToTopEnabled = sharedPreferenceUtil?.prefBackToTop == true
isOpenNewTabInBackground = sharedPreferenceUtil?.prefNewTabBackground == true isOpenNewTabInBackground = sharedPreferenceUtil?.prefNewTabBackground == true
if (!isBackToTopEnabled) { if (!isBackToTopEnabled) {
backToTopButton?.hide() hideBackToTopButton()
} }
openFullScreenIfEnabled() openFullScreenIfEnabled()
updateNightMode() updateNightMode()
} }
private fun showBackToTopButton() {
readerScreenState.update { copy(showBackToTopButton = true) }
}
private fun hideBackToTopButton() {
readerScreenState.update { copy(showBackToTopButton = false) }
}
/** /**
* Saves the current state of tabs and web view history to persistent storage. * Saves the current state of tabs and web view history to persistent storage.
* *
@ -2869,28 +2714,18 @@ abstract class CoreReaderFragment :
tabsAdapter?.notifyDataSetChanged() tabsAdapter?.notifyDataSetChanged()
} }
@Suppress("NestedBlockDepth", "MagicNumber") @Suppress("MagicNumber")
override fun webViewPageChanged(page: Int, maxPages: Int) { override fun webViewPageChanged(page: Int, maxPages: Int) {
if (isBackToTopEnabled) { if (!isBackToTopEnabled) return
hideBackToTopTimer?.apply { hideBackToTopTimer?.apply {
cancel() cancel()
start() start()
} }
getCurrentWebView()?.scrollY?.let { val scrollY = getCurrentWebView()?.scrollY ?: return
if (it > 200) { if (scrollY > 200 && !readerScreenState.value.showTtsControls) {
if ( showBackToTopButton()
(backToTopButton?.isGone == true || backToTopButton?.isInvisible == true) && } else {
ttsControls?.visibility == GONE hideBackToTopButton()
) {
backToTopButton?.show()
}
} else {
backToTopButton?.isVisible
if (backToTopButton?.visibility == VISIBLE) {
backToTopButton?.hide()
}
}
}
} }
} }

View File

@ -25,6 +25,7 @@ import android.widget.FrameLayout
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
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -46,6 +47,7 @@ import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
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
@ -54,14 +56,16 @@ import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
@ -73,6 +77,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
@ -106,8 +111,10 @@ import org.kiwix.kiwixmobile.core.ui.models.IconItem
import org.kiwix.kiwixmobile.core.ui.models.IconItem.Drawable import org.kiwix.kiwixmobile.core.ui.models.IconItem.Drawable
import org.kiwix.kiwixmobile.core.ui.models.toPainter import org.kiwix.kiwixmobile.core.ui.models.toPainter
import org.kiwix.kiwixmobile.core.ui.theme.Black import org.kiwix.kiwixmobile.core.ui.theme.Black
import org.kiwix.kiwixmobile.core.ui.theme.DenimBlue800
import org.kiwix.kiwixmobile.core.ui.theme.KiwixDialogTheme import org.kiwix.kiwixmobile.core.ui.theme.KiwixDialogTheme
import org.kiwix.kiwixmobile.core.ui.theme.White import org.kiwix.kiwixmobile.core.ui.theme.White
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.BACK_TO_TOP_BUTTON_BOTTOM_MARGIN
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.CLOSE_ALL_TAB_BUTTON_BOTTOM_PADDING import org.kiwix.kiwixmobile.core.utils.ComposeDimens.CLOSE_ALL_TAB_BUTTON_BOTTOM_PADDING
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.CLOSE_TAB_ICON_ANIMATION_TIMEOUT import org.kiwix.kiwixmobile.core.utils.ComposeDimens.CLOSE_TAB_ICON_ANIMATION_TIMEOUT
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.CLOSE_TAB_ICON_SIZE import org.kiwix.kiwixmobile.core.utils.ComposeDimens.CLOSE_TAB_ICON_SIZE
@ -115,8 +122,11 @@ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOUR_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOUR_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.ONE_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.ONE_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_DISABLE_BUTTON_ALPHA
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_LAYOUT_HEIGHT import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_LAYOUT_HEIGHT
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SEVEN_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TEN_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TTS_BUTTONS_CONTROL_ALPHA import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TTS_BUTTONS_CONTROL_ALPHA
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP
import org.kiwix.kiwixmobile.core.utils.StyleUtils.fromHtml import org.kiwix.kiwixmobile.core.utils.StyleUtils.fromHtml
@ -166,7 +176,7 @@ private fun ReaderTopBar(
scrollBehavior: TopAppBarScrollBehavior, scrollBehavior: TopAppBarScrollBehavior,
navigationIcon: @Composable () -> Unit, navigationIcon: @Composable () -> Unit,
) { ) {
if (!state.fullScreenItem.first) { if (!state.shouldShowFullScreenMode) {
KiwixAppBar( KiwixAppBar(
title = if (state.showTabSwitcher) "" else state.readerScreenTitle, title = if (state.showTabSwitcher) "" else state.readerScreenTitle,
navigationIcon = navigationIcon, navigationIcon = navigationIcon,
@ -205,6 +215,10 @@ private fun ReaderContentLayout(state: ReaderScreenState, modifier: Modifier = M
) )
} }
ShowFullScreenView(state) ShowFullScreenView(state)
CloseFullScreenImageButton(
state.shouldShowFullScreenMode,
state.onExitFullscreenClick
)
} }
} }
@ -212,6 +226,29 @@ private fun ReaderContentLayout(state: ReaderScreenState, modifier: Modifier = M
} }
} }
@Composable
private fun BoxScope.CloseFullScreenImageButton(
shouldShowFullScreenMode: Boolean,
onExitFullScreen: () -> Unit
) {
if (shouldShowFullScreenMode) {
IconButton(
onClick = onExitFullScreen,
modifier = Modifier
.align(Alignment.TopEnd)
.padding(SEVEN_DP)
.minimumInteractiveComponentSize()
.background(MaterialTheme.colorScheme.onSurface)
) {
Icon(
painter = IconItem.Drawable(R.drawable.fullscreen_exit).toPainter(),
contentDescription = stringResource(id = R.string.menu_exit_full_screen),
tint = MaterialTheme.colorScheme.surface
)
}
}
}
@Composable @Composable
private fun ShowZIMFileContent(state: ReaderScreenState) { private fun ShowZIMFileContent(state: ReaderScreenState) {
state.selectedWebView?.let { selectedWebView -> state.selectedWebView?.let { selectedWebView ->
@ -303,12 +340,12 @@ private fun TtsControls(state: ReaderScreenState) {
@Composable @Composable
private fun BackToTopFab(state: ReaderScreenState) { private fun BackToTopFab(state: ReaderScreenState) {
if (state.showBackToTopButton) { if (state.showBackToTopButton) {
FloatingActionButton( SmallFloatingActionButton(
onClick = state.backToTopButtonClick, onClick = state.backToTopButtonClick,
modifier = Modifier, modifier = Modifier.padding(bottom = BACK_TO_TOP_BUTTON_BOTTOM_MARGIN),
containerColor = MaterialTheme.colorScheme.primary, containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary, contentColor = MaterialTheme.colorScheme.onPrimary,
shape = FloatingActionButtonDefaults.smallShape shape = CircleShape
) { ) {
Icon( Icon(
painter = Drawable(R.drawable.action_find_previous).toPainter(), painter = Drawable(R.drawable.action_find_previous).toPainter(),
@ -389,15 +426,26 @@ private fun BottomAppBarButtonIcon(
shouldEnable: Boolean = true, shouldEnable: Boolean = true,
contentDescription: String contentDescription: String
) { ) {
IconButton( Box(
onClick = onClick, modifier = Modifier
modifier = Modifier.combinedClickable(onClick = onClick, onLongClick = onLongClick), .size(READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE + TEN_DP)
enabled = shouldEnable .clip(CircleShape)
.combinedClickable(
onClick = onClick,
onLongClick = onLongClick,
enabled = shouldEnable
),
contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
buttonIcon.toPainter(), buttonIcon.toPainter(),
contentDescription, contentDescription,
modifier = Modifier.size(READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE) modifier = Modifier.size(READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE),
tint = if (shouldEnable) {
LocalContentColor.current
} else {
LocalContentColor.current.copy(alpha = READER_BOTTOM_APP_BAR_DISABLE_BUTTON_ALPHA)
}
) )
} }
} }
@ -504,6 +552,8 @@ private fun BoxScope.CloseAllTabButton(onCloseAllTabs: () -> Unit) {
onCloseAllTabs() onCloseAllTabs()
} }
), ),
containerColor = DenimBlue800,
contentColor = White
) { ) {
Icon( Icon(
painter = painterResource( painter = painterResource(

View File

@ -56,13 +56,17 @@ data class ReaderScreenState(
*/ */
val shouldShowDonationPopup: Boolean, val shouldShowDonationPopup: Boolean,
/** /**
* Manages the showing of "Full screen view". * Manages the showing of "Full screen view" of webView's video.
* *
* A [Pair] containing: * A [Pair] containing:
* - [Boolean]: Whether to show/hide full screen mode. * - [Boolean]: Whether to show/hide full screen mode.
* - [ComposeView]: full screen view. * - [ComposeView]: full screen view.
*/ */
val fullScreenItem: Pair<Boolean, ComposeView?>, val fullScreenItem: Pair<Boolean, ComposeView?>,
/**
* Manages the showing of "Full screen mode".
*/
val shouldShowFullScreenMode: Boolean,
/** /**
* Manages the showing of "BackToTop" fab button. * Manages the showing of "BackToTop" fab button.
*/ */
@ -71,7 +75,6 @@ data class ReaderScreenState(
* Handles the click of "BackToTop" fab button. * Handles the click of "BackToTop" fab button.
*/ */
val backToTopButtonClick: () -> Unit, val backToTopButtonClick: () -> Unit,
val showFullscreenButton: Boolean = false,
val onExitFullscreenClick: () -> Unit = {}, val onExitFullscreenClick: () -> Unit = {},
val showTtsControls: Boolean = false, val showTtsControls: Boolean = false,
val onPauseTtsClick: () -> Unit = {}, val onPauseTtsClick: () -> Unit = {},

View File

@ -1,73 +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.utils
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.animation.LinearInterpolator
import androidx.core.animation.addListener
import com.google.android.material.floatingactionbutton.FloatingActionButton
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.extensions.setImageDrawableCompat
private const val HEIGHT_ANIM_DURATION = 100L
private const val ROTATE_ANIM_DURATION = 200L
private const val FULL_ROTATION = 360.0f
object AnimationUtils {
@JvmStatic fun View.expand() {
measure(MATCH_PARENT, WRAP_CONTENT)
// Older versions of android (pre API 21) cancel animations for views with a height of 0.
layoutParams.height = 1
visibility = View.VISIBLE
animateHeight(1, measuredHeight) {
layoutParams.height = WRAP_CONTENT
}
}
@JvmStatic fun View.collapse() {
animateHeight(measuredHeight, 0) { visibility = View.GONE }
}
@SuppressLint("Recycle")
private fun View.animateHeight(start: Int, end: Int, onEndAction: () -> Unit) {
ValueAnimator.ofInt(start, end).apply {
addUpdateListener {
layoutParams.height = it.animatedValue as Int
requestLayout()
}
addListener(onEnd = { onEndAction.invoke() })
duration = HEIGHT_ANIM_DURATION
interpolator = LinearInterpolator()
}.start()
}
@JvmStatic fun FloatingActionButton.rotate() {
animate().apply {
withStartAction { setImageDrawableCompat(R.drawable.ic_close_black_24dp) }
withEndAction { setImageDrawableCompat(R.drawable.ic_done_white_24dp) }
rotationBy(FULL_ROTATION)
duration = ROTATE_ANIM_DURATION
}.start()
}
}

View File

@ -48,6 +48,7 @@ object ComposeDimens {
val TWELVE_DP = 12.dp val TWELVE_DP = 12.dp
val TEN_DP = 10.dp val TEN_DP = 10.dp
val EIGHT_DP = 8.dp val EIGHT_DP = 8.dp
val SEVEN_DP = 7.dp
val SIX_DP = 6.dp val SIX_DP = 6.dp
val FIVE_DP = 5.dp val FIVE_DP = 5.dp
val FOUR_DP = 4.dp val FOUR_DP = 4.dp
@ -189,4 +190,6 @@ object ComposeDimens {
const val TAB_SWITCHER_ICON_CORNER_RADIUS = 10 const val TAB_SWITCHER_ICON_CORNER_RADIUS = 10
val CLOSE_TAB_ICON_SIZE = 20.dp val CLOSE_TAB_ICON_SIZE = 20.dp
const val CLOSE_TAB_ICON_ANIMATION_TIMEOUT = 1200L const val CLOSE_TAB_ICON_ANIMATION_TIMEOUT = 1200L
val BACK_TO_TOP_BUTTON_BOTTOM_MARGIN = 80.dp
const val READER_BOTTOM_APP_BAR_DISABLE_BUTTON_ALPHA = 0.38f
} }