mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-22 12:03:09 -04:00
Fixed: WebView was not loading content properly in Compose.
* Refactored KiwixAppBar to support a string title instead of a string resource ID, allowing dynamic titles like ZIM file names. * Fixed: ZIM file title was not appearing correctly in the toolbar. * Refactored all snackbar-related functionalities on the reader screen. * Updated KiwixAppBar to support an overflow menu. * Introduced ReaderMenuState to manage menu item state dynamically for improved maintainability.
This commit is contained in:
parent
6ea560407d
commit
5c879484a6
@ -36,6 +36,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.extensions.CollectSideEffectWithActivity
|
||||
@ -70,7 +71,7 @@ fun LanguageScreen(
|
||||
}
|
||||
Scaffold(topBar = {
|
||||
KiwixAppBar(
|
||||
titleId = R.string.select_languages,
|
||||
title = stringResource(R.string.select_languages),
|
||||
navigationIcon = navigationIcon,
|
||||
actionMenuItems = actionMenuItemList,
|
||||
searchBar = if (isSearchActive) {
|
||||
|
@ -113,7 +113,7 @@ fun LocalFileTransferScreen(
|
||||
Scaffold(
|
||||
topBar = {
|
||||
KiwixAppBar(
|
||||
titleId = toolbarTitle,
|
||||
title = stringResource(toolbarTitle),
|
||||
actionMenuItems = actionMenuItems.map {
|
||||
it.copy(
|
||||
modifier =
|
||||
|
@ -233,7 +233,7 @@ class KiwixMainActivity : CoreMainActivity() {
|
||||
*
|
||||
* TODO Remove this once we migrate to compose.
|
||||
*/
|
||||
fun toggleBottomNavigation(isVisible: Boolean) {
|
||||
override fun toggleBottomNavigation(isVisible: Boolean) {
|
||||
activityKiwixMainBinding.bottomNavView.animate()
|
||||
?.translationY(
|
||||
if (isVisible) {
|
||||
|
@ -38,32 +38,24 @@ import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.kiwix.kiwixmobile.R.string
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
|
||||
import org.kiwix.kiwixmobile.core.main.reader.CONTENT_LOADING_PROGRESSBAR_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.main.reader.rememberScrollBehavior
|
||||
import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar
|
||||
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
|
||||
import org.kiwix.kiwixmobile.core.ui.components.KiwixButton
|
||||
import org.kiwix.kiwixmobile.core.ui.components.KiwixSnackbarHost
|
||||
import org.kiwix.kiwixmobile.core.ui.components.ProgressBarStyle
|
||||
import org.kiwix.kiwixmobile.core.ui.components.ScrollDirection
|
||||
import org.kiwix.kiwixmobile.core.ui.components.SwipeRefreshLayout
|
||||
import org.kiwix.kiwixmobile.core.ui.components.rememberLazyListScrollListener
|
||||
import org.kiwix.kiwixmobile.core.ui.theme.Black
|
||||
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
|
||||
import org.kiwix.kiwixmobile.core.ui.theme.White
|
||||
@ -102,7 +94,12 @@ fun LocalLibraryScreen(
|
||||
Scaffold(
|
||||
snackbarHost = { KiwixSnackbarHost(snackbarHostState = state.snackBarHostState) },
|
||||
topBar = {
|
||||
KiwixAppBar(R.string.library, navigationIcon, state.actionMenuItems, scrollBehavior)
|
||||
KiwixAppBar(
|
||||
stringResource(R.string.library),
|
||||
navigationIcon,
|
||||
state.actionMenuItems,
|
||||
scrollBehavior
|
||||
)
|
||||
},
|
||||
floatingActionButton = { SelectFileButton(fabButtonClick) },
|
||||
modifier = Modifier
|
||||
@ -141,33 +138,6 @@ fun LocalLibraryScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberScrollBehavior(
|
||||
bottomNavigationHeight: Int,
|
||||
listState: LazyListState,
|
||||
): Pair<MutableState<Dp>, LazyListState> {
|
||||
val bottomNavHeightInDp = with(LocalDensity.current) { bottomNavigationHeight.toDp() }
|
||||
val bottomNavHeight = remember { mutableStateOf(bottomNavHeightInDp) }
|
||||
val lazyListState = rememberLazyListScrollListener(
|
||||
lazyListState = listState,
|
||||
onScrollChanged = { direction ->
|
||||
when (direction) {
|
||||
ScrollDirection.SCROLL_UP -> {
|
||||
bottomNavHeight.value = bottomNavHeightInDp
|
||||
}
|
||||
|
||||
ScrollDirection.SCROLL_DOWN -> {
|
||||
bottomNavHeight.value = ZERO.dp
|
||||
}
|
||||
|
||||
ScrollDirection.IDLE -> {}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return bottomNavHeight to lazyListState
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BookItemList(
|
||||
state: FileSelectListState,
|
||||
|
@ -53,6 +53,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import org.kiwix.kiwixmobile.core.R.string
|
||||
import org.kiwix.kiwixmobile.core.extensions.hideKeyboardOnLazyColumnScroll
|
||||
import org.kiwix.kiwixmobile.core.main.reader.rememberScrollBehavior
|
||||
import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar
|
||||
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
|
||||
import org.kiwix.kiwixmobile.core.ui.components.KiwixSearchView
|
||||
@ -72,7 +73,6 @@ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOUR_DP
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIX_DP
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.THREE_DP
|
||||
import org.kiwix.kiwixmobile.nav.destination.library.local.rememberScrollBehavior
|
||||
import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryListItem
|
||||
import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryListItem.DividerItem
|
||||
|
||||
@ -100,7 +100,7 @@ fun OnlineLibraryScreen(
|
||||
snackbarHost = { KiwixSnackbarHost(snackbarHostState = state.snackBarHostState) },
|
||||
topBar = {
|
||||
KiwixAppBar(
|
||||
string.download,
|
||||
stringResource(string.download),
|
||||
navigationIcon,
|
||||
actionMenuItems,
|
||||
scrollBehavior,
|
||||
|
@ -27,11 +27,11 @@ import android.view.MenuInflater
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import kotlinx.coroutines.launch
|
||||
import org.kiwix.kiwixmobile.R
|
||||
@ -51,12 +51,12 @@ import org.kiwix.kiwixmobile.core.extensions.snack
|
||||
import org.kiwix.kiwixmobile.core.extensions.toast
|
||||
import org.kiwix.kiwixmobile.core.extensions.update
|
||||
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
|
||||
import org.kiwix.kiwixmobile.core.main.reader.CoreReaderFragment
|
||||
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.RestoreOrigin
|
||||
import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin.FromExternalLaunch
|
||||
import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin.FromSearchScreen
|
||||
import org.kiwix.kiwixmobile.core.main.ToolbarScrollingKiwixWebView
|
||||
import org.kiwix.kiwixmobile.core.page.history.adapter.WebViewHistoryItem
|
||||
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
|
||||
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource.Companion.fromDatabaseValue
|
||||
@ -253,6 +253,9 @@ class KiwixReaderFragment : CoreReaderFragment() {
|
||||
exitBook()
|
||||
}
|
||||
|
||||
override fun getBottomNavigationView(): BottomNavigationView? =
|
||||
requireActivity().findViewById(R.id.bottom_nav_view)
|
||||
|
||||
/**
|
||||
* Restores the view state based on the provided webViewHistoryItemList data and restore origin.
|
||||
*
|
||||
@ -291,7 +294,10 @@ class KiwixReaderFragment : CoreReaderFragment() {
|
||||
}
|
||||
restoreTabs(webViewHistoryItemList, currentTab, onComplete)
|
||||
} else {
|
||||
getCurrentWebView()?.snack(string.zim_not_opened)
|
||||
readerScreenState.value.snackBarHostState.snack(
|
||||
requireActivity().getString(string.zim_not_opened),
|
||||
lifecycleScope = lifecycleScope
|
||||
)
|
||||
exitBook() // hide the options for zim file to avoid unexpected UI behavior
|
||||
}
|
||||
}
|
||||
@ -305,16 +311,17 @@ class KiwixReaderFragment : CoreReaderFragment() {
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
override fun createWebView(attrs: AttributeSet?): ToolbarScrollingKiwixWebView? {
|
||||
requireNotNull(activityMainRoot)
|
||||
// requireNotNull(activityMainRoot)
|
||||
return ToolbarScrollingKiwixWebView(
|
||||
requireContext(),
|
||||
this,
|
||||
attrs ?: throw IllegalArgumentException("AttributeSet must not be null"),
|
||||
activityMainRoot as ViewGroup,
|
||||
requireNotNull(videoView),
|
||||
null,
|
||||
// requireNotNull(videoView),
|
||||
null,
|
||||
CoreWebViewClient(this, requireNotNull(zimReaderContainer)),
|
||||
requireNotNull(toolbarContainer),
|
||||
requireNotNull(bottomToolbar),
|
||||
// requireNotNull(toolbarContainer),
|
||||
// requireNotNull(bottomToolbar),
|
||||
sharedPreferenceUtil = requireNotNull(sharedPreferenceUtil),
|
||||
parentNavigationBar = requireActivity().findViewById(R.id.bottom_nav_view)
|
||||
)
|
||||
|
@ -87,7 +87,7 @@ fun ZimHostScreen(
|
||||
) {
|
||||
KiwixTheme {
|
||||
Scaffold(topBar = {
|
||||
KiwixAppBar(R.string.menu_wifi_hotspot, navigationIcon)
|
||||
KiwixAppBar(stringResource(R.string.menu_wifi_hotspot), navigationIcon)
|
||||
}) { contentPadding ->
|
||||
Column(modifier = Modifier.fillMaxSize().padding(contentPadding)) {
|
||||
Row(
|
||||
|
@ -30,7 +30,8 @@ fun SnackbarHostState.snack(
|
||||
actionClick: (() -> Unit)? = null,
|
||||
// Default duration is 4 seconds.
|
||||
snackbarDuration: SnackbarDuration = SnackbarDuration.Short,
|
||||
lifecycleScope: CoroutineScope
|
||||
lifecycleScope: CoroutineScope,
|
||||
snackBarResult: (SnackbarResult) -> Unit = {}
|
||||
) {
|
||||
lifecycleScope.launch {
|
||||
val result = showSnackbar(
|
||||
@ -41,5 +42,6 @@ fun SnackbarHostState.snack(
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
actionClick?.invoke()
|
||||
}
|
||||
snackBarResult.invoke(result)
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ fun HelpScreen(
|
||||
KiwixTheme {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
KiwixAppBar(R.string.menu_help, navigationIcon)
|
||||
KiwixAppBar(stringResource(R.string.menu_help), navigationIcon)
|
||||
}
|
||||
) { innerPadding ->
|
||||
Column(modifier = Modifier.padding(innerPadding)) {
|
||||
|
@ -72,7 +72,7 @@ fun AddNoteDialogScreen(
|
||||
KiwixDialogTheme {
|
||||
Scaffold(
|
||||
snackbarHost = { KiwixSnackbarHost(snackbarHostState = snackBarHostState) },
|
||||
topBar = { KiwixAppBar(R.string.note, navigationIcon, actionMenuItems) }
|
||||
topBar = { KiwixAppBar(stringResource(R.string.note), navigationIcon, actionMenuItems) }
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
@ -495,4 +495,14 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
|
||||
abstract val readerFragmentResId: Int
|
||||
abstract fun createApplicationShortcuts()
|
||||
abstract fun setDialogHostToActivity(alertDialogShower: AlertDialogShower)
|
||||
|
||||
/**
|
||||
* This is for showing and hiding the bottomNavigationView when user scroll the screen.
|
||||
* We are making this abstract so that it can be easily used from the reader screen.
|
||||
* Since we do not have the bottomNavigationView in custom apps. So doing this way both apps will
|
||||
* provide there own implementation.
|
||||
*
|
||||
* TODO we will remove this once we will migrate mainActivity to the compose.
|
||||
*/
|
||||
abstract fun toggleBottomNavigation(isVisible: Boolean)
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ open class KiwixWebView @SuppressLint("SetJavaScriptEnabled") constructor(
|
||||
private val callback: WebViewCallback,
|
||||
attrs: AttributeSet,
|
||||
private var nonVideoView: ViewGroup?,
|
||||
videoView: ViewGroup,
|
||||
videoView: ViewGroup?,
|
||||
private val webViewClient: CoreWebViewClient,
|
||||
val sharedPreferenceUtil: SharedPreferenceUtil
|
||||
) : VideoEnabledWebView(context, attrs) {
|
||||
|
@ -25,19 +25,16 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import org.kiwix.kiwixmobile.core.utils.DimenUtils.getToolbarHeight
|
||||
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,
|
||||
nonVideoView: ViewGroup,
|
||||
videoView: ViewGroup,
|
||||
nonVideoView: ViewGroup?,
|
||||
videoView: ViewGroup?,
|
||||
webViewClient: CoreWebViewClient,
|
||||
private val toolbarView: View,
|
||||
private val bottomBarView: View,
|
||||
sharedPreferenceUtil: SharedPreferenceUtil,
|
||||
private val parentNavigationBar: View? = null
|
||||
) : KiwixWebView(
|
||||
@ -65,58 +62,60 @@ class ToolbarScrollingKiwixWebView @JvmOverloads constructor(
|
||||
moveToolbar(0)
|
||||
}
|
||||
|
||||
@Suppress("FunctionOnlyReturningConstant", "UnusedParameter")
|
||||
private fun moveToolbar(scrollDelta: Int): Boolean {
|
||||
val originalTranslation = toolbarView.translationY
|
||||
val newTranslation =
|
||||
if (scrollDelta > 0) {
|
||||
// scroll down
|
||||
max(-toolbarHeight.toFloat(), originalTranslation - scrollDelta)
|
||||
} else {
|
||||
// scroll up
|
||||
min(0f, originalTranslation - scrollDelta)
|
||||
}
|
||||
|
||||
toolbarView.translationY = newTranslation
|
||||
bottomBarView.translationY =
|
||||
newTranslation * -1 * (bottomBarView.height / toolbarHeight.toFloat())
|
||||
parentNavigationBar?.let {
|
||||
it.translationY = newTranslation * -1 * (it.height / toolbarHeight.toFloat())
|
||||
}
|
||||
this.translationY = newTranslation + toolbarHeight
|
||||
return toolbarHeight + newTranslation != 0f && newTranslation != 0f
|
||||
// val originalTranslation = toolbarView.translationY
|
||||
// val newTranslation =
|
||||
// if (scrollDelta > 0) {
|
||||
// // scroll down
|
||||
// max(-toolbarHeight.toFloat(), originalTranslation - scrollDelta)
|
||||
// } else {
|
||||
// // scroll up
|
||||
// min(0f, originalTranslation - scrollDelta)
|
||||
// }
|
||||
//
|
||||
// toolbarView.translationY = newTranslation
|
||||
// bottomBarView.translationY =
|
||||
// newTranslation * -1 * (bottomBarView.height / toolbarHeight.toFloat())
|
||||
// parentNavigationBar?.let {
|
||||
// it.translationY = newTranslation * -1 * (it.height / toolbarHeight.toFloat())
|
||||
// }
|
||||
// this.translationY = newTranslation + toolbarHeight
|
||||
// return toolbarHeight + newTranslation != 0f && newTranslation != 0f
|
||||
return false
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
val transY = toolbarView.translationY.toInt()
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> startY = event.rawY
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
// If we are in fullscreen don't scroll bar
|
||||
if (sharedPreferenceUtil.prefFullScreen) {
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
// Filter out zooms since we don't want to affect the toolbar when zooming
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the toolbar is half-visible,
|
||||
// either open or close it entirely depending on how far it is visible
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL ->
|
||||
if (transY != 0 && transY > -toolbarHeight) {
|
||||
if (transY > -toolbarHeight / 2) {
|
||||
ensureToolbarDisplayed()
|
||||
} else {
|
||||
ensureToolbarHidden()
|
||||
}
|
||||
}
|
||||
}
|
||||
// val transY = toolbarView.translationY.toInt()
|
||||
// when (event.actionMasked) {
|
||||
// MotionEvent.ACTION_DOWN -> startY = event.rawY
|
||||
// MotionEvent.ACTION_MOVE -> {
|
||||
// // If we are in fullscreen don't scroll bar
|
||||
// if (sharedPreferenceUtil.prefFullScreen) {
|
||||
// return super.onTouchEvent(event)
|
||||
// }
|
||||
// // Filter out zooms since we don't want to affect the toolbar when zooming
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // If the toolbar is half-visible,
|
||||
// // either open or close it entirely depending on how far it is visible
|
||||
// MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL ->
|
||||
// if (transY != 0 && transY > -toolbarHeight) {
|
||||
// if (transY > -toolbarHeight / 2) {
|
||||
// ensureToolbarDisplayed()
|
||||
// } else {
|
||||
// ensureToolbarHidden()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
|
||||
|
@ -44,8 +44,6 @@ import android.view.Gravity.BOTTOM
|
||||
import android.view.Gravity.CENTER_HORIZONTAL
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
@ -67,27 +65,28 @@ import androidx.annotation.AnimRes
|
||||
import androidx.appcompat.app.ActionBar
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.constraintlayout.widget.Group
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.MenuHost
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.ContentLoadingProgressBar
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
@ -96,9 +95,9 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
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.bottomnavigation.BottomNavigationView
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -151,7 +150,6 @@ import org.kiwix.kiwixmobile.core.main.KiwixTextToSpeech.OnInitSucceedListener
|
||||
import org.kiwix.kiwixmobile.core.main.KiwixTextToSpeech.OnSpeakingListener
|
||||
import org.kiwix.kiwixmobile.core.main.KiwixWebView
|
||||
import org.kiwix.kiwixmobile.core.main.MainMenu
|
||||
import org.kiwix.kiwixmobile.core.main.MainMenu.MenuClickListener
|
||||
import org.kiwix.kiwixmobile.core.main.MainRepositoryActions
|
||||
import org.kiwix.kiwixmobile.core.main.OnSwipeTouchListener
|
||||
import org.kiwix.kiwixmobile.core.main.ServiceWorkerUninitialiser
|
||||
@ -181,6 +179,7 @@ import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
|
||||
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
|
||||
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchItemToOpen
|
||||
import org.kiwix.kiwixmobile.core.ui.components.NavigationIcon
|
||||
import org.kiwix.kiwixmobile.core.ui.components.rememberBottomNavigationVisibility
|
||||
import org.kiwix.kiwixmobile.core.ui.models.IconItem
|
||||
import org.kiwix.kiwixmobile.core.utils.AnimationUtils.rotate
|
||||
import org.kiwix.kiwixmobile.core.utils.DimenUtils.getToolbarHeight
|
||||
@ -224,13 +223,13 @@ const val SEARCH_ITEM_TITLE_KEY = "searchItemTitle"
|
||||
abstract class CoreReaderFragment :
|
||||
BaseFragment(),
|
||||
WebViewCallback,
|
||||
MenuClickListener,
|
||||
ReaderMenuState.MenuClickListener,
|
||||
FragmentActivityExtensions,
|
||||
WebViewProvider,
|
||||
ReadAloudCallbacks,
|
||||
NavigationHistoryClickListener,
|
||||
ShowDonationDialogCallback {
|
||||
protected val webViewList: MutableList<KiwixWebView> = ArrayList()
|
||||
protected val webViewList = mutableStateListOf<KiwixWebView>()
|
||||
private val webUrlsFlow = MutableStateFlow("")
|
||||
private var fragmentReaderBinding: FragmentReaderBinding? = null
|
||||
|
||||
@ -317,8 +316,6 @@ abstract class CoreReaderFragment :
|
||||
|
||||
private var tabRecyclerView: RecyclerView? = null
|
||||
|
||||
private var snackBarRoot: CoordinatorLayout? = null
|
||||
|
||||
private var noOpenBookText: TextView? = null
|
||||
private var bottomToolbarToc: ImageView? = null
|
||||
|
||||
@ -395,11 +392,15 @@ abstract class CoreReaderFragment :
|
||||
{ goToBookmarks() },
|
||||
IconItem.Drawable(R.drawable.ic_bookmark_border_24dp)
|
||||
),
|
||||
previousPageButtonItem = { goBack() } to { showBackwardHistory() },
|
||||
previousPageButtonItem = Triple({ goBack() }, { showBackwardHistory() }, false),
|
||||
onHomeButtonClick = { openMainPage() },
|
||||
nextPageButtonItem = { goForward() } to { showForwardHistory() },
|
||||
nextPageButtonItem = Triple({ goForward() }, { showForwardHistory() }, false),
|
||||
onTocClick = { openToc() },
|
||||
onCloseAllTabs = { closeAllTabs() }
|
||||
onCloseAllTabs = { closeAllTabs() },
|
||||
bottomNavigationHeight = ZERO,
|
||||
shouldShowBottomAppBar = true,
|
||||
selectedWebView = null,
|
||||
readerScreenTitle = ""
|
||||
)
|
||||
)
|
||||
private var readerLifeCycleScope: CoroutineScope? = null
|
||||
@ -498,6 +499,19 @@ abstract class CoreReaderFragment :
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
composeView?.apply {
|
||||
setContent {
|
||||
val lazyListState = rememberLazyListState()
|
||||
val isBottomNavVisible = rememberBottomNavigationVisibility(lazyListState)
|
||||
LaunchedEffect(isBottomNavVisible) {
|
||||
(requireActivity() as CoreMainActivity).toggleBottomNavigation(isBottomNavVisible)
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
readerScreenState.update {
|
||||
copy(
|
||||
bottomNavigationHeight = getBottomNavigationHeight(),
|
||||
readerScreenTitle = context.getString(R.string.reader)
|
||||
)
|
||||
}
|
||||
}
|
||||
ReaderScreen(
|
||||
state = readerScreenState.value,
|
||||
actionMenuItems = emptyList(),
|
||||
@ -507,13 +521,13 @@ abstract class CoreReaderFragment :
|
||||
contentDescription = string.open_drawer,
|
||||
onClick = { navigationIconClick() }
|
||||
)
|
||||
}
|
||||
},
|
||||
listState = lazyListState
|
||||
)
|
||||
DialogHost(alertDialogShower as AlertDialogShower)
|
||||
}
|
||||
}
|
||||
addAlertDialogToDialogHost()
|
||||
setupMenu()
|
||||
donationDialogHandler?.setDonationDialogCallBack(this)
|
||||
val activity = requireActivity() as AppCompatActivity?
|
||||
activity?.let {
|
||||
@ -613,6 +627,8 @@ abstract class CoreReaderFragment :
|
||||
handleClicks()
|
||||
}
|
||||
|
||||
private fun getBottomNavigationHeight(): Int = getBottomNavigationView()?.measuredHeight ?: ZERO
|
||||
|
||||
private fun navigationIconClick() {
|
||||
// Manually handle the navigation open/close.
|
||||
// Since currently we are using the view based navigation drawer in other screens.
|
||||
@ -657,7 +673,6 @@ abstract class CoreReaderFragment :
|
||||
bottomToolbarArrowForward = findViewById(R.id.bottom_toolbar_arrow_forward)
|
||||
bottomToolbarHome = findViewById(R.id.bottom_toolbar_home)
|
||||
tabRecyclerView = findViewById(R.id.tab_switcher_recycler_view)
|
||||
snackBarRoot = findViewById(R.id.snackbar_root)
|
||||
bottomToolbarToc = findViewById(R.id.bottom_toolbar_toc)
|
||||
donationLayout = findViewById(R.id.donation_layout)
|
||||
}
|
||||
@ -912,7 +927,9 @@ abstract class CoreReaderFragment :
|
||||
// the unwanted blank space caused by the toolbar.
|
||||
setTopMarginToWebViews(-requireActivity().getToolbarHeight())
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||
bottomToolbar?.visibility = GONE
|
||||
readerScreenState.update {
|
||||
copy(shouldShowBottomAppBar = false)
|
||||
}
|
||||
contentFrame?.visibility = GONE
|
||||
progressBar?.visibility = GONE
|
||||
backToTopButton?.hide()
|
||||
@ -1123,19 +1140,12 @@ abstract class CoreReaderFragment :
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun updateBottomToolbarArrowsAlpha() {
|
||||
bottomToolbarArrowForward?.let {
|
||||
if (getCurrentWebView()?.canGoForward() == true) {
|
||||
bottomToolbarArrowForward?.alpha = 1f
|
||||
} else {
|
||||
bottomToolbarArrowForward?.alpha = 0.6f
|
||||
}
|
||||
}
|
||||
bottomToolbarArrowBack?.let {
|
||||
if (getCurrentWebView()?.canGoBack() == true) {
|
||||
bottomToolbarArrowBack?.alpha = 1f
|
||||
} else {
|
||||
bottomToolbarArrowBack?.alpha = 0.6f
|
||||
}
|
||||
val currentWebView = getCurrentWebView()
|
||||
readerScreenState.update {
|
||||
copy(
|
||||
previousPageButtonItem = previousPageButtonItem.copy(third = currentWebView?.canGoBack() == true),
|
||||
nextPageButtonItem = nextPageButtonItem.copy(third = currentWebView?.canGoForward() == true)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1224,7 +1234,9 @@ abstract class CoreReaderFragment :
|
||||
*/
|
||||
open fun updateTitle() {
|
||||
if (isAdded) {
|
||||
actionBar?.title = getValidTitle(zimReaderContainer?.zimFileTitle)
|
||||
readerScreenState.update {
|
||||
copy(readerScreenTitle = getValidTitle(zimReaderContainer?.zimFileTitle))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1350,7 +1362,6 @@ abstract class CoreReaderFragment :
|
||||
super.onDestroyView()
|
||||
findInPageTitle = null
|
||||
searchItemToOpen = null
|
||||
restoreTabsSnackbarCallback = null
|
||||
try {
|
||||
coreReaderLifeCycleScope?.cancel()
|
||||
readerLifeCycleScope?.cancel()
|
||||
@ -1412,7 +1423,6 @@ abstract class CoreReaderFragment :
|
||||
bottomToolbarArrowForward = null
|
||||
bottomToolbarHome = null
|
||||
tabRecyclerView = null
|
||||
snackBarRoot = null
|
||||
noOpenBookText = null
|
||||
bottomToolbarToc = null
|
||||
bottomToolbar = null
|
||||
@ -1484,16 +1494,17 @@ abstract class CoreReaderFragment :
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
protected open fun createWebView(attrs: AttributeSet?): ToolbarScrollingKiwixWebView? {
|
||||
requireNotNull(activityMainRoot)
|
||||
// requireNotNull(activityMainRoot)
|
||||
return ToolbarScrollingKiwixWebView(
|
||||
requireActivity(),
|
||||
this,
|
||||
attrs ?: throw IllegalArgumentException("AttributeSet must not be null"),
|
||||
activityMainRoot as ViewGroup,
|
||||
requireNotNull(videoView),
|
||||
null,
|
||||
// requireNotNull(readerScreenState.value.fullScreenItem.second),
|
||||
null,
|
||||
CoreWebViewClient(this, requireNotNull(zimReaderContainer)),
|
||||
requireNotNull(toolbarContainer),
|
||||
requireNotNull(bottomToolbar),
|
||||
// requireNotNull(toolbarContainer),
|
||||
// requireNotNull(bottomToolbar),
|
||||
requireNotNull(sharedPreferenceUtil)
|
||||
)
|
||||
}
|
||||
@ -1550,14 +1561,18 @@ abstract class CoreReaderFragment :
|
||||
notifyItemRemoved(index)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
snackBarRoot?.let {
|
||||
it.bringToFront()
|
||||
Snackbar.make(it, R.string.tab_closed, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.undo) { undoButton ->
|
||||
undoButton.isEnabled = false
|
||||
restoreDeletedTab(index)
|
||||
}.addCallback(restoreTabsSnackbarCallback).show()
|
||||
}
|
||||
readerScreenState.value.snackBarHostState.snack(
|
||||
requireActivity().getString(R.string.tab_closed),
|
||||
actionLabel = requireActivity().getString(R.string.undo),
|
||||
snackbarDuration = SnackbarDuration.Long,
|
||||
actionClick = { restoreDeletedTab(index) },
|
||||
lifecycleScope = lifecycleScope,
|
||||
snackBarResult = { result ->
|
||||
if (result != SnackbarResult.Dismissed && webViewList.isEmpty() && isAdded) {
|
||||
closeZimBook()
|
||||
}
|
||||
}
|
||||
)
|
||||
openHomeScreen()
|
||||
}
|
||||
|
||||
@ -1569,8 +1584,12 @@ abstract class CoreReaderFragment :
|
||||
|
||||
protected fun exitBook(shouldCloseZimBook: Boolean = true) {
|
||||
showNoBookOpenViews()
|
||||
bottomToolbar?.visibility = GONE
|
||||
actionBar?.title = getString(R.string.reader)
|
||||
readerScreenState.update {
|
||||
copy(
|
||||
shouldShowBottomAppBar = false,
|
||||
readerScreenTitle = requireActivity().getString(R.string.reader)
|
||||
)
|
||||
}
|
||||
contentFrame?.visibility = GONE
|
||||
hideProgressBar()
|
||||
mainMenu?.hideBookSpecificMenuItems()
|
||||
@ -1586,17 +1605,14 @@ abstract class CoreReaderFragment :
|
||||
}
|
||||
|
||||
protected fun showProgressBarWithProgress(progress: Int) {
|
||||
progressBar?.apply {
|
||||
visibility = VISIBLE
|
||||
show()
|
||||
this.progress = progress
|
||||
readerScreenState.update {
|
||||
copy(pageLoadingItem = true to progress)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun hideProgressBar() {
|
||||
progressBar?.apply {
|
||||
visibility = GONE
|
||||
hide()
|
||||
readerScreenState.update {
|
||||
copy(pageLoadingItem = false to ZERO)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1617,9 +1633,10 @@ abstract class CoreReaderFragment :
|
||||
}
|
||||
webViewList.add(index, it)
|
||||
tabsAdapter?.notifyDataSetChanged()
|
||||
snackBarRoot?.let { root ->
|
||||
Snackbar.make(root, R.string.tab_restored, Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
readerScreenState.value.snackBarHostState.snack(
|
||||
requireActivity().getString(R.string.tab_restored),
|
||||
lifecycleScope = lifecycleScope
|
||||
)
|
||||
setUpWithTextToSpeech(it)
|
||||
updateBottomToolbarVisibility()
|
||||
safelyAddWebView(it)
|
||||
@ -1628,22 +1645,21 @@ abstract class CoreReaderFragment :
|
||||
|
||||
private fun safelyAddWebView(webView: KiwixWebView) {
|
||||
webView.parent?.let { (it as ViewGroup).removeView(webView) }
|
||||
contentFrame?.addView(webView)
|
||||
readerScreenState.update {
|
||||
copy(selectedWebView = webView)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun selectTab(position: Int) {
|
||||
currentWebViewIndex = position
|
||||
contentFrame?.let {
|
||||
it.removeAllViews()
|
||||
val webView = safelyGetWebView(position) ?: return@selectTab
|
||||
safelyAddWebView(webView)
|
||||
tabsAdapter?.selected = currentWebViewIndex
|
||||
updateBottomToolbarVisibility()
|
||||
loadPrefs()
|
||||
updateUrlFlow()
|
||||
updateTableOfContents()
|
||||
updateTitle()
|
||||
}
|
||||
val webView = safelyGetWebView(position) ?: return
|
||||
safelyAddWebView(webView)
|
||||
tabsAdapter?.selected = currentWebViewIndex
|
||||
updateBottomToolbarVisibility()
|
||||
loadPrefs()
|
||||
updateUrlFlow()
|
||||
updateTableOfContents()
|
||||
updateTitle()
|
||||
}
|
||||
|
||||
private fun safelyGetWebView(position: Int): KiwixWebView? =
|
||||
@ -1667,22 +1683,6 @@ abstract class CoreReaderFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupMenu() {
|
||||
(requireActivity() as MenuHost).addMenuProvider(
|
||||
object : MenuProvider {
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menu.clear()
|
||||
mainMenu = createMainMenu(menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean =
|
||||
mainMenu?.onOptionsItemSelected(menuItem) == true
|
||||
},
|
||||
viewLifecycleOwner,
|
||||
Lifecycle.State.RESUMED
|
||||
)
|
||||
}
|
||||
|
||||
override fun onFullscreenMenuClicked() {
|
||||
if (isInFullScreenMode()) {
|
||||
closeFullScreen()
|
||||
@ -1846,7 +1846,9 @@ abstract class CoreReaderFragment :
|
||||
protected open fun openFullScreen() {
|
||||
(requireActivity() as CoreMainActivity).disableDrawer(false)
|
||||
toolbarContainer?.visibility = GONE
|
||||
bottomToolbar?.visibility = GONE
|
||||
readerScreenState.update {
|
||||
copy(shouldShowBottomAppBar = false)
|
||||
}
|
||||
exitFullscreenButton?.visibility = VISIBLE
|
||||
exitFullscreenButton?.background?.alpha = 153
|
||||
val window = requireActivity().window
|
||||
@ -2038,16 +2040,19 @@ abstract class CoreReaderFragment :
|
||||
zimReaderSource?.let { openZimFile(it) }
|
||||
}
|
||||
} else {
|
||||
snackBarRoot?.let { snackBarRoot ->
|
||||
Snackbar.make(snackBarRoot, R.string.request_storage, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.menu_settings) {
|
||||
val intent = Intent()
|
||||
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
val uri = Uri.fromParts("package", requireActivity().packageName, null)
|
||||
intent.data = uri
|
||||
startActivity(intent)
|
||||
}.show()
|
||||
}
|
||||
readerScreenState.value.snackBarHostState.snack(
|
||||
requireActivity().getString(R.string.request_storage),
|
||||
actionLabel = requireActivity().getString(R.string.menu_settings),
|
||||
snackbarDuration = SnackbarDuration.Long,
|
||||
actionClick = {
|
||||
val intent = Intent()
|
||||
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
val uri = Uri.fromParts("package", requireActivity().packageName, null)
|
||||
intent.data = uri
|
||||
startActivity(intent)
|
||||
},
|
||||
lifecycleScope = lifecycleScope
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2073,27 +2078,18 @@ abstract class CoreReaderFragment :
|
||||
webViewList.clear()
|
||||
tabsAdapter?.notifyDataSetChanged()
|
||||
openHomeScreen()
|
||||
snackBarRoot?.let { root ->
|
||||
root.bringToFront()
|
||||
Snackbar.make(root, R.string.tabs_closed, Snackbar.LENGTH_LONG).apply {
|
||||
setAction(R.string.undo) {
|
||||
it.isEnabled = false // to prevent multiple clicks on this button
|
||||
setIsCloseAllTabButtonClickable(true)
|
||||
restoreDeletedTabs()
|
||||
readerScreenState.value.snackBarHostState.snack(
|
||||
requireActivity().getString(R.string.tabs_closed),
|
||||
actionLabel = requireActivity().getString(R.string.undo),
|
||||
snackbarDuration = SnackbarDuration.Long,
|
||||
actionClick = { restoreDeletedTabs() },
|
||||
lifecycleScope = lifecycleScope,
|
||||
snackBarResult = { result ->
|
||||
if (result != SnackbarResult.Dismissed && webViewList.isEmpty() && isAdded) {
|
||||
closeZimBook()
|
||||
}
|
||||
}.addCallback(restoreTabsSnackbarCallback).show()
|
||||
}
|
||||
}
|
||||
|
||||
private var restoreTabsSnackbarCallback: Snackbar.Callback? = object : Snackbar.Callback() {
|
||||
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
|
||||
super.onDismissed(transientBottomBar, event)
|
||||
// If the undo button is not clicked and no tabs are left, exit the book and
|
||||
// clean up resources.
|
||||
if (event != DISMISS_EVENT_ACTION && webViewList.isEmpty() && isAdded) {
|
||||
closeZimBook()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun setIsCloseAllTabButtonClickable(isClickable: Boolean) {
|
||||
@ -2104,9 +2100,10 @@ abstract class CoreReaderFragment :
|
||||
if (tempWebViewListForUndo.isNotEmpty()) {
|
||||
webViewList.addAll(tempWebViewListForUndo)
|
||||
tabsAdapter?.notifyDataSetChanged()
|
||||
snackBarRoot?.let { root ->
|
||||
Snackbar.make(root, R.string.tabs_restored, Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
readerScreenState.value.snackBarHostState.snack(
|
||||
requireActivity().getString(R.string.tabs_restored),
|
||||
lifecycleScope = lifecycleScope
|
||||
)
|
||||
reopenBook()
|
||||
showTabSwitcher()
|
||||
setUpWithTextToSpeech(tempWebViewListForUndo[tempWebViewListForUndo.lastIndex])
|
||||
@ -2117,13 +2114,11 @@ abstract class CoreReaderFragment :
|
||||
|
||||
// opens home screen when user closes all tabs
|
||||
protected fun showNoBookOpenViews() {
|
||||
noOpenBookButton?.visibility = VISIBLE
|
||||
noOpenBookText?.visibility = VISIBLE
|
||||
readerScreenState.update { copy(isNoBookOpenInReader = true) }
|
||||
}
|
||||
|
||||
private fun hideNoBookOpenViews() {
|
||||
noOpenBookButton?.visibility = GONE
|
||||
noOpenBookText?.visibility = GONE
|
||||
readerScreenState.update { copy(isNoBookOpenInReader = false) }
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@ -2145,19 +2140,20 @@ abstract class CoreReaderFragment :
|
||||
val libKiwixBook = getLibkiwixBook(zimFileReader)
|
||||
if (isBookmarked) {
|
||||
repositoryActions?.deleteBookmark(libKiwixBook.id, articleUrl)
|
||||
snackBarRoot?.snack(R.string.bookmark_removed)
|
||||
readerScreenState.value.snackBarHostState.snack(
|
||||
requireActivity().getString(R.string.bookmark_removed),
|
||||
lifecycleScope = lifecycleScope
|
||||
)
|
||||
} else {
|
||||
getCurrentWebView()?.title?.let {
|
||||
repositoryActions?.saveBookmark(
|
||||
LibkiwixBookmarkItem(it, articleUrl, zimFileReader, libKiwixBook)
|
||||
)
|
||||
snackBarRoot?.snack(
|
||||
stringId = R.string.bookmark_added,
|
||||
actionStringId = R.string.open,
|
||||
actionClick = {
|
||||
goToBookmarks()
|
||||
Unit
|
||||
}
|
||||
readerScreenState.value.snackBarHostState.snack(
|
||||
requireActivity().getString(R.string.bookmark_added),
|
||||
lifecycleScope = lifecycleScope,
|
||||
actionLabel = requireActivity().getString(R.string.open),
|
||||
actionClick = { goToBookmarks() }
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -2174,7 +2170,7 @@ abstract class CoreReaderFragment :
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the libkiwix book evertime when user saves or remove the bookmark.
|
||||
* Returns the libkiwix book everytime when user saves or remove the bookmark.
|
||||
* the object will be created once to avoid creating it multiple times.
|
||||
*/
|
||||
private fun getLibkiwixBook(zimFileReader: ZimFileReader): Book {
|
||||
@ -2289,14 +2285,10 @@ abstract class CoreReaderFragment :
|
||||
protected fun isInFullScreenMode(): Boolean = sharedPreferenceUtil?.prefFullScreen == true
|
||||
|
||||
private fun updateBottomToolbarVisibility() {
|
||||
bottomToolbar?.let {
|
||||
if (urlIsValid() &&
|
||||
tabSwitcherRoot?.visibility != VISIBLE && !isInFullScreenMode()
|
||||
) {
|
||||
it.visibility = VISIBLE
|
||||
} else {
|
||||
it.visibility = GONE
|
||||
}
|
||||
// TODO refactroe this code once we integrate the tabSwitcher
|
||||
// tabSwitcherRoot?.visibility != VISIBLE && !isInFullScreenMode()
|
||||
readerScreenState.update {
|
||||
copy(shouldShowBottomAppBar = !isInFullScreenMode())
|
||||
}
|
||||
}
|
||||
|
||||
@ -2566,17 +2558,13 @@ abstract class CoreReaderFragment :
|
||||
* WARNING: If modifying this method, ensure thorough testing with custom apps
|
||||
* to verify proper functionality.
|
||||
*/
|
||||
protected open fun createMainMenu(menu: Menu?): MainMenu? =
|
||||
menu?.let {
|
||||
menuFactory?.create(
|
||||
it,
|
||||
webViewList,
|
||||
urlIsValid(),
|
||||
menuClickListener = this,
|
||||
disableReadAloud = false,
|
||||
disableTabs = false
|
||||
)
|
||||
}
|
||||
protected open fun createMainMenu(menu: Menu?): ReaderMenuState? =
|
||||
ReaderMenuState(
|
||||
this,
|
||||
disableReadAloud = false,
|
||||
disableTabs = false,
|
||||
disableSearch = false
|
||||
)
|
||||
|
||||
protected fun urlIsValid(): Boolean = getCurrentWebView()?.url != null
|
||||
|
||||
@ -2588,7 +2576,7 @@ abstract class CoreReaderFragment :
|
||||
painter?.update(
|
||||
getCurrentWebView(),
|
||||
::shouldActivateNightMode,
|
||||
videoView
|
||||
readerScreenState.value.fullScreenItem.second
|
||||
)
|
||||
}
|
||||
|
||||
@ -2878,11 +2866,14 @@ abstract class CoreReaderFragment :
|
||||
{
|
||||
if (isOpenNewTabInBackground) {
|
||||
newTabInBackground(url)
|
||||
snackBarRoot?.snack(
|
||||
stringId = R.string.new_tab_snack_bar,
|
||||
actionStringId = R.string.open,
|
||||
readerScreenState.value.snackBarHostState.snack(
|
||||
message = requireActivity().getString(R.string.new_tab_snack_bar),
|
||||
lifecycleScope = lifecycleScope,
|
||||
actionLabel = requireActivity().getString(R.string.open),
|
||||
actionClick = {
|
||||
if (webViewList.size > 1) selectTab(webViewList.size - 1)
|
||||
if (webViewList.size > 1) {
|
||||
selectTab(webViewList.size - 1)
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
@ -3104,6 +3095,8 @@ abstract class CoreReaderFragment :
|
||||
* when handling invalid JSON scenarios.
|
||||
*/
|
||||
abstract fun restoreViewStateOnInvalidWebViewHistory()
|
||||
|
||||
abstract fun getBottomNavigationView(): BottomNavigationView?
|
||||
}
|
||||
|
||||
enum class RestoreOrigin {
|
||||
|
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Kiwix Android
|
||||
* Copyright (c) 2025 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.reader
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.page.SEARCH_ICON_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
|
||||
import org.kiwix.kiwixmobile.core.ui.models.IconItem
|
||||
|
||||
const val READ_ALOUD_MENU_ITEM_TESTING_TAG = "readAloudMenuItemTestingTag"
|
||||
const val TAKE_NOTE_MENU_ITEM_TESTING_TAG = "takeNoteMenuItemTestingTag"
|
||||
const val FULL_SCREEN_MENU_ITEM_TESTING_TAG = "fullScreenMenuItemTestingTag"
|
||||
const val RANDOM_ARTICLE_MENU_ITEM_TESTING_TAG = "randomArticleMenuItemTestingTag"
|
||||
const val TAB_MENU_ITEM_TESTING_TAG = "tabMenuItemTestingTag"
|
||||
|
||||
@Stable
|
||||
class ReaderMenuState(
|
||||
private val menuClickListener: MenuClickListener,
|
||||
private val disableReadAloud: Boolean = false,
|
||||
private val disableTabs: Boolean = false,
|
||||
private val disableSearch: Boolean = false
|
||||
) {
|
||||
interface MenuClickListener {
|
||||
fun onTabMenuClicked()
|
||||
fun onHomeMenuClicked()
|
||||
fun onAddNoteMenuClicked()
|
||||
fun onRandomArticleMenuClicked()
|
||||
fun onReadAloudMenuClicked()
|
||||
fun onFullscreenMenuClicked()
|
||||
fun onSearchMenuClickedMenuClicked()
|
||||
}
|
||||
|
||||
var isInTabSwitcher by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
var isReadingAloud by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
var webViewCount by mutableStateOf(0)
|
||||
var urlIsValid by mutableStateOf(false)
|
||||
var zimFileReaderAvailable by mutableStateOf(false)
|
||||
|
||||
fun onTabsChanged(count: Int) {
|
||||
webViewCount = count
|
||||
}
|
||||
|
||||
fun onUrlValidityChanged(valid: Boolean) {
|
||||
urlIsValid = valid
|
||||
}
|
||||
|
||||
fun onZimFileReaderAvailable(available: Boolean) {
|
||||
zimFileReaderAvailable = available
|
||||
}
|
||||
|
||||
fun onTextToSpeechStarted() {
|
||||
isReadingAloud = true
|
||||
}
|
||||
|
||||
fun onTextToSpeechStopped() {
|
||||
isReadingAloud = false
|
||||
}
|
||||
|
||||
fun exitTabSwitcher() {
|
||||
isInTabSwitcher = false
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "MagicNumber")
|
||||
fun getActionMenuItems(): List<ActionMenuItem> {
|
||||
if (isInTabSwitcher) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val list = mutableListOf<ActionMenuItem>()
|
||||
|
||||
if (!disableSearch && urlIsValid) {
|
||||
list += ActionMenuItem(
|
||||
icon = IconItem.Drawable(R.drawable.action_search),
|
||||
contentDescription = R.string.search_label,
|
||||
onClick = { menuClickListener.onSearchMenuClickedMenuClicked() },
|
||||
isInOverflow = false,
|
||||
testingTag = SEARCH_ICON_TESTING_TAG
|
||||
)
|
||||
}
|
||||
|
||||
if (!disableTabs) {
|
||||
val tabLabel = if (webViewCount > 99) ":D" else "$webViewCount"
|
||||
list += ActionMenuItem(
|
||||
icon = IconItem.Vector(Icons.Default.Add),
|
||||
contentDescription = R.string.switch_tabs,
|
||||
onClick = {
|
||||
isInTabSwitcher = true
|
||||
menuClickListener.onTabMenuClicked()
|
||||
},
|
||||
isInOverflow = false,
|
||||
iconButtonText = tabLabel,
|
||||
testingTag = TAB_MENU_ITEM_TESTING_TAG
|
||||
)
|
||||
}
|
||||
|
||||
if (urlIsValid) {
|
||||
list += listOf(
|
||||
ActionMenuItem(
|
||||
icon = IconItem.Drawable(R.drawable.ic_add_note),
|
||||
contentDescription = R.string.take_notes,
|
||||
onClick = { menuClickListener.onAddNoteMenuClicked() },
|
||||
testingTag = TAKE_NOTE_MENU_ITEM_TESTING_TAG
|
||||
),
|
||||
ActionMenuItem(
|
||||
contentDescription = R.string.menu_random_article,
|
||||
onClick = { menuClickListener.onRandomArticleMenuClicked() },
|
||||
testingTag = RANDOM_ARTICLE_MENU_ITEM_TESTING_TAG
|
||||
),
|
||||
ActionMenuItem(
|
||||
contentDescription = R.string.menu_full_screen,
|
||||
onClick = { menuClickListener.onFullscreenMenuClicked() },
|
||||
testingTag = FULL_SCREEN_MENU_ITEM_TESTING_TAG
|
||||
)
|
||||
)
|
||||
|
||||
if (!disableReadAloud) {
|
||||
list += ActionMenuItem(
|
||||
contentDescription = if (isReadingAloud) R.string.menu_read_aloud_stop else R.string.menu_read_aloud,
|
||||
onClick = {
|
||||
isReadingAloud = !isReadingAloud
|
||||
menuClickListener.onReadAloudMenuClicked()
|
||||
},
|
||||
testingTag = READ_ALOUD_MENU_ITEM_TESTING_TAG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
}
|
@ -36,13 +36,16 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.BottomAppBar
|
||||
import androidx.compose.material3.BottomAppBarDefaults
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
@ -54,23 +57,31 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
|
||||
import org.kiwix.kiwixmobile.core.main.DarkModeViewPainter
|
||||
import org.kiwix.kiwixmobile.core.main.KiwixWebView
|
||||
import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar
|
||||
@ -78,6 +89,8 @@ import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
|
||||
import org.kiwix.kiwixmobile.core.ui.components.KiwixButton
|
||||
import org.kiwix.kiwixmobile.core.ui.components.KiwixSnackbarHost
|
||||
import org.kiwix.kiwixmobile.core.ui.components.ProgressBarStyle
|
||||
import org.kiwix.kiwixmobile.core.ui.components.ScrollDirection
|
||||
import org.kiwix.kiwixmobile.core.ui.components.rememberLazyListScrollListener
|
||||
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
|
||||
import org.kiwix.kiwixmobile.core.ui.models.IconItem
|
||||
import org.kiwix.kiwixmobile.core.ui.models.IconItem.Drawable
|
||||
@ -103,14 +116,29 @@ const val CONTENT_LOADING_PROGRESSBAR_TESTING_TAG = "contentLoadingProgressBarTe
|
||||
@Composable
|
||||
fun ReaderScreen(
|
||||
state: ReaderScreenState,
|
||||
listState: LazyListState,
|
||||
actionMenuItems: List<ActionMenuItem>,
|
||||
navigationIcon: @Composable () -> Unit
|
||||
) {
|
||||
val (bottomNavHeight, lazyListState) =
|
||||
rememberScrollBehavior(state.bottomNavigationHeight, listState)
|
||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||
KiwixDialogTheme {
|
||||
Scaffold(
|
||||
snackbarHost = { KiwixSnackbarHost(snackbarHostState = state.snackBarHostState) },
|
||||
topBar = { KiwixAppBar(R.string.note, navigationIcon, actionMenuItems) },
|
||||
floatingActionButton = { BackToTopFab(state) }
|
||||
topBar = {
|
||||
KiwixAppBar(
|
||||
state.readerScreenTitle,
|
||||
navigationIcon,
|
||||
actionMenuItems,
|
||||
scrollBehavior
|
||||
)
|
||||
},
|
||||
floatingActionButton = { BackToTopFab(state) },
|
||||
modifier = Modifier
|
||||
.systemBarsPadding()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.padding(bottom = bottomNavHeight.value)
|
||||
) { paddingValues ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@ -120,15 +148,16 @@ fun ReaderScreen(
|
||||
if (state.isNoBookOpenInReader) {
|
||||
NoBookOpenView(state.onOpenLibraryButtonClicked)
|
||||
} else {
|
||||
ShowZIMFileContent(state)
|
||||
ShowProgressBarIfZIMFilePageIsLoading(state)
|
||||
ShowZIMFileContent(state.kiwixWebViewList)
|
||||
TtsControls(state)
|
||||
BottomAppBarOfReaderScreen(
|
||||
state.bookmarkButtonItem,
|
||||
state.previousPageButtonItem,
|
||||
state.onHomeButtonClick,
|
||||
state.nextPageButtonItem,
|
||||
state.onTocClick
|
||||
state.onTocClick,
|
||||
state.shouldShowBottomAppBar
|
||||
)
|
||||
ShowFullScreenView(state)
|
||||
}
|
||||
@ -139,9 +168,14 @@ fun ReaderScreen(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShowZIMFileContent(kiwixWebViewList: List<KiwixWebView>) {
|
||||
if (kiwixWebViewList.isNotEmpty()) {
|
||||
AndroidView({ kiwixWebViewList[0] }, modifier = Modifier.fillMaxSize())
|
||||
private fun ShowZIMFileContent(state: ReaderScreenState) {
|
||||
state.selectedWebView?.let { selectedWebView ->
|
||||
key(selectedWebView) {
|
||||
AndroidView(
|
||||
factory = { selectedWebView },
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,7 +192,7 @@ private fun BoxScope.ShowProgressBarIfZIMFilePageIsLoading(state: ReaderScreenSt
|
||||
ContentLoadingProgressBar(
|
||||
modifier = Modifier
|
||||
.testTag(CONTENT_LOADING_PROGRESSBAR_TESTING_TAG)
|
||||
.align(Alignment.CenterEnd),
|
||||
.align(Alignment.TopCenter),
|
||||
progressBarStyle = ProgressBarStyle.HORIZONTAL,
|
||||
progress = state.pageLoadingItem.second
|
||||
)
|
||||
@ -240,17 +274,22 @@ private fun BackToTopFab(state: ReaderScreenState) {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun BottomAppBarOfReaderScreen(
|
||||
private fun BoxScope.BottomAppBarOfReaderScreen(
|
||||
bookmarkButtonItem: Triple<() -> Unit, () -> Unit, Drawable>,
|
||||
previousPageButtonItem: Pair<() -> Unit, () -> Unit>,
|
||||
previousPageButtonItem: Triple<() -> Unit, () -> Unit, Boolean>,
|
||||
onHomeButtonClick: () -> Unit,
|
||||
nextPageButtonItem: Pair<() -> Unit, () -> Unit>,
|
||||
onTocClick: () -> Unit
|
||||
nextPageButtonItem: Triple<() -> Unit, () -> Unit, Boolean>,
|
||||
onTocClick: () -> Unit,
|
||||
shouldShowBottomAppBar: Boolean
|
||||
) {
|
||||
if (!shouldShowBottomAppBar) return
|
||||
BottomAppBar(
|
||||
containerColor = Black,
|
||||
contentColor = White
|
||||
contentColor = White,
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@ -271,6 +310,7 @@ private fun BottomAppBarOfReaderScreen(
|
||||
onClick = previousPageButtonItem.first,
|
||||
onLongClick = previousPageButtonItem.second,
|
||||
buttonIcon = Drawable(R.drawable.ic_keyboard_arrow_left_24dp),
|
||||
shouldEnable = previousPageButtonItem.third,
|
||||
contentDescription = stringResource(R.string.go_to_previous_page)
|
||||
)
|
||||
// Home Icon(to open the home page of ZIM file)
|
||||
@ -284,6 +324,7 @@ private fun BottomAppBarOfReaderScreen(
|
||||
onClick = nextPageButtonItem.first,
|
||||
onLongClick = nextPageButtonItem.second,
|
||||
buttonIcon = Drawable(R.drawable.ic_keyboard_arrow_right_24dp),
|
||||
shouldEnable = nextPageButtonItem.third,
|
||||
contentDescription = stringResource(R.string.go_to_next_page)
|
||||
)
|
||||
// Toggle Icon(to open the table of content in right side bar)
|
||||
@ -301,11 +342,13 @@ private fun BottomAppBarButtonIcon(
|
||||
onClick: () -> Unit,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
buttonIcon: IconItem,
|
||||
shouldEnable: Boolean = true,
|
||||
contentDescription: String
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.combinedClickable(onClick = onClick, onLongClick = onLongClick)
|
||||
modifier = Modifier.combinedClickable(onClick = onClick, onLongClick = onLongClick),
|
||||
enabled = shouldEnable
|
||||
) {
|
||||
Icon(
|
||||
buttonIcon.toPainter(),
|
||||
@ -454,3 +497,30 @@ fun TabItemView(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberScrollBehavior(
|
||||
bottomNavigationHeight: Int,
|
||||
listState: LazyListState,
|
||||
): Pair<MutableState<Dp>, LazyListState> {
|
||||
val bottomNavHeightInDp = with(LocalDensity.current) { bottomNavigationHeight.toDp() }
|
||||
val bottomNavHeight = remember { mutableStateOf(bottomNavHeightInDp) }
|
||||
val lazyListState = rememberLazyListScrollListener(
|
||||
lazyListState = listState,
|
||||
onScrollChanged = { direction ->
|
||||
when (direction) {
|
||||
ScrollDirection.SCROLL_UP -> {
|
||||
bottomNavHeight.value = bottomNavHeightInDp
|
||||
}
|
||||
|
||||
ScrollDirection.SCROLL_DOWN -> {
|
||||
bottomNavHeight.value = ZERO.dp
|
||||
}
|
||||
|
||||
ScrollDirection.IDLE -> {}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return bottomNavHeight to lazyListState
|
||||
}
|
||||
|
@ -83,6 +83,10 @@ data class ReaderScreenState(
|
||||
* To show in the tabs view.
|
||||
*/
|
||||
val kiwixWebViewList: List<KiwixWebView>,
|
||||
/**
|
||||
* Manages the showing of current selected webView.
|
||||
*/
|
||||
val selectedWebView: KiwixWebView?,
|
||||
/**
|
||||
* Handles the (UI, and clicks) for bookmark button in reader bottom toolbar.
|
||||
*
|
||||
@ -95,11 +99,12 @@ data class ReaderScreenState(
|
||||
/**
|
||||
* Handles the clicks of previous page button in reader bottom toolbar.
|
||||
*
|
||||
* A [Pair] containing:
|
||||
* A [Triple] containing:
|
||||
* - [Unit]: Handles the normal click of button(For going to previous page).
|
||||
* - [Unit]: Handles the long click of button(For showing the previous pages history).
|
||||
* - [Boolean]: Handles the button should enable or not.
|
||||
*/
|
||||
val previousPageButtonItem: Pair<() -> Unit, () -> Unit>,
|
||||
val previousPageButtonItem: Triple<() -> Unit, () -> Unit, Boolean>,
|
||||
/**
|
||||
* Handles the click to open home page of ZIM file button click in reader bottom toolbar.
|
||||
*/
|
||||
@ -110,11 +115,21 @@ data class ReaderScreenState(
|
||||
* A [Pair] containing:
|
||||
* - [Unit]: Handles the normal click of button(For going to next page).
|
||||
* - [Unit]: Handles the long click of button(For showing the next pages history).
|
||||
* - [Boolean]: Handles the button should enable or not.
|
||||
*/
|
||||
val nextPageButtonItem: Pair<() -> Unit, () -> Unit>,
|
||||
val nextPageButtonItem: Triple<() -> Unit, () -> Unit, Boolean>,
|
||||
/**
|
||||
* Handles the click to open right sidebar button click in reader bottom toolbar.
|
||||
*/
|
||||
val onTocClick: () -> Unit,
|
||||
val onCloseAllTabs: () -> Unit
|
||||
val onCloseAllTabs: () -> Unit,
|
||||
/**
|
||||
* Stores the height of the bottom navigation bar in pixels.
|
||||
*/
|
||||
val bottomNavigationHeight: Int,
|
||||
/**
|
||||
* Manages the showing of Reader's [BottomAppBarOfReaderScreen].
|
||||
*/
|
||||
val shouldShowBottomAppBar: Boolean,
|
||||
val readerScreenTitle: String
|
||||
)
|
||||
|
@ -88,7 +88,7 @@ fun PageScreen(
|
||||
topBar = {
|
||||
Column {
|
||||
KiwixAppBar(
|
||||
titleId = state.screenTitle,
|
||||
title = stringResource(state.screenTitle),
|
||||
navigationIcon = navigationIcon,
|
||||
actionMenuItems = actionMenuItems,
|
||||
searchBar = searchBarIfActive(state)
|
||||
|
@ -70,7 +70,7 @@ fun NavigationHistoryDialogScreen(
|
||||
) {
|
||||
KiwixDialogTheme {
|
||||
Scaffold(
|
||||
topBar = { KiwixAppBar(titleId, navigationIcon, actionMenuItems) }
|
||||
topBar = { KiwixAppBar(stringResource(titleId), navigationIcon, actionMenuItems) }
|
||||
) { paddingValues ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
@ -260,7 +260,7 @@ class SearchFragment : BaseFragment() {
|
||||
searchViewModel.actions.trySend(ClickedSearchInText).isSuccess
|
||||
},
|
||||
testingTag = FIND_IN_PAGE_TESTING_TAG,
|
||||
iconButtonText = R.string.menu_search_in_text,
|
||||
iconButtonText = requireActivity().getString(R.string.menu_search_in_text),
|
||||
isEnabled = findInPageMenuItem.value.first
|
||||
)
|
||||
} else {
|
||||
|
@ -87,7 +87,7 @@ fun SearchScreen(
|
||||
Scaffold(
|
||||
topBar = {
|
||||
KiwixAppBar(
|
||||
titleId = R.string.empty_string,
|
||||
title = stringResource(R.string.empty_string),
|
||||
navigationIcon = searchScreenState.navigationIcon,
|
||||
actionMenuItems = actionMenuItemList,
|
||||
searchBar = {
|
||||
|
@ -117,7 +117,7 @@ fun SettingsScreen(
|
||||
Scaffold(
|
||||
topBar = {
|
||||
KiwixAppBar(
|
||||
titleId = R.string.menu_settings,
|
||||
title = stringResource(R.string.menu_settings),
|
||||
navigationIcon = navigationIcon
|
||||
)
|
||||
}
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
package org.kiwix.kiwixmobile.core.ui.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@ -29,6 +28,10 @@ import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
@ -67,7 +70,7 @@ const val TOOLBAR_TITLE_TESTING_TAG = "toolbarTitle"
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun KiwixAppBar(
|
||||
@StringRes titleId: Int,
|
||||
title: String,
|
||||
navigationIcon: @Composable () -> Unit,
|
||||
actionMenuItems: List<ActionMenuItem> = emptyList(),
|
||||
topAppBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
|
||||
@ -76,7 +79,7 @@ fun KiwixAppBar(
|
||||
) {
|
||||
KiwixTheme {
|
||||
TopAppBar(
|
||||
title = { AppBarTitleSection(titleId, searchBar) },
|
||||
title = { AppBarTitleSection(title, searchBar) },
|
||||
navigationIcon = navigationIcon,
|
||||
actions = { ActionMenu(actionMenuItems) },
|
||||
scrollBehavior = topAppBarScrollBehavior,
|
||||
@ -95,7 +98,7 @@ fun KiwixAppBar(
|
||||
@Suppress("ComposableLambdaParameterNaming")
|
||||
@Composable
|
||||
private fun AppBarTitleSection(
|
||||
@StringRes titleId: Int,
|
||||
title: String,
|
||||
searchBar: (@Composable () -> Unit)? = null
|
||||
) {
|
||||
Box(
|
||||
@ -107,14 +110,14 @@ private fun AppBarTitleSection(
|
||||
searchBar?.let {
|
||||
it()
|
||||
} ?: run {
|
||||
AppBarTitle(titleId)
|
||||
AppBarTitle(title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppBarTitle(
|
||||
@StringRes titleId: Int
|
||||
title: String
|
||||
) {
|
||||
val appBarTitleColor = if (isSystemInDarkTheme()) {
|
||||
MineShaftGray350
|
||||
@ -122,7 +125,7 @@ private fun AppBarTitle(
|
||||
White
|
||||
}
|
||||
Text(
|
||||
text = stringResource(titleId),
|
||||
text = title,
|
||||
color = appBarTitleColor,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
@ -132,10 +135,14 @@ private fun AppBarTitle(
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun ActionMenu(actionMenuItems: List<ActionMenuItem>) {
|
||||
var overflowExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
Row {
|
||||
actionMenuItems.forEach { menuItem ->
|
||||
val (mainActions, overflowActions) = actionMenuItems.partition { !it.isInOverflow }
|
||||
mainActions.forEach { menuItem ->
|
||||
val modifier = menuItem.modifier.testTag(menuItem.testingTag)
|
||||
// If icon is not null show the icon.
|
||||
menuItem.icon?.let {
|
||||
@ -158,7 +165,7 @@ private fun ActionMenu(actionMenuItems: List<ActionMenuItem>) {
|
||||
modifier = modifier
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = menuItem.iconButtonText).uppercase(),
|
||||
text = menuItem.iconButtonText.uppercase(),
|
||||
color = if (menuItem.isEnabled) Color.White else Color.Gray,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
@ -166,6 +173,32 @@ private fun ActionMenu(actionMenuItems: List<ActionMenuItem>) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (overflowActions.isNotEmpty()) {
|
||||
IconButton(onClick = { overflowExpanded = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = null,
|
||||
tint = White
|
||||
)
|
||||
}
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = overflowExpanded,
|
||||
onDismissRequest = { overflowExpanded = false }
|
||||
) {
|
||||
overflowActions.forEach { menuItem ->
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(text = menuItem.iconButtonText)
|
||||
},
|
||||
onClick = {
|
||||
overflowExpanded = false
|
||||
menuItem.onClick()
|
||||
},
|
||||
enabled = menuItem.isEnabled
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,6 @@ package org.kiwix.kiwixmobile.core.ui.models
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.ui.theme.White
|
||||
|
||||
data class ActionMenuItem(
|
||||
@ -30,7 +29,8 @@ data class ActionMenuItem(
|
||||
val onClick: () -> Unit,
|
||||
val iconTint: Color = White,
|
||||
val isEnabled: Boolean = true,
|
||||
@StringRes val iconButtonText: Int = R.string.empty_string,
|
||||
val iconButtonText: String = "",
|
||||
val testingTag: String,
|
||||
val modifier: Modifier = Modifier
|
||||
val modifier: Modifier = Modifier,
|
||||
val isInOverflow: Boolean = false
|
||||
)
|
||||
|
@ -181,7 +181,7 @@ object ComposeDimens {
|
||||
val CATEGORY_TITLE_TEXT_SIZE = 14.sp
|
||||
|
||||
// Reader screen dimes
|
||||
val READER_BOTTOM_APP_BAR_LAYOUT_HEIGHT = 48.dp
|
||||
val READER_BOTTOM_APP_BAR_LAYOUT_HEIGHT = 40.dp
|
||||
val READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE = 30.dp
|
||||
const val TTS_BUTTONS_CONTROL_ALPHA = 0.6f
|
||||
val CLOSE_ALL_TAB_BUTTON_BOTTOM_PADDING = 24.dp
|
||||
|
@ -215,6 +215,10 @@ class CustomMainActivity : CoreMainActivity() {
|
||||
activityCustomMainBinding.root.addView(getDialogHostComposeView(alertDialogShower), 0)
|
||||
}
|
||||
|
||||
override fun toggleBottomNavigation(isVisible: Boolean) {
|
||||
// Do nothing as we do not have the bottomNavigationView in custom apps.
|
||||
}
|
||||
|
||||
// Outdated shortcut id(new_tab)
|
||||
// Remove if the application has the outdated shortcut.
|
||||
private fun removeOutdatedIdShortcuts() {
|
||||
|
@ -31,6 +31,7 @@ import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.net.toUri
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import kotlinx.coroutines.launch
|
||||
import org.kiwix.kiwixmobile.core.R.dimen
|
||||
import org.kiwix.kiwixmobile.core.base.BaseActivity
|
||||
@ -38,7 +39,7 @@ import org.kiwix.kiwixmobile.core.extensions.browserIntent
|
||||
import org.kiwix.kiwixmobile.core.extensions.getResizedDrawable
|
||||
import org.kiwix.kiwixmobile.core.extensions.isFileExist
|
||||
import org.kiwix.kiwixmobile.core.main.reader.CoreReaderFragment
|
||||
import org.kiwix.kiwixmobile.core.main.MainMenu
|
||||
import org.kiwix.kiwixmobile.core.main.reader.ReaderMenuState
|
||||
import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin
|
||||
import org.kiwix.kiwixmobile.core.page.history.adapter.WebViewHistoryItem
|
||||
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
|
||||
@ -157,6 +158,9 @@ class CustomReaderFragment : CoreReaderFragment() {
|
||||
openHomeScreen()
|
||||
}
|
||||
|
||||
// Since custom apps do not have the bottomNavigationView, so returning null.
|
||||
override fun getBottomNavigationView(): BottomNavigationView? = null
|
||||
|
||||
/**
|
||||
* Restores the view state when the webViewHistory data is valid.
|
||||
* This method restores the tabs with webView pages history.
|
||||
@ -304,19 +308,13 @@ class CustomReaderFragment : CoreReaderFragment() {
|
||||
* provided configuration. It takes into account whether read aloud and tabs are enabled or disabled
|
||||
* and creates the menu accordingly.
|
||||
*/
|
||||
override fun createMainMenu(menu: Menu?): MainMenu? {
|
||||
return menu?.let {
|
||||
menuFactory?.create(
|
||||
it,
|
||||
webViewList,
|
||||
urlIsValid(),
|
||||
this,
|
||||
BuildConfig.DISABLE_READ_ALOUD,
|
||||
BuildConfig.DISABLE_TABS,
|
||||
BuildConfig.DISABLE_TITLE
|
||||
)
|
||||
}
|
||||
}
|
||||
override fun createMainMenu(menu: Menu?): ReaderMenuState? =
|
||||
ReaderMenuState(
|
||||
this,
|
||||
disableReadAloud = BuildConfig.DISABLE_READ_ALOUD,
|
||||
disableTabs = BuildConfig.DISABLE_TABS,
|
||||
disableSearch = BuildConfig.DISABLE_TITLE
|
||||
)
|
||||
|
||||
/**
|
||||
* Overrides the method to control the functionality of showing the "Open In New Tab" dialog.
|
||||
|
Loading…
x
Reference in New Issue
Block a user