diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt
index 089ffe209..c2cbaeb3a 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt
+++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt
@@ -197,7 +197,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
progressBar?.progress = 0
contentFrame?.visibility = View.VISIBLE
}
- mainMenu?.showWebViewOptions(true)
+ readerMenuState?.showWebViewOptions(true)
if (webViewList.isEmpty()) {
exitBook(shouldCloseZimBook)
} else {
@@ -231,7 +231,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
super.onCreateOptionsMenu(menu, menuInflater)
if (zimReaderContainer?.zimFileReader == null) {
- mainMenu?.hideBookSpecificMenuItems()
+ readerMenuState?.hideBookSpecificMenuItems()
}
}
diff --git a/core/detekt_baseline.xml b/core/detekt_baseline.xml
index 09091af73..c20fc03cd 100644
--- a/core/detekt_baseline.xml
+++ b/core/detekt_baseline.xml
@@ -13,7 +13,7 @@
LongParameterList:MainMenu.kt$MainMenu.Factory$( menu: Menu, webViews: MutableList<KiwixWebView>, urlIsValid: Boolean, menuClickListener: MenuClickListener, disableReadAloud: Boolean, disableTabs: Boolean )
LongParameterList:PageTestHelpers.kt$( bookmarkTitle: String = "bookmarkTitle", isSelected: Boolean = false, id: Long = 2, zimId: String = "zimId", zimName: String = "zimName", zimFilePath: String = "zimFilePath", bookmarkUrl: String = "bookmarkUrl", favicon: String = "favicon" )
LongParameterList:Repository.kt$Repository$( private val libkiwixBookOnDisk: LibkiwixBookOnDisk, private val libkiwixBookmarks: LibkiwixBookmarks, private val historyRoomDao: HistoryRoomDao, private val webViewHistoryRoomDao: WebViewHistoryRoomDao, private val notesRoomDao: NotesRoomDao, private val languageDao: NewLanguagesDao, private val recentSearchRoomDao: RecentSearchRoomDao, private val zimReaderContainer: ZimReaderContainer )
- LongParameterList:ToolbarScrollingKiwixWebView.kt$ToolbarScrollingKiwixWebView$( context: Context, callback: WebViewCallback, attrs: AttributeSet, nonVideoView: ViewGroup, videoView: ViewGroup, webViewClient: CoreWebViewClient, private val toolbarView: View, private val bottomBarView: View, sharedPreferenceUtil: SharedPreferenceUtil, private val parentNavigationBar: View? = null )
+ LongParameterList:ToolbarScrollingKiwixWebView.kt$ToolbarScrollingKiwixWebView$( context: Context, callback: WebViewCallback, attrs: AttributeSet, nonVideoView: ViewGroup?, videoView: ViewGroup?, webViewClient: CoreWebViewClient, sharedPreferenceUtil: SharedPreferenceUtil, private val parentNavigationBar: View? = null )
MagicNumber:ArticleCount.kt$ArticleCount$3
MagicNumber:CompatFindActionModeCallback.kt$CompatFindActionModeCallback$100
MagicNumber:DownloadItem.kt$DownloadItem$1000L
@@ -23,6 +23,7 @@
MagicNumber:JNIInitialiser.kt$JNIInitialiser$1024
MagicNumber:Byte.kt$Byte$1024.0
MagicNumber:MainMenu.kt$MainMenu$99
+ MagicNumber:ReaderMenuState.kt$ReaderMenuState$99
MagicNumber:OnSwipeTouchListener.kt$OnSwipeTouchListener.GestureListener$100
MagicNumber:SearchResultGenerator.kt$ZimSearchResultGenerator$200
MagicNumber:Seconds.kt$Seconds$24
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt
index d06897d1d..bf87ad94c 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt
@@ -74,6 +74,7 @@ import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.platform.ComposeView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.Group
@@ -106,6 +107,7 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
@@ -368,6 +370,7 @@ abstract class CoreReaderFragment :
private var isReadAloudServiceRunning = false
private var libkiwixBook: Book? = null
+ protected var readerMenuState: ReaderMenuState? = null
private var composeView: ComposeView? = null
protected val readerScreenState = mutableStateOf(
ReaderScreenState(
@@ -384,7 +387,7 @@ abstract class CoreReaderFragment :
onExitFullscreenClick = { closeFullScreen() },
showTtsControls = false,
onPauseTtsClick = { pauseTts() },
- pauseTtsButtonText = "",
+ pauseTtsButtonText = context?.getString(R.string.tts_pause).orEmpty(),
onStopTtsClick = { stopTts() },
kiwixWebViewList = webViewList,
bookmarkButtonItem = Triple(
@@ -497,6 +500,7 @@ abstract class CoreReaderFragment :
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
+ readerMenuState = createMainMenu()
composeView?.apply {
setContent {
val lazyListState = rememberLazyListState()
@@ -504,6 +508,13 @@ abstract class CoreReaderFragment :
LaunchedEffect(isBottomNavVisible) {
(requireActivity() as CoreMainActivity).toggleBottomNavigation(isBottomNavVisible)
}
+ LaunchedEffect(Unit) {
+ snapshotFlow { webViewList.size }
+ .distinctUntilChanged()
+ .collect { size ->
+ updateTabIcon(size)
+ }
+ }
LaunchedEffect(Unit) {
readerScreenState.update {
copy(
@@ -514,7 +525,7 @@ abstract class CoreReaderFragment :
}
ReaderScreen(
state = readerScreenState.value,
- actionMenuItems = emptyList(),
+ actionMenuItems = readerMenuState?.menuItems.orEmpty(),
navigationIcon = {
NavigationIcon(
iconItem = IconItem.Vector(Icons.Filled.Menu),
@@ -806,7 +817,7 @@ abstract class CoreReaderFragment :
}
private val isInTabSwitcher: Boolean
- get() = mainMenu?.isInTabSwitcher() == true
+ get() = readerMenuState?.isInTabSwitcher == true
private fun setupDocumentParser() {
documentParser = DocumentParser(object : SectionsListener {
@@ -865,7 +876,7 @@ abstract class CoreReaderFragment :
).apply {
registerAdapterDataObserver(object : AdapterDataObserver() {
override fun onChanged() {
- mainMenu?.updateTabIcon(itemCount)
+ readerMenuState?.updateTabIcon(itemCount)
}
})
}
@@ -948,7 +959,7 @@ abstract class CoreReaderFragment :
// reflected correctly.
tabsAdapter.notifyDataSetChanged()
}
- mainMenu?.showTabSwitcherOptions()
+ readerMenuState?.showTabSwitcherOptions()
}
/**
@@ -1019,7 +1030,7 @@ abstract class CoreReaderFragment :
}
progressBar?.hide()
selectTab(currentWebViewIndex)
- mainMenu?.showWebViewOptions(urlIsValid())
+ readerMenuState?.showWebViewOptions(urlIsValid())
// Reset the top margin of web views to 0 to remove any previously set margin
// This ensures that the web views are displayed without any additional top margin for kiwix custom apps.
setTopMarginToWebViews(0)
@@ -1267,17 +1278,21 @@ abstract class CoreReaderFragment :
object : OnSpeakingListener {
override fun onSpeakingStarted() {
requireActivity().runOnUiThread {
- mainMenu?.onTextToSpeechStartedTalking()
- ttsControls?.visibility = VISIBLE
+ readerMenuState?.onTextToSpeechStarted()
+ readerScreenState.update { copy(showTtsControls = true) }
setActionAndStartTTSService(ACTION_PAUSE_OR_RESUME_TTS, false)
}
}
override fun onSpeakingEnded() {
requireActivity().runOnUiThread {
- mainMenu?.onTextToSpeechStoppedTalking()
- ttsControls?.visibility = GONE
- pauseTTSButton?.setText(R.string.tts_pause)
+ readerMenuState?.onTextToSpeechStopped()
+ readerScreenState.update {
+ copy(
+ showTtsControls = false,
+ pauseTtsButtonText = context?.getString(R.string.tts_pause).orEmpty()
+ )
+ }
setActionAndStartTTSService(ACTION_STOP_TTS)
}
}
@@ -1293,12 +1308,16 @@ abstract class CoreReaderFragment :
when (focusChange) {
AudioManager.AUDIOFOCUS_LOSS -> {
if (tts?.currentTTSTask?.paused == false) tts?.pauseOrResume()
- pauseTTSButton?.setText(R.string.tts_resume)
+ readerScreenState.update {
+ copy(pauseTtsButtonText = context?.getString(R.string.tts_resume).orEmpty())
+ }
setActionAndStartTTSService(ACTION_PAUSE_OR_RESUME_TTS, true)
}
AudioManager.AUDIOFOCUS_GAIN -> {
- pauseTTSButton?.setText(R.string.tts_pause)
+ readerScreenState.update {
+ copy(pauseTtsButtonText = context?.getString(R.string.tts_pause).orEmpty())
+ }
setActionAndStartTTSService(ACTION_PAUSE_OR_RESUME_TTS, false)
}
}
@@ -1543,6 +1562,10 @@ abstract class CoreReaderFragment :
return webView
}
+ private fun updateTabIcon(size: Int) {
+ readerMenuState?.updateTabIcon(size)
+ }
+
private fun closeTab(index: Int) {
if (currentTtsWebViewIndex == index) {
onReadAloudStop()
@@ -1579,7 +1602,7 @@ abstract class CoreReaderFragment :
private fun reopenBook() {
hideNoBookOpenViews()
contentFrame?.visibility = VISIBLE
- mainMenu?.showBookSpecificMenuItems()
+ readerMenuState?.showBookSpecificMenuItems()
}
protected fun exitBook(shouldCloseZimBook: Boolean = true) {
@@ -1592,7 +1615,7 @@ abstract class CoreReaderFragment :
}
contentFrame?.visibility = GONE
hideProgressBar()
- mainMenu?.hideBookSpecificMenuItems()
+ readerMenuState?.hideBookSpecificMenuItems()
if (shouldCloseZimBook) {
closeZimBook()
}
@@ -1702,28 +1725,26 @@ abstract class CoreReaderFragment :
@Suppress("NestedBlockDepth")
override fun onReadAloudMenuClicked() {
if (requireActivity().hasNotificationPermission(sharedPreferenceUtil)) {
- ttsControls?.let { ttsControls ->
- when (ttsControls.visibility) {
- GONE -> {
- if (isBackToTopEnabled) {
- backToTopButton?.hide()
- }
- if (tts?.isInitialized == false) {
- isReadSelection = false
- tts?.initializeTTS()
- } else {
- startReadAloud()
- }
- }
-
- VISIBLE -> {
- if (isBackToTopEnabled) {
- backToTopButton?.show()
- }
- tts?.stop()
- }
-
- else -> {}
+ if (readerScreenState.value.showTtsControls) {
+ // currently TTS is running
+ if (isBackToTopEnabled) {
+ readerScreenState.update { copy(showBackToTopButton = true) }
+ backToTopButton?.show()
+ }
+ tts?.stop()
+ } else {
+ // TTS is not running.
+ if (isBackToTopEnabled) {
+ readerScreenState.update { copy(showBackToTopButton = false) }
+ }
+ readerScreenState.update {
+ copy(pauseTtsButtonText = context?.getString(R.string.tts_pause).orEmpty())
+ }
+ if (tts?.isInitialized == false) {
+ isReadSelection = false
+ tts?.initializeTTS()
+ } else {
+ startReadAloud()
}
}
} else {
@@ -1945,7 +1966,7 @@ abstract class CoreReaderFragment :
if (!isFromManageExternalLaunch) {
openArticle(UNINITIALISER_ADDRESS)
}
- mainMenu?.onFileOpened(urlIsValid())
+ readerMenuState?.onFileOpened(urlIsValid())
setUpBookmarks(zimFileReader)
} ?: kotlin.run {
// If the ZIM file is not opened properly (especially for ZIM chunks), exit the book to
@@ -2558,9 +2579,10 @@ abstract class CoreReaderFragment :
* WARNING: If modifying this method, ensure thorough testing with custom apps
* to verify proper functionality.
*/
- protected open fun createMainMenu(menu: Menu?): ReaderMenuState? =
+ protected open fun createMainMenu(): ReaderMenuState =
ReaderMenuState(
this,
+ isUrlValidInitially = urlIsValid(),
disableReadAloud = false,
disableTabs = false,
disableSearch = false
@@ -2799,6 +2821,7 @@ abstract class CoreReaderFragment :
}
override fun webViewTitleUpdated(title: String) {
+ updateTabIcon(webViewList.size)
tabsAdapter?.notifyDataSetChanged()
}
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderMenuState.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderMenuState.kt
index abdf44096..bc03c5b9d 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderMenuState.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderMenuState.kt
@@ -18,16 +18,39 @@
package org.kiwix.kiwixmobile.core.main.reader
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
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
+import org.kiwix.kiwixmobile.core.ui.theme.Black
+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.ONE_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_TEXT_SIZE
+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.TWO_DP
const val READ_ALOUD_MENU_ITEM_TESTING_TAG = "readAloudMenuItemTestingTag"
const val TAKE_NOTE_MENU_ITEM_TESTING_TAG = "takeNoteMenuItemTestingTag"
@@ -38,6 +61,7 @@ const val TAB_MENU_ITEM_TESTING_TAG = "tabMenuItemTestingTag"
@Stable
class ReaderMenuState(
private val menuClickListener: MenuClickListener,
+ private val isUrlValidInitially: Boolean,
private val disableReadAloud: Boolean = false,
private val disableTabs: Boolean = false,
private val disableSearch: Boolean = false
@@ -52,50 +76,105 @@ class ReaderMenuState(
fun onSearchMenuClickedMenuClicked()
}
+ val menuItems = mutableStateListOf()
+
+ private val menuItemVisibility = mutableMapOf().apply {
+ put(MenuItemType.Search, true)
+ put(MenuItemType.TabSwitcher, true)
+ put(MenuItemType.AddNote, true)
+ put(MenuItemType.RandomArticle, true)
+ put(MenuItemType.Fullscreen, true)
+ put(MenuItemType.ReadAloud, true)
+ }
+
var isInTabSwitcher by mutableStateOf(false)
private set
- var isReadingAloud by mutableStateOf(false)
- private set
+ private var isReadingAloud by mutableStateOf(false)
- var webViewCount by mutableStateOf(0)
- var urlIsValid by mutableStateOf(false)
- var zimFileReaderAvailable by mutableStateOf(false)
+ private var webViewCount by mutableStateOf(0)
+ private var urlIsValid by mutableStateOf(false)
- fun onTabsChanged(count: Int) {
+ fun updateTabIcon(count: Int) {
webViewCount = count
+ updateMenuItems()
}
- fun onUrlValidityChanged(valid: Boolean) {
+ init {
+ showWebViewOptions(isUrlValidInitially)
+ }
+
+ fun showWebViewOptions(valid: Boolean) {
+ isInTabSwitcher = false
urlIsValid = valid
+ setVisibility(
+ urlIsValid,
+ MenuItemType.RandomArticle,
+ MenuItemType.Search,
+ MenuItemType.ReadAloud,
+ MenuItemType.Fullscreen,
+ MenuItemType.AddNote,
+ MenuItemType.TabSwitcher
+ )
}
- fun onZimFileReaderAvailable(available: Boolean) {
- zimFileReaderAvailable = available
+ fun onFileOpened(urlIsValid: Boolean) {
+ showWebViewOptions(urlIsValid)
}
fun onTextToSpeechStarted() {
isReadingAloud = true
+ updateMenuItems()
}
fun onTextToSpeechStopped() {
isReadingAloud = false
+ updateMenuItems()
}
- fun exitTabSwitcher() {
- isInTabSwitcher = false
+ fun hideBookSpecificMenuItems() {
+ setVisibility(
+ false,
+ MenuItemType.Search,
+ MenuItemType.TabSwitcher,
+ MenuItemType.RandomArticle,
+ MenuItemType.AddNote,
+ MenuItemType.ReadAloud
+ )
}
- @Suppress("LongMethod", "MagicNumber")
- fun getActionMenuItems(): List {
- if (isInTabSwitcher) {
- return emptyList()
- }
+ fun showBookSpecificMenuItems() {
+ setVisibility(
+ true,
+ MenuItemType.Search,
+ MenuItemType.TabSwitcher,
+ MenuItemType.RandomArticle,
+ MenuItemType.AddNote,
+ MenuItemType.ReadAloud
+ )
+ }
- val list = mutableListOf()
+ fun showTabSwitcherOptions() {
+ isInTabSwitcher = true
+ setVisibility(
+ false,
+ MenuItemType.RandomArticle,
+ MenuItemType.ReadAloud,
+ MenuItemType.AddNote,
+ MenuItemType.Fullscreen
+ )
+ }
- if (!disableSearch && urlIsValid) {
- list += ActionMenuItem(
+ private fun updateMenuItems() {
+ menuItems.clear()
+ addSearchMenuItem()
+ addTabMenuItem()
+ addReaderMenuItems()
+ }
+
+ private fun addSearchMenuItem() {
+ if (menuItemVisibility[MenuItemType.Search] == true && !disableSearch && urlIsValid) {
+ menuItems += ActionMenuItem(
icon = IconItem.Drawable(R.drawable.action_search),
contentDescription = R.string.search_label,
onClick = { menuClickListener.onSearchMenuClickedMenuClicked() },
@@ -103,11 +182,13 @@ class ReaderMenuState(
testingTag = SEARCH_ICON_TESTING_TAG
)
}
+ }
- if (!disableTabs) {
+ private fun addTabMenuItem() {
+ if (!disableTabs && urlIsValid) {
val tabLabel = if (webViewCount > 99) ":D" else "$webViewCount"
- list += ActionMenuItem(
- icon = IconItem.Vector(Icons.Default.Add),
+ menuItems += ActionMenuItem(
+ icon = null,
contentDescription = R.string.switch_tabs,
onClick = {
isInTabSwitcher = true
@@ -115,42 +196,102 @@ class ReaderMenuState(
},
isInOverflow = false,
iconButtonText = tabLabel,
- testingTag = TAB_MENU_ITEM_TESTING_TAG
+ testingTag = TAB_MENU_ITEM_TESTING_TAG,
+ customView = { TabSwitcherBadge(tabLabel = tabLabel) }
)
}
+ }
- 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
+ @Composable
+ fun TabSwitcherBadge(tabLabel: String, modifier: Modifier = Modifier) {
+ Box(
+ modifier = modifier
+ .size(MATERIAL_MINIMUM_HEIGHT_AND_WIDTH)
+ .padding(TWELVE_DP),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = modifier
+ .clip(RoundedCornerShape(TAB_SWITCHER_CORNER_RADIUS))
+ .background(Black)
+ .border(ONE_DP, White, RoundedCornerShape(TAB_SWITCHER_CORNER_RADIUS))
+ .padding(horizontal = SIX_DP, vertical = TWO_DP)
+ .defaultMinSize(minWidth = TWENTY_DP, minHeight = TWENTY_DP),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = tabLabel,
+ color = White,
+ fontWeight = FontWeight.Bold,
+ fontSize = TAB_SWITCHER_TEXT_SIZE,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
)
}
}
+ }
- return list
+ private fun addReaderMenuItems() {
+ if (!urlIsValid) return
+
+ if (menuItemVisibility[MenuItemType.Search] == true) {
+ menuItems += ActionMenuItem(
+ icon = IconItem.Drawable(R.drawable.ic_add_note),
+ contentDescription = R.string.take_notes,
+ onClick = { menuClickListener.onAddNoteMenuClicked() },
+ testingTag = TAKE_NOTE_MENU_ITEM_TESTING_TAG,
+ isInOverflow = true
+ )
+ }
+
+ if (menuItemVisibility[MenuItemType.RandomArticle] == true) {
+ menuItems += ActionMenuItem(
+ contentDescription = R.string.menu_random_article,
+ onClick = { menuClickListener.onRandomArticleMenuClicked() },
+ testingTag = RANDOM_ARTICLE_MENU_ITEM_TESTING_TAG,
+ isInOverflow = true
+ )
+ }
+
+ if (menuItemVisibility[MenuItemType.Fullscreen] == true) {
+ menuItems += ActionMenuItem(
+ contentDescription = R.string.menu_full_screen,
+ onClick = { menuClickListener.onFullscreenMenuClicked() },
+ testingTag = FULL_SCREEN_MENU_ITEM_TESTING_TAG,
+ isInOverflow = true
+ )
+ }
+
+ if (menuItemVisibility[MenuItemType.ReadAloud] == true && !disableReadAloud) {
+ menuItems += 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,
+ isInOverflow = true
+ )
+ }
+ }
+
+ private fun setVisibility(visible: Boolean, vararg types: MenuItemType) {
+ types.forEach {
+ if (it == MenuItemType.Search && disableSearch) {
+ menuItemVisibility[it] = false
+ } else {
+ menuItemVisibility[it] = visible
+ }
+ }
+ updateMenuItems()
}
}
+
+enum class MenuItemType {
+ Search,
+ TabSwitcher,
+ AddNote,
+ RandomArticle,
+ Fullscreen,
+ ReadAloud
+}
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreen.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreen.kt
index 5d43ef8c1..9673e6652 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreen.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/ReaderScreen.kt
@@ -150,15 +150,19 @@ fun ReaderScreen(
} else {
ShowZIMFileContent(state)
ShowProgressBarIfZIMFilePageIsLoading(state)
- TtsControls(state)
- BottomAppBarOfReaderScreen(
- state.bookmarkButtonItem,
- state.previousPageButtonItem,
- state.onHomeButtonClick,
- state.nextPageButtonItem,
- state.onTocClick,
- state.shouldShowBottomAppBar
- )
+ Column(
+ modifier = Modifier.align(Alignment.BottomCenter)
+ ) {
+ TtsControls(state)
+ BottomAppBarOfReaderScreen(
+ state.bookmarkButtonItem,
+ state.previousPageButtonItem,
+ state.onHomeButtonClick,
+ state.nextPageButtonItem,
+ state.onTocClick,
+ state.shouldShowBottomAppBar
+ )
+ }
ShowFullScreenView(state)
}
ShowDonationLayout(state)
@@ -225,9 +229,9 @@ private fun NoBookOpenView(
}
@Composable
-private fun BoxScope.TtsControls(state: ReaderScreenState) {
+private fun TtsControls(state: ReaderScreenState) {
if (state.showTtsControls) {
- Row(modifier = Modifier.align(Alignment.BottomCenter)) {
+ Row {
Button(
onClick = state.onPauseTtsClick,
modifier = Modifier
@@ -235,7 +239,7 @@ private fun BoxScope.TtsControls(state: ReaderScreenState) {
.alpha(TTS_BUTTONS_CONTROL_ALPHA)
) {
Text(
- text = state.pauseTtsButtonText,
+ text = state.pauseTtsButtonText.uppercase(),
fontWeight = FontWeight.Bold
)
}
@@ -247,7 +251,7 @@ private fun BoxScope.TtsControls(state: ReaderScreenState) {
.alpha(TTS_BUTTONS_CONTROL_ALPHA)
) {
Text(
- text = stringResource(R.string.stop),
+ text = stringResource(R.string.stop).uppercase(),
fontWeight = FontWeight.Bold
)
}
@@ -276,7 +280,7 @@ private fun BackToTopFab(state: ReaderScreenState) {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-private fun BoxScope.BottomAppBarOfReaderScreen(
+private fun BottomAppBarOfReaderScreen(
bookmarkButtonItem: Triple<() -> Unit, () -> Unit, Drawable>,
previousPageButtonItem: Triple<() -> Unit, () -> Unit, Boolean>,
onHomeButtonClick: () -> Unit,
@@ -288,7 +292,6 @@ private fun BoxScope.BottomAppBarOfReaderScreen(
BottomAppBar(
containerColor = Black,
contentColor = White,
- modifier = Modifier.align(Alignment.BottomCenter),
scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
) {
Row(
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/KiwixAppBar.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/KiwixAppBar.kt
index 59eead867..710c896d3 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/KiwixAppBar.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/KiwixAppBar.kt
@@ -18,8 +18,10 @@
package org.kiwix.kiwixmobile.core.ui.components
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
@@ -135,44 +137,13 @@ private fun AppBarTitle(
)
}
-@Suppress("LongMethod")
@Composable
private fun ActionMenu(actionMenuItems: List) {
var overflowExpanded by remember { mutableStateOf(false) }
Row {
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 {
- IconButton(
- enabled = menuItem.isEnabled,
- onClick = menuItem.onClick,
- modifier = modifier
- ) {
- Icon(
- painter = it.toPainter(),
- contentDescription = stringResource(menuItem.contentDescription),
- tint = if (menuItem.isEnabled) menuItem.iconTint else Color.Gray
- )
- }
- } ?: run {
- // Else show the textView button in menuItem.
- TextButton(
- enabled = menuItem.isEnabled,
- onClick = menuItem.onClick,
- modifier = modifier
- ) {
- Text(
- text = menuItem.iconButtonText.uppercase(),
- color = if (menuItem.isEnabled) Color.White else Color.Gray,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
- )
- }
- }
- }
+ MainMenuItems(mainActions)
if (overflowActions.isNotEmpty()) {
IconButton(onClick = { overflowExpanded = true }) {
Icon(
@@ -182,22 +153,77 @@ private fun ActionMenu(actionMenuItems: List) {
)
}
}
- DropdownMenu(
- expanded = overflowExpanded,
- onDismissRequest = { overflowExpanded = false }
- ) {
- overflowActions.forEach { menuItem ->
- DropdownMenuItem(
- text = {
- Text(text = menuItem.iconButtonText)
- },
- onClick = {
- overflowExpanded = false
- menuItem.onClick()
- },
- enabled = menuItem.isEnabled
- )
+ OverflowMenuItems(overflowExpanded, overflowActions) { overflowExpanded = false }
+ }
+}
+
+@Composable
+private fun MainMenuItems(mainActions: List) {
+ mainActions.forEach { menuItem ->
+ val modifier = menuItem.modifier.testTag(menuItem.testingTag)
+
+ menuItem.customView?.let { customComposable ->
+ Box(modifier = modifier.clickable(enabled = menuItem.isEnabled) { menuItem.onClick() }) {
+ customComposable()
}
+ } ?: run {
+ menuItem.icon?.let { iconItem ->
+ IconButton(
+ enabled = menuItem.isEnabled,
+ onClick = menuItem.onClick,
+ modifier = modifier
+ ) {
+ Icon(
+ painter = iconItem.toPainter(),
+ contentDescription = stringResource(menuItem.contentDescription),
+ tint = if (menuItem.isEnabled) menuItem.iconTint else Color.Gray
+ )
+ }
+ } ?: run {
+ TextButton(
+ enabled = menuItem.isEnabled,
+ onClick = menuItem.onClick,
+ modifier = modifier
+ ) {
+ Text(
+ text = menuItem.iconButtonText.uppercase(),
+ color = if (menuItem.isEnabled) Color.White else Color.Gray,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun OverflowMenuItems(
+ overflowExpanded: Boolean,
+ overflowActions: List,
+ onDismiss: () -> Unit
+) {
+ DropdownMenu(
+ expanded = overflowExpanded,
+ onDismissRequest = onDismiss
+ ) {
+ overflowActions.forEachIndexed { index, menuItem ->
+ DropdownMenuItem(
+ text = {
+ Column {
+ Text(
+ text = menuItem.iconButtonText.ifEmpty {
+ stringResource(id = menuItem.contentDescription)
+ }
+ )
+ }
+ },
+ onClick = {
+ onDismiss()
+ menuItem.onClick()
+ },
+ enabled = menuItem.isEnabled
+ )
}
}
}
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/ui/models/ActionMenuItem.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/ui/models/ActionMenuItem.kt
index ae5b7ea0f..4c9fa935e 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/ui/models/ActionMenuItem.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/ui/models/ActionMenuItem.kt
@@ -19,6 +19,7 @@
package org.kiwix.kiwixmobile.core.ui.models
import androidx.annotation.StringRes
+import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import org.kiwix.kiwixmobile.core.ui.theme.White
@@ -32,5 +33,6 @@ data class ActionMenuItem(
val iconButtonText: String = "",
val testingTag: String,
val modifier: Modifier = Modifier,
- val isInOverflow: Boolean = false
+ val isInOverflow: Boolean = false,
+ val customView: (@Composable () -> Unit)? = null
)
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt
index 8529c2a14..2c81cff90 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt
@@ -185,4 +185,6 @@ object ComposeDimens {
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
+ val TAB_SWITCHER_TEXT_SIZE = 12.sp
+ const val TAB_SWITCHER_CORNER_RADIUS = 10
}
diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt
index c79ce3c82..77d79383c 100644
--- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt
+++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt
@@ -308,9 +308,10 @@ 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?): ReaderMenuState? =
+ override fun createMainMenu(): ReaderMenuState =
ReaderMenuState(
this,
+ isUrlValidInitially = urlIsValid(),
disableReadAloud = BuildConfig.DISABLE_READ_ALOUD,
disableTabs = BuildConfig.DISABLE_TABS,
disableSearch = BuildConfig.DISABLE_TITLE