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