mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-09 07:16:04 -04:00
Refactored all tab-related functionalities.
* Improved the tabs UI to align with our XML-based layout. Added a border to the selected tab for better visual feedback, making it easier for users to identify the active tab. * Added animation to smoothly scroll to the selected tab when the tab switcher is opened. * Fixed a crash scenario when "Close All Tabs" was triggered. * Fixed a crash that occurred when launching the app for the first time. * Fixed an issue where the tab menu item was still visible even after all tabs were closed.
This commit is contained in:
parent
f654a64b7e
commit
b694ae3170
@ -42,6 +42,7 @@ 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
|
||||||
import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions.Super.ShouldCall
|
import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions.Super.ShouldCall
|
||||||
|
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
|
||||||
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.setupDrawerToggle
|
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.setupDrawerToggle
|
||||||
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
|
||||||
@ -208,6 +209,26 @@ class KiwixReaderFragment : CoreReaderFragment() {
|
|||||||
selectTab(currentWebViewIndex)
|
selectTab(currentWebViewIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
actionBar?.setDisplayShowTitleEnabled(true)
|
||||||
|
toolbar?.let { activity?.setupDrawerToggle(it, true) }
|
||||||
|
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
|
||||||
|
if (webViewList.isEmpty()) {
|
||||||
|
readerMenuState?.hideTabSwitcher()
|
||||||
|
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)
|
||||||
|
readerScreenState.update {
|
||||||
|
copy(
|
||||||
|
shouldShowBottomAppBar = true,
|
||||||
|
pageLoadingItem = false to ZERO,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
readerMenuState?.showWebViewOptions(urlIsValid())
|
||||||
|
selectTab(currentWebViewIndex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setFragmentContainerBottomMarginToSizeOfNavBar() {
|
private fun setFragmentContainerBottomMarginToSizeOfNavBar() {
|
||||||
|
@ -72,8 +72,10 @@ import androidx.compose.material3.SnackbarDuration
|
|||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.SnackbarResult
|
import androidx.compose.material3.SnackbarResult
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
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
|
||||||
@ -184,7 +186,6 @@ 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.AnimationUtils.rotate
|
||||||
import org.kiwix.kiwixmobile.core.utils.DimenUtils.getToolbarHeight
|
|
||||||
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
|
||||||
@ -291,7 +292,7 @@ abstract class CoreReaderFragment :
|
|||||||
@JvmField
|
@JvmField
|
||||||
@Inject
|
@Inject
|
||||||
var painter: DarkModeViewPainter? = null
|
var painter: DarkModeViewPainter? = null
|
||||||
protected var currentWebViewIndex = 0
|
protected var currentWebViewIndex by mutableStateOf(0)
|
||||||
private var currentTtsWebViewIndex = 0
|
private var currentTtsWebViewIndex = 0
|
||||||
protected var actionBar: ActionBar? = null
|
protected var actionBar: ActionBar? = null
|
||||||
protected var mainMenu: MainMenu? = null
|
protected var mainMenu: MainMenu? = null
|
||||||
@ -403,7 +404,23 @@ abstract class CoreReaderFragment :
|
|||||||
bottomNavigationHeight = ZERO,
|
bottomNavigationHeight = ZERO,
|
||||||
shouldShowBottomAppBar = true,
|
shouldShowBottomAppBar = true,
|
||||||
selectedWebView = null,
|
selectedWebView = null,
|
||||||
readerScreenTitle = ""
|
readerScreenTitle = "",
|
||||||
|
showTabSwitcher = false,
|
||||||
|
darkModeViewPainter = null,
|
||||||
|
currentWebViewPosition = ZERO,
|
||||||
|
onTabClickListener = object : TabClickListener {
|
||||||
|
override fun onSelectTab(position: Int) {
|
||||||
|
hideTabSwitcher()
|
||||||
|
selectTab(position)
|
||||||
|
|
||||||
|
// Bug Fix #592
|
||||||
|
updateBottomToolbarArrowsAlpha()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCloseTab(position: Int) {
|
||||||
|
closeTab(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
private var readerLifeCycleScope: CoroutineScope? = null
|
private var readerLifeCycleScope: CoroutineScope? = null
|
||||||
@ -506,7 +523,7 @@ abstract class CoreReaderFragment :
|
|||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
val isBottomNavVisible = rememberBottomNavigationVisibility(lazyListState)
|
val isBottomNavVisible = rememberBottomNavigationVisibility(lazyListState)
|
||||||
LaunchedEffect(isBottomNavVisible) {
|
LaunchedEffect(isBottomNavVisible) {
|
||||||
(requireActivity() as CoreMainActivity).toggleBottomNavigation(isBottomNavVisible)
|
(activity as? CoreMainActivity)?.toggleBottomNavigation(isBottomNavVisible)
|
||||||
}
|
}
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
snapshotFlow { webViewList.size }
|
snapshotFlow { webViewList.size }
|
||||||
@ -515,11 +532,14 @@ abstract class CoreReaderFragment :
|
|||||||
updateTabIcon(size)
|
updateTabIcon(size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(currentWebViewIndex, readerMenuState?.isInTabSwitcher) {
|
||||||
readerScreenState.update {
|
readerScreenState.update {
|
||||||
copy(
|
copy(
|
||||||
bottomNavigationHeight = getBottomNavigationHeight(),
|
bottomNavigationHeight = getBottomNavigationHeight(),
|
||||||
readerScreenTitle = context.getString(R.string.reader)
|
readerScreenTitle = context.getString(R.string.reader),
|
||||||
|
darkModeViewPainter = darkModeViewPainter,
|
||||||
|
currentWebViewPosition = currentWebViewIndex,
|
||||||
|
showTabSwitcher = readerMenuState?.isInTabSwitcher == true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -528,8 +548,8 @@ abstract class CoreReaderFragment :
|
|||||||
actionMenuItems = readerMenuState?.menuItems.orEmpty(),
|
actionMenuItems = readerMenuState?.menuItems.orEmpty(),
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
NavigationIcon(
|
NavigationIcon(
|
||||||
iconItem = IconItem.Vector(Icons.Filled.Menu),
|
iconItem = navigationIcon(),
|
||||||
contentDescription = string.open_drawer,
|
contentDescription = navigationIconContentDescription(),
|
||||||
onClick = { navigationIconClick() }
|
onClick = { navigationIconClick() }
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -640,18 +660,35 @@ abstract class CoreReaderFragment :
|
|||||||
|
|
||||||
private fun getBottomNavigationHeight(): Int = getBottomNavigationView()?.measuredHeight ?: ZERO
|
private fun getBottomNavigationHeight(): Int = getBottomNavigationView()?.measuredHeight ?: ZERO
|
||||||
|
|
||||||
private fun navigationIconClick() {
|
private fun navigationIconContentDescription() =
|
||||||
// Manually handle the navigation open/close.
|
if (readerMenuState?.isInTabSwitcher == true) {
|
||||||
// Since currently we are using the view based navigation drawer in other screens.
|
R.string.search_open_in_new_tab
|
||||||
// Once we fully migrate to jetpack compose we will refactor this code to use the
|
|
||||||
// compose navigation.
|
|
||||||
// TODO Replace with compose based navigation when migration is done.
|
|
||||||
val activity = activity as CoreMainActivity
|
|
||||||
if (activity.navigationDrawerIsOpen()) {
|
|
||||||
activity.closeNavigationDrawer()
|
|
||||||
} else {
|
} else {
|
||||||
activity.openNavigationDrawer()
|
string.open_drawer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun navigationIconClick() {
|
||||||
|
if (readerMenuState?.isInTabSwitcher == true) {
|
||||||
|
onHomeMenuClicked()
|
||||||
|
} else {
|
||||||
|
// Manually handle the navigation open/close.
|
||||||
|
// Since currently we are using the view based navigation drawer in other screens.
|
||||||
|
// Once we fully migrate to jetpack compose we will refactor this code to use the
|
||||||
|
// compose navigation.
|
||||||
|
// TODO Replace with compose based navigation when migration is done.
|
||||||
|
val activity = activity as CoreMainActivity
|
||||||
|
if (activity.navigationDrawerIsOpen()) {
|
||||||
|
activity.closeNavigationDrawer()
|
||||||
|
} else {
|
||||||
|
activity.openNavigationDrawer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigationIcon() = if (readerMenuState?.isInTabSwitcher == true) {
|
||||||
|
IconItem.Drawable(R.drawable.ic_round_add_white_36dp)
|
||||||
|
} else {
|
||||||
|
IconItem.Vector(Icons.Filled.Menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addAlertDialogToDialogHost() {
|
private fun addAlertDialogToDialogHost() {
|
||||||
@ -936,10 +973,14 @@ abstract class CoreReaderFragment :
|
|||||||
setIsCloseAllTabButtonClickable(true)
|
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())
|
||||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||||
readerScreenState.update {
|
readerScreenState.update {
|
||||||
copy(shouldShowBottomAppBar = false)
|
copy(
|
||||||
|
shouldShowBottomAppBar = false,
|
||||||
|
pageLoadingItem = false to ZERO,
|
||||||
|
readerScreenTitle = ""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
contentFrame?.visibility = GONE
|
contentFrame?.visibility = GONE
|
||||||
progressBar?.visibility = GONE
|
progressBar?.visibility = GONE
|
||||||
@ -1030,10 +1071,16 @@ abstract class CoreReaderFragment :
|
|||||||
}
|
}
|
||||||
progressBar?.hide()
|
progressBar?.hide()
|
||||||
selectTab(currentWebViewIndex)
|
selectTab(currentWebViewIndex)
|
||||||
|
readerScreenState.update {
|
||||||
|
copy(
|
||||||
|
shouldShowBottomAppBar = true,
|
||||||
|
pageLoadingItem = false to ZERO,
|
||||||
|
)
|
||||||
|
}
|
||||||
readerMenuState?.showWebViewOptions(urlIsValid())
|
readerMenuState?.showWebViewOptions(urlIsValid())
|
||||||
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1587,11 +1634,10 @@ abstract class CoreReaderFragment :
|
|||||||
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),
|
||||||
snackbarDuration = SnackbarDuration.Long,
|
|
||||||
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 && webViewList.isEmpty() && isAdded) {
|
||||||
closeZimBook()
|
closeZimBook()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1610,7 +1656,7 @@ abstract class CoreReaderFragment :
|
|||||||
readerScreenState.update {
|
readerScreenState.update {
|
||||||
copy(
|
copy(
|
||||||
shouldShowBottomAppBar = false,
|
shouldShowBottomAppBar = false,
|
||||||
readerScreenTitle = requireActivity().getString(R.string.reader)
|
readerScreenTitle = context?.getString(R.string.reader).orEmpty()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
contentFrame?.visibility = GONE
|
contentFrame?.visibility = GONE
|
||||||
@ -1657,7 +1703,7 @@ abstract class CoreReaderFragment :
|
|||||||
webViewList.add(index, it)
|
webViewList.add(index, it)
|
||||||
tabsAdapter?.notifyDataSetChanged()
|
tabsAdapter?.notifyDataSetChanged()
|
||||||
readerScreenState.value.snackBarHostState.snack(
|
readerScreenState.value.snackBarHostState.snack(
|
||||||
requireActivity().getString(R.string.tab_restored),
|
context?.getString(R.string.tab_restored).orEmpty(),
|
||||||
lifecycleScope = lifecycleScope
|
lifecycleScope = lifecycleScope
|
||||||
)
|
)
|
||||||
setUpWithTextToSpeech(it)
|
setUpWithTextToSpeech(it)
|
||||||
@ -1778,16 +1824,15 @@ abstract class CoreReaderFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onHomeMenuClicked() {
|
override fun onHomeMenuClicked() {
|
||||||
if (tabSwitcherRoot?.visibility == VISIBLE) {
|
if (readerScreenState.value.showTabSwitcher) {
|
||||||
hideTabSwitcher()
|
hideTabSwitcher()
|
||||||
}
|
}
|
||||||
createNewTab()
|
createNewTab()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTabMenuClicked() {
|
override fun onTabMenuClicked() {
|
||||||
if (tabSwitcherRoot?.visibility == VISIBLE) {
|
if (readerScreenState.value.showTabSwitcher) {
|
||||||
hideTabSwitcher()
|
hideTabSwitcher()
|
||||||
selectTab(currentWebViewIndex)
|
|
||||||
} else {
|
} else {
|
||||||
showTabSwitcher()
|
showTabSwitcher()
|
||||||
}
|
}
|
||||||
@ -2062,8 +2107,8 @@ abstract class CoreReaderFragment :
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
readerScreenState.value.snackBarHostState.snack(
|
readerScreenState.value.snackBarHostState.snack(
|
||||||
requireActivity().getString(R.string.request_storage),
|
context?.getString(R.string.request_storage).orEmpty(),
|
||||||
actionLabel = requireActivity().getString(R.string.menu_settings),
|
context?.getString(R.string.menu_settings),
|
||||||
snackbarDuration = SnackbarDuration.Long,
|
snackbarDuration = SnackbarDuration.Long,
|
||||||
actionClick = {
|
actionClick = {
|
||||||
val intent = Intent()
|
val intent = Intent()
|
||||||
@ -2100,13 +2145,12 @@ abstract class CoreReaderFragment :
|
|||||||
tabsAdapter?.notifyDataSetChanged()
|
tabsAdapter?.notifyDataSetChanged()
|
||||||
openHomeScreen()
|
openHomeScreen()
|
||||||
readerScreenState.value.snackBarHostState.snack(
|
readerScreenState.value.snackBarHostState.snack(
|
||||||
requireActivity().getString(R.string.tabs_closed),
|
context?.getString(R.string.tabs_closed).orEmpty(),
|
||||||
actionLabel = requireActivity().getString(R.string.undo),
|
context?.getString(R.string.undo),
|
||||||
snackbarDuration = SnackbarDuration.Long,
|
|
||||||
actionClick = { restoreDeletedTabs() },
|
actionClick = { restoreDeletedTabs() },
|
||||||
lifecycleScope = lifecycleScope,
|
lifecycleScope = lifecycleScope,
|
||||||
snackBarResult = { result ->
|
snackBarResult = { result ->
|
||||||
if (result != SnackbarResult.Dismissed && webViewList.isEmpty() && isAdded) {
|
if (result == SnackbarResult.Dismissed && webViewList.isEmpty() && isAdded) {
|
||||||
closeZimBook()
|
closeZimBook()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2122,7 +2166,7 @@ abstract class CoreReaderFragment :
|
|||||||
webViewList.addAll(tempWebViewListForUndo)
|
webViewList.addAll(tempWebViewListForUndo)
|
||||||
tabsAdapter?.notifyDataSetChanged()
|
tabsAdapter?.notifyDataSetChanged()
|
||||||
readerScreenState.value.snackBarHostState.snack(
|
readerScreenState.value.snackBarHostState.snack(
|
||||||
requireActivity().getString(R.string.tabs_restored),
|
context?.getString(R.string.tabs_restored).orEmpty(),
|
||||||
lifecycleScope = lifecycleScope
|
lifecycleScope = lifecycleScope
|
||||||
)
|
)
|
||||||
reopenBook()
|
reopenBook()
|
||||||
@ -2162,7 +2206,7 @@ abstract class CoreReaderFragment :
|
|||||||
if (isBookmarked) {
|
if (isBookmarked) {
|
||||||
repositoryActions?.deleteBookmark(libKiwixBook.id, articleUrl)
|
repositoryActions?.deleteBookmark(libKiwixBook.id, articleUrl)
|
||||||
readerScreenState.value.snackBarHostState.snack(
|
readerScreenState.value.snackBarHostState.snack(
|
||||||
requireActivity().getString(R.string.bookmark_removed),
|
context?.getString(R.string.bookmark_removed).orEmpty(),
|
||||||
lifecycleScope = lifecycleScope
|
lifecycleScope = lifecycleScope
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -2171,9 +2215,9 @@ abstract class CoreReaderFragment :
|
|||||||
LibkiwixBookmarkItem(it, articleUrl, zimFileReader, libKiwixBook)
|
LibkiwixBookmarkItem(it, articleUrl, zimFileReader, libKiwixBook)
|
||||||
)
|
)
|
||||||
readerScreenState.value.snackBarHostState.snack(
|
readerScreenState.value.snackBarHostState.snack(
|
||||||
requireActivity().getString(R.string.bookmark_added),
|
context?.getString(R.string.bookmark_added).orEmpty(),
|
||||||
lifecycleScope = lifecycleScope,
|
lifecycleScope = lifecycleScope,
|
||||||
actionLabel = requireActivity().getString(R.string.open),
|
actionLabel = context?.getString(R.string.open),
|
||||||
actionClick = { goToBookmarks() }
|
actionClick = { goToBookmarks() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -2890,9 +2934,9 @@ abstract class CoreReaderFragment :
|
|||||||
if (isOpenNewTabInBackground) {
|
if (isOpenNewTabInBackground) {
|
||||||
newTabInBackground(url)
|
newTabInBackground(url)
|
||||||
readerScreenState.value.snackBarHostState.snack(
|
readerScreenState.value.snackBarHostState.snack(
|
||||||
message = requireActivity().getString(R.string.new_tab_snack_bar),
|
message = context?.getString(R.string.new_tab_snack_bar).orEmpty(),
|
||||||
lifecycleScope = lifecycleScope,
|
lifecycleScope = lifecycleScope,
|
||||||
actionLabel = requireActivity().getString(R.string.open),
|
actionLabel = context?.getString(R.string.open),
|
||||||
actionClick = {
|
actionClick = {
|
||||||
if (webViewList.size > 1) {
|
if (webViewList.size > 1) {
|
||||||
selectTab(webViewList.size - 1)
|
selectTab(webViewList.size - 1)
|
||||||
|
@ -46,7 +46,7 @@ import org.kiwix.kiwixmobile.core.ui.theme.White
|
|||||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.MATERIAL_MINIMUM_HEIGHT_AND_WIDTH
|
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.MATERIAL_MINIMUM_HEIGHT_AND_WIDTH
|
||||||
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.SIX_DP
|
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIX_DP
|
||||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TAB_SWITCHER_CORNER_RADIUS
|
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TAB_SWITCHER_ICON_CORNER_RADIUS
|
||||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TAB_SWITCHER_TEXT_SIZE
|
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TAB_SWITCHER_TEXT_SIZE
|
||||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWELVE_DP
|
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWELVE_DP
|
||||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWENTY_DP
|
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWENTY_DP
|
||||||
@ -105,7 +105,7 @@ class ReaderMenuState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun showWebViewOptions(valid: Boolean) {
|
fun showWebViewOptions(valid: Boolean) {
|
||||||
isInTabSwitcher = false
|
hideTabSwitcher()
|
||||||
urlIsValid = valid
|
urlIsValid = valid
|
||||||
setVisibility(
|
setVisibility(
|
||||||
urlIsValid,
|
urlIsValid,
|
||||||
@ -165,6 +165,10 @@ class ReaderMenuState(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hideTabSwitcher() {
|
||||||
|
isInTabSwitcher = false
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateMenuItems() {
|
private fun updateMenuItems() {
|
||||||
menuItems.clear()
|
menuItems.clear()
|
||||||
addSearchMenuItem()
|
addSearchMenuItem()
|
||||||
@ -185,15 +189,12 @@ class ReaderMenuState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addTabMenuItem() {
|
private fun addTabMenuItem() {
|
||||||
if (!disableTabs && urlIsValid) {
|
if (!disableTabs && urlIsValid && webViewCount > 0) {
|
||||||
val tabLabel = if (webViewCount > 99) ":D" else "$webViewCount"
|
val tabLabel = if (webViewCount > 99) ":D" else "$webViewCount"
|
||||||
menuItems += ActionMenuItem(
|
menuItems += ActionMenuItem(
|
||||||
icon = null,
|
icon = null,
|
||||||
contentDescription = R.string.switch_tabs,
|
contentDescription = R.string.switch_tabs,
|
||||||
onClick = {
|
onClick = { menuClickListener.onTabMenuClicked() },
|
||||||
isInTabSwitcher = true
|
|
||||||
menuClickListener.onTabMenuClicked()
|
|
||||||
},
|
|
||||||
isInOverflow = false,
|
isInOverflow = false,
|
||||||
iconButtonText = tabLabel,
|
iconButtonText = tabLabel,
|
||||||
testingTag = TAB_MENU_ITEM_TESTING_TAG,
|
testingTag = TAB_MENU_ITEM_TESTING_TAG,
|
||||||
@ -212,9 +213,9 @@ class ReaderMenuState(
|
|||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.clip(RoundedCornerShape(TAB_SWITCHER_CORNER_RADIUS))
|
.clip(RoundedCornerShape(TAB_SWITCHER_ICON_CORNER_RADIUS))
|
||||||
.background(Black)
|
.background(Black)
|
||||||
.border(ONE_DP, White, RoundedCornerShape(TAB_SWITCHER_CORNER_RADIUS))
|
.border(ONE_DP, White, RoundedCornerShape(TAB_SWITCHER_ICON_CORNER_RADIUS))
|
||||||
.padding(horizontal = SIX_DP, vertical = TWO_DP)
|
.padding(horizontal = SIX_DP, vertical = TWO_DP)
|
||||||
.defaultMinSize(minWidth = TWENTY_DP, minHeight = TWENTY_DP),
|
.defaultMinSize(minWidth = TWENTY_DP, minHeight = TWENTY_DP),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
@ -232,9 +233,7 @@ class ReaderMenuState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addReaderMenuItems() {
|
private fun addReaderMenuItems() {
|
||||||
if (!urlIsValid) return
|
if (menuItemVisibility[MenuItemType.AddNote] == true) {
|
||||||
|
|
||||||
if (menuItemVisibility[MenuItemType.Search] == true) {
|
|
||||||
menuItems += ActionMenuItem(
|
menuItems += ActionMenuItem(
|
||||||
icon = IconItem.Drawable(R.drawable.ic_add_note),
|
icon = IconItem.Drawable(R.drawable.ic_add_note),
|
||||||
contentDescription = R.string.take_notes,
|
contentDescription = R.string.take_notes,
|
||||||
|
@ -18,8 +18,12 @@
|
|||||||
|
|
||||||
package org.kiwix.kiwixmobile.core.main.reader
|
package org.kiwix.kiwixmobile.core.main.reader
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
@ -30,7 +34,6 @@ 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.aspectRatio
|
|
||||||
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
|
||||||
@ -38,10 +41,10 @@ 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
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.widthIn
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
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.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.BottomAppBar
|
import androidx.compose.material3.BottomAppBar
|
||||||
@ -58,19 +61,24 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
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.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
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.key
|
import androidx.compose.runtime.key
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.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.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalWindowInfo
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@ -80,7 +88,9 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
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 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.ZERO
|
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
|
||||||
import org.kiwix.kiwixmobile.core.main.DarkModeViewPainter
|
import org.kiwix.kiwixmobile.core.main.DarkModeViewPainter
|
||||||
import org.kiwix.kiwixmobile.core.main.KiwixWebView
|
import org.kiwix.kiwixmobile.core.main.KiwixWebView
|
||||||
@ -99,6 +109,8 @@ import org.kiwix.kiwixmobile.core.ui.theme.Black
|
|||||||
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.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_SIZE
|
||||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
|
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
|
||||||
@ -127,11 +139,11 @@ fun ReaderScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
snackbarHost = { KiwixSnackbarHost(snackbarHostState = state.snackBarHostState) },
|
snackbarHost = { KiwixSnackbarHost(snackbarHostState = state.snackBarHostState) },
|
||||||
topBar = {
|
topBar = {
|
||||||
KiwixAppBar(
|
ReaderTopBar(
|
||||||
state.readerScreenTitle,
|
state,
|
||||||
navigationIcon,
|
|
||||||
actionMenuItems,
|
actionMenuItems,
|
||||||
scrollBehavior
|
scrollBehavior,
|
||||||
|
navigationIcon
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = { BackToTopFab(state) },
|
floatingActionButton = { BackToTopFab(state) },
|
||||||
@ -140,34 +152,63 @@ fun ReaderScreen(
|
|||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
.padding(bottom = bottomNavHeight.value)
|
.padding(bottom = bottomNavHeight.value)
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Box(
|
ReaderContentLayout(state, Modifier.padding(paddingValues))
|
||||||
modifier = Modifier
|
}
|
||||||
.fillMaxSize()
|
}
|
||||||
.padding(paddingValues)
|
}
|
||||||
) {
|
|
||||||
if (state.isNoBookOpenInReader) {
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
NoBookOpenView(state.onOpenLibraryButtonClicked)
|
@Suppress("ComposableLambdaParameterNaming")
|
||||||
} else {
|
@Composable
|
||||||
ShowZIMFileContent(state)
|
private fun ReaderTopBar(
|
||||||
ShowProgressBarIfZIMFilePageIsLoading(state)
|
state: ReaderScreenState,
|
||||||
Column(
|
actionMenuItems: List<ActionMenuItem>,
|
||||||
modifier = Modifier.align(Alignment.BottomCenter)
|
scrollBehavior: TopAppBarScrollBehavior,
|
||||||
) {
|
navigationIcon: @Composable () -> Unit,
|
||||||
TtsControls(state)
|
) {
|
||||||
BottomAppBarOfReaderScreen(
|
if (!state.fullScreenItem.first) {
|
||||||
state.bookmarkButtonItem,
|
KiwixAppBar(
|
||||||
state.previousPageButtonItem,
|
title = if (state.showTabSwitcher) "" else state.readerScreenTitle,
|
||||||
state.onHomeButtonClick,
|
navigationIcon = navigationIcon,
|
||||||
state.nextPageButtonItem,
|
actionMenuItems = actionMenuItems,
|
||||||
state.onTocClick,
|
topAppBarScrollBehavior = scrollBehavior
|
||||||
state.shouldShowBottomAppBar
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
ShowFullScreenView(state)
|
|
||||||
|
@Composable
|
||||||
|
private fun ReaderContentLayout(state: ReaderScreenState, modifier: Modifier = Modifier) {
|
||||||
|
Box(modifier = modifier.fillMaxSize()) {
|
||||||
|
when {
|
||||||
|
state.showTabSwitcher -> TabSwitcherView(
|
||||||
|
state.kiwixWebViewList,
|
||||||
|
state.currentWebViewPosition,
|
||||||
|
state.onTabClickListener,
|
||||||
|
state.onCloseAllTabs,
|
||||||
|
state.darkModeViewPainter
|
||||||
|
)
|
||||||
|
|
||||||
|
state.isNoBookOpenInReader -> NoBookOpenView(state.onOpenLibraryButtonClicked)
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
ShowZIMFileContent(state)
|
||||||
|
ShowProgressBarIfZIMFilePageIsLoading(state)
|
||||||
|
Column(Modifier.align(Alignment.BottomCenter)) {
|
||||||
|
TtsControls(state)
|
||||||
|
BottomAppBarOfReaderScreen(
|
||||||
|
state.bookmarkButtonItem,
|
||||||
|
state.previousPageButtonItem,
|
||||||
|
state.onHomeButtonClick,
|
||||||
|
state.nextPageButtonItem,
|
||||||
|
state.onTocClick,
|
||||||
|
state.shouldShowBottomAppBar
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ShowDonationLayout(state)
|
ShowFullScreenView(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShowDonationLayout(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,11 +419,11 @@ private fun BoxScope.ShowDonationLayout(state: ReaderScreenState) {
|
|||||||
fun TabSwitcherView(
|
fun TabSwitcherView(
|
||||||
webViews: List<KiwixWebView>,
|
webViews: List<KiwixWebView>,
|
||||||
selectedIndex: Int,
|
selectedIndex: Int,
|
||||||
onSelectTab: (Int) -> Unit,
|
onTabClickListener: TabClickListener,
|
||||||
onCloseTab: (Int) -> Unit,
|
|
||||||
onCloseAllTabs: () -> Unit,
|
onCloseAllTabs: () -> Unit,
|
||||||
painter: DarkModeViewPainter
|
painter: DarkModeViewPainter?
|
||||||
) {
|
) {
|
||||||
|
val state = rememberLazyListState()
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
LazyRow(
|
LazyRow(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -390,7 +431,8 @@ fun TabSwitcherView(
|
|||||||
.align(Alignment.TopCenter)
|
.align(Alignment.TopCenter)
|
||||||
.padding(top = SIXTEEN_DP),
|
.padding(top = SIXTEEN_DP),
|
||||||
contentPadding = PaddingValues(horizontal = SIXTEEN_DP, vertical = EIGHT_DP),
|
contentPadding = PaddingValues(horizontal = SIXTEEN_DP, vertical = EIGHT_DP),
|
||||||
horizontalArrangement = Arrangement.spacedBy(EIGHT_DP)
|
horizontalArrangement = Arrangement.spacedBy(EIGHT_DP),
|
||||||
|
state = state
|
||||||
) {
|
) {
|
||||||
itemsIndexed(webViews, key = { _, item -> item.hashCode() }) { index, webView ->
|
itemsIndexed(webViews, key = { _, item -> item.hashCode() }) { index, webView ->
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@ -401,33 +443,76 @@ fun TabSwitcherView(
|
|||||||
|
|
||||||
LaunchedEffect(webView) {
|
LaunchedEffect(webView) {
|
||||||
if (title != context.getString(R.string.menu_home)) {
|
if (title != context.getString(R.string.menu_home)) {
|
||||||
painter.update(webView)
|
painter?.update(webView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TabItemView(
|
TabItemView(
|
||||||
|
index = index,
|
||||||
title = title,
|
title = title,
|
||||||
isSelected = index == selectedIndex,
|
isSelected = index == selectedIndex,
|
||||||
webView = webView,
|
webView = webView,
|
||||||
onSelectTab = { onSelectTab(index) },
|
onTabClickListener = onTabClickListener,
|
||||||
onCloseTab = { onCloseTab(index) }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
state.animateScrollToItem(selectedIndex)
|
||||||
|
}
|
||||||
CloseAllTabButton(onCloseAllTabs)
|
CloseAllTabButton(onCloseAllTabs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BoxScope.CloseAllTabButton(onCloseAllTabs: () -> Unit) {
|
private fun BoxScope.CloseAllTabButton(onCloseAllTabs: () -> Unit) {
|
||||||
|
var isAnimating by remember { mutableStateOf(false) }
|
||||||
|
var isDone by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// Animate rotation from 0f to 360f
|
||||||
|
val rotation by animateFloatAsState(
|
||||||
|
targetValue = if (isAnimating) 360f else 0f,
|
||||||
|
animationSpec = tween(durationMillis = 600),
|
||||||
|
finishedListener = {
|
||||||
|
isDone = true
|
||||||
|
isAnimating = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ⏳ Auto-reset to close icon after delay
|
||||||
|
LaunchedEffect(isDone) {
|
||||||
|
if (isDone) {
|
||||||
|
delay(CLOSE_TAB_ICON_ANIMATION_TIMEOUT)
|
||||||
|
isDone = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = onCloseAllTabs,
|
onClick = {
|
||||||
|
isAnimating = true
|
||||||
|
onCloseAllTabs()
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomCenter)
|
.align(Alignment.BottomCenter)
|
||||||
.padding(bottom = CLOSE_ALL_TAB_BUTTON_BOTTOM_PADDING)
|
.padding(bottom = CLOSE_ALL_TAB_BUTTON_BOTTOM_PADDING)
|
||||||
|
.graphicsLayer {
|
||||||
|
rotationZ = rotation
|
||||||
|
}
|
||||||
|
.clickable(
|
||||||
|
enabled = !isAnimating,
|
||||||
|
onClick = {
|
||||||
|
isAnimating = true
|
||||||
|
onCloseAllTabs()
|
||||||
|
}
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_close_black_24dp),
|
painter = painterResource(
|
||||||
|
id = if (isDone) {
|
||||||
|
R.drawable.ic_done_white_24dp
|
||||||
|
} else {
|
||||||
|
R.drawable.ic_close_black_24dp
|
||||||
|
}
|
||||||
|
),
|
||||||
contentDescription = stringResource(R.string.close_all_tabs)
|
contentDescription = stringResource(R.string.close_all_tabs)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -436,71 +521,120 @@ private fun BoxScope.CloseAllTabButton(onCloseAllTabs: () -> Unit) {
|
|||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
@Composable
|
@Composable
|
||||||
fun TabItemView(
|
fun TabItemView(
|
||||||
|
index: Int,
|
||||||
title: String,
|
title: String,
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
webView: KiwixWebView,
|
webView: KiwixWebView,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onSelectTab: () -> Unit,
|
onTabClickListener: TabClickListener
|
||||||
onCloseTab: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
val cardElevation = if (isSelected) EIGHT_DP else TWO_DP
|
val cardElevation = if (isSelected) EIGHT_DP else TWO_DP
|
||||||
val borderColor = if (isSelected) MaterialTheme.colorScheme.primary else Color.Transparent
|
val borderColor = if (isSelected) MaterialTheme.colorScheme.primary else Color.Transparent
|
||||||
|
val (cardWidth, cardHeight) = getTabCardSize(toolbarHeightDp = 56.dp)
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = EIGHT_DP, vertical = FOUR_DP)
|
.padding(horizontal = EIGHT_DP, vertical = FOUR_DP)
|
||||||
.widthIn(min = 200.dp)
|
.width(cardWidth)
|
||||||
) {
|
) {
|
||||||
Row(
|
TabItemHeader(title, index, onTabClickListener)
|
||||||
modifier = Modifier
|
TabItemCard(
|
||||||
.fillMaxWidth()
|
webView,
|
||||||
.padding(horizontal = FOUR_DP),
|
cardWidth,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
cardHeight,
|
||||||
) {
|
onTabClickListener,
|
||||||
Text(
|
borderColor,
|
||||||
text = title,
|
cardElevation,
|
||||||
maxLines = 1,
|
index
|
||||||
overflow = TextOverflow.Ellipsis,
|
)
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.padding(end = EIGHT_DP),
|
|
||||||
style = MaterialTheme.typography.labelLarge
|
|
||||||
)
|
|
||||||
IconButton(onClick = onCloseTab) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.ic_clear_white_24dp),
|
|
||||||
contentDescription = stringResource(R.string.close_tab)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Card with WebView (non-interactive with overlay)
|
|
||||||
Card(
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation),
|
|
||||||
border = BorderStroke(ONE_DP, borderColor),
|
|
||||||
shape = MaterialTheme.shapes.medium,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.aspectRatio(1.6f) // approximate height logic
|
|
||||||
.clickable { onSelectTab() }
|
|
||||||
) {
|
|
||||||
AndroidView(
|
|
||||||
factory = { context ->
|
|
||||||
// Detach if needed to avoid WebView already has a parent issue
|
|
||||||
(webView.parent as? ViewGroup)?.removeView(webView)
|
|
||||||
FrameLayout(context).apply {
|
|
||||||
addView(webView)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TabItemHeader(
|
||||||
|
title: String,
|
||||||
|
index: Int,
|
||||||
|
onTabClickListener: TabClickListener
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = FOUR_DP),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = FOUR_DP)
|
||||||
|
.weight(1f),
|
||||||
|
style = MaterialTheme.typography.labelSmall
|
||||||
|
)
|
||||||
|
IconButton(
|
||||||
|
onClick = { onTabClickListener.onCloseTab(index) },
|
||||||
|
modifier = Modifier.size(CLOSE_TAB_ICON_SIZE)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_clear_white_24dp),
|
||||||
|
contentDescription = stringResource(R.string.close_tab)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TabItemCard(
|
||||||
|
webView: KiwixWebView,
|
||||||
|
cardWidth: Dp,
|
||||||
|
cardHeight: Dp,
|
||||||
|
onTabClickListener: TabClickListener,
|
||||||
|
borderColor: Color,
|
||||||
|
elevation: Dp,
|
||||||
|
index: Int
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = elevation),
|
||||||
|
border = BorderStroke(ONE_DP, borderColor),
|
||||||
|
shape = MaterialTheme.shapes.extraSmall,
|
||||||
|
modifier = Modifier
|
||||||
|
.width(cardWidth)
|
||||||
|
.height(cardHeight)
|
||||||
|
.clickable { onTabClickListener.onSelectTab(index) }
|
||||||
|
) {
|
||||||
|
AndroidView(
|
||||||
|
factory = { context ->
|
||||||
|
FrameLayout(context).apply {
|
||||||
|
(webView.parent as? ViewGroup)?.removeView(webView)
|
||||||
|
addView(webView)
|
||||||
|
val clickableView = View(context).apply {
|
||||||
|
layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
||||||
|
setOnClickListener { onTabClickListener.onSelectTab(index) }
|
||||||
|
}
|
||||||
|
addView(clickableView)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getTabCardSize(toolbarHeightDp: Dp): Pair<Dp, Dp> {
|
||||||
|
val windowSize = LocalWindowInfo.current.containerSize
|
||||||
|
val density = LocalDensity.current
|
||||||
|
|
||||||
|
val screenWidth = with(density) { windowSize.width.toDp() }
|
||||||
|
val screenHeight = with(density) { windowSize.height.toDp() }
|
||||||
|
|
||||||
|
val cardWidth = screenWidth / 2
|
||||||
|
val cardHeight = ((screenHeight - toolbarHeightDp) / 2).coerceAtLeast(HUNDERED.dp)
|
||||||
|
|
||||||
|
return cardWidth to cardHeight
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberScrollBehavior(
|
fun rememberScrollBehavior(
|
||||||
bottomNavigationHeight: Int,
|
bottomNavigationHeight: Int,
|
||||||
@ -527,3 +661,8 @@ fun rememberScrollBehavior(
|
|||||||
|
|
||||||
return bottomNavHeight to lazyListState
|
return bottomNavHeight to lazyListState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TabClickListener {
|
||||||
|
fun onSelectTab(position: Int)
|
||||||
|
fun onCloseTab(position: Int)
|
||||||
|
}
|
||||||
|
@ -20,6 +20,7 @@ package org.kiwix.kiwixmobile.core.main.reader
|
|||||||
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.ui.platform.ComposeView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
|
import org.kiwix.kiwixmobile.core.main.DarkModeViewPainter
|
||||||
import org.kiwix.kiwixmobile.core.main.KiwixWebView
|
import org.kiwix.kiwixmobile.core.main.KiwixWebView
|
||||||
import org.kiwix.kiwixmobile.core.ui.models.IconItem.Drawable
|
import org.kiwix.kiwixmobile.core.ui.models.IconItem.Drawable
|
||||||
|
|
||||||
@ -79,10 +80,18 @@ data class ReaderScreenState(
|
|||||||
*/
|
*/
|
||||||
val pauseTtsButtonText: String,
|
val pauseTtsButtonText: String,
|
||||||
val onStopTtsClick: () -> Unit = {},
|
val onStopTtsClick: () -> Unit = {},
|
||||||
|
/**
|
||||||
|
* Holds the current selected webView position.
|
||||||
|
*/
|
||||||
|
val currentWebViewPosition: Int,
|
||||||
/**
|
/**
|
||||||
* To show in the tabs view.
|
* To show in the tabs view.
|
||||||
*/
|
*/
|
||||||
val kiwixWebViewList: List<KiwixWebView>,
|
val kiwixWebViewList: List<KiwixWebView>,
|
||||||
|
/**
|
||||||
|
* To show/hide tab switcher.
|
||||||
|
*/
|
||||||
|
val showTabSwitcher: Boolean,
|
||||||
/**
|
/**
|
||||||
* Manages the showing of current selected webView.
|
* Manages the showing of current selected webView.
|
||||||
*/
|
*/
|
||||||
@ -131,5 +140,10 @@ data class ReaderScreenState(
|
|||||||
* Manages the showing of Reader's [BottomAppBarOfReaderScreen].
|
* Manages the showing of Reader's [BottomAppBarOfReaderScreen].
|
||||||
*/
|
*/
|
||||||
val shouldShowBottomAppBar: Boolean,
|
val shouldShowBottomAppBar: Boolean,
|
||||||
val readerScreenTitle: String
|
val readerScreenTitle: String,
|
||||||
|
val darkModeViewPainter: DarkModeViewPainter?,
|
||||||
|
/**
|
||||||
|
* Manages the click event on tabs.
|
||||||
|
*/
|
||||||
|
val onTabClickListener: TabClickListener,
|
||||||
)
|
)
|
||||||
|
@ -180,11 +180,13 @@ object ComposeDimens {
|
|||||||
val STORAGE_LOADING_PROGRESS_BAR_SIZE = 40.dp
|
val STORAGE_LOADING_PROGRESS_BAR_SIZE = 40.dp
|
||||||
val CATEGORY_TITLE_TEXT_SIZE = 14.sp
|
val CATEGORY_TITLE_TEXT_SIZE = 14.sp
|
||||||
|
|
||||||
// Reader screen dimes
|
// Reader screen dimens
|
||||||
val READER_BOTTOM_APP_BAR_LAYOUT_HEIGHT = 40.dp
|
val READER_BOTTOM_APP_BAR_LAYOUT_HEIGHT = 40.dp
|
||||||
val READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE = 30.dp
|
val READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE = 30.dp
|
||||||
const val TTS_BUTTONS_CONTROL_ALPHA = 0.6f
|
const val TTS_BUTTONS_CONTROL_ALPHA = 0.6f
|
||||||
val CLOSE_ALL_TAB_BUTTON_BOTTOM_PADDING = 24.dp
|
val CLOSE_ALL_TAB_BUTTON_BOTTOM_PADDING = 24.dp
|
||||||
val TAB_SWITCHER_TEXT_SIZE = 12.sp
|
val TAB_SWITCHER_TEXT_SIZE = 12.sp
|
||||||
const val TAB_SWITCHER_CORNER_RADIUS = 10
|
const val TAB_SWITCHER_ICON_CORNER_RADIUS = 10
|
||||||
|
val CLOSE_TAB_ICON_SIZE = 20.dp
|
||||||
|
const val CLOSE_TAB_ICON_ANIMATION_TIMEOUT = 1200L
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user