Thin screen toolbar with search

This commit is contained in:
Syer10
2022-02-04 19:58:58 -05:00
parent 53061d95bd
commit 2ca87bf93e
9 changed files with 592 additions and 107 deletions

View File

@@ -39,6 +39,8 @@
<string name="action_refresh_manga">Refresh</string>
<string name="action_retry">Retry</string>
<string name="action_close">Close</string>
<string name="action_search">Search</string>
<string name="action_searching">Search…</string>
<!-- Locations -->
<string name="location_library">Library</string>

View File

@@ -0,0 +1,153 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.ui.base.navigation
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.LocalContentAlpha
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.util.fastForEach
// Originally from https://gist.github.com/MachFour/369ebb56a66e2f583ebfb988dda2decf
// Essentially a wrapper around a lambda function to give it a name and icon
// akin to Android menu XML entries.
// As an item on the action bar, the action will be displayed with an IconButton
// with the given icon, if not null. Otherwise, the string from the name resource is used.
// In overflow menu, item will always be displayed as text.
@Stable
data class ActionItem(
val name: String,
val icon: ImageVector? = null,
val overflowMode: OverflowMode = OverflowMode.IF_NECESSARY,
val enabled: Boolean = true,
val doAction: () -> Unit,
) {
// allow 'calling' the action like a function
operator fun invoke() = doAction()
}
// Whether action items are allowed to overflow into a dropdown menu - or NOT SHOWN to hide
enum class OverflowMode {
NEVER_OVERFLOW, IF_NECESSARY, ALWAYS_OVERFLOW, NOT_SHOWN
}
// Note: should be used in a RowScope
@Composable
fun ActionMenu(
items: List<ActionItem>,
numIcons: Int = 3, // includes overflow menu icon; may be overridden by NEVER_OVERFLOW
menuVisible: MutableState<Boolean> = remember { mutableStateOf(false) },
iconItem: @Composable (onClick: () -> Unit, name: String, icon: ImageVector, enabled: Boolean) -> Unit
) {
if (items.isEmpty()) {
return
}
// decide how many action items to show as icons
val (appbarActions, overflowActions) = derivedStateOf {
separateIntoIconAndOverflow(items, numIcons)
}.value
appbarActions.fastForEach { item ->
key(item.hashCode()) {
if (item.icon != null) {
iconItem(item.doAction, item.name, item.icon, item.enabled)
} else {
TextButton(onClick = item.doAction, enabled = item.enabled) {
Text(
text = item.name,
color = MaterialTheme.colors.onPrimary.copy(alpha = LocalContentAlpha.current),
)
}
}
}
}
if (overflowActions.isNotEmpty()) {
IconButton(onClick = { menuVisible.value = true }) {
Icon(Icons.Default.MoreVert, "More actions")
}
DropdownMenu(
expanded = menuVisible.value,
onDismissRequest = { menuVisible.value = false },
) {
overflowActions.fastForEach { item ->
key(item.hashCode()) {
DropdownMenuItem(
onClick = {
menuVisible.value = false
item()
},
enabled = item.enabled
) {
//Icon(item.icon, item.name) just have text in the overflow menu
Text(item.name)
}
}
}
}
}
}
private fun separateIntoIconAndOverflow(
items: List<ActionItem>,
numIcons: Int
): Pair<List<ActionItem>, List<ActionItem>> {
var (iconCount, overflowCount, preferIconCount) = Triple(0, 0, 0)
for (item in items) {
when (item.overflowMode) {
OverflowMode.NEVER_OVERFLOW -> iconCount++
OverflowMode.IF_NECESSARY -> preferIconCount++
OverflowMode.ALWAYS_OVERFLOW -> overflowCount++
OverflowMode.NOT_SHOWN -> {}
}
}
val needsOverflow = iconCount + preferIconCount > numIcons || overflowCount > 0
val actionIconSpace = numIcons - (if (needsOverflow) 1 else 0)
val iconActions = ArrayList<ActionItem>()
val overflowActions = ArrayList<ActionItem>()
var iconsAvailableBeforeOverflow = actionIconSpace - iconCount
for (item in items) {
when (item.overflowMode) {
OverflowMode.NEVER_OVERFLOW -> {
iconActions.add(item)
}
OverflowMode.ALWAYS_OVERFLOW -> {
overflowActions.add(item)
}
OverflowMode.IF_NECESSARY -> {
if (iconsAvailableBeforeOverflow > 0) {
iconActions.add(item)
iconsAvailableBeforeOverflow--
} else {
overflowActions.add(item)
}
}
OverflowMode.NOT_SHOWN -> {
// skip
}
}
}
return iconActions to overflowActions
}

View File

@@ -14,9 +14,10 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -26,34 +27,39 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.AppBarDefaults
import androidx.compose.material.Card
import androidx.compose.material.ContentAlpha
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.LocalContentAlpha
import androidx.compose.material.LocalContentColor
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ProvideTextStyle
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.contentColorFor
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.Close
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material.icons.rounded.Sort
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
@@ -64,6 +70,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import ca.gosyer.i18n.MR
import ca.gosyer.uicore.components.BoxWithTooltipSurface
import ca.gosyer.uicore.components.keyboardHandler
import ca.gosyer.uicore.resources.stringResource
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
@@ -75,13 +82,60 @@ fun Toolbar(
closable: Boolean = (navigator?.size ?: 0) > 1,
onClose: () -> Unit = { navigator?.pop() },
modifier: Modifier = Modifier,
actions: @Composable RowScope.() -> Unit = {},
actions: @Composable () -> List<ActionItem> = { emptyList() },
backgroundColor: Color = MaterialTheme.colors.surface, // CustomColors.current.bars,
contentColor: Color = contentColorFor(backgroundColor), // CustomColors.current.onBars,
elevation: Dp = AppBarDefaults.TopAppBarElevation,
elevation: Dp = Dp.Hairline,
searchText: String? = null,
search: ((String) -> Unit)? = null,
searchSubmit: (() -> Unit)? = null,
) {
BoxWithConstraints {
if (maxWidth > 600.dp) {
WideToolbar(
name = name,
closable = closable,
onClose = onClose,
modifier = modifier,
actions = actions,
backgroundColor = backgroundColor,
contentColor = contentColor,
elevation = elevation,
searchText = searchText,
search = search,
searchSubmit = searchSubmit
)
} else {
ThinToolbar(
name = name,
closable = closable,
onClose = onClose,
modifier = modifier,
actions = actions,
backgroundColor = backgroundColor,
contentColor = contentColor,
elevation = elevation,
searchText = searchText,
search = search,
searchSubmit = searchSubmit
)
}
}
}
@Composable
private fun WideToolbar(
name: String,
closable: Boolean,
onClose: () -> Unit,
modifier: Modifier,
actions: @Composable () -> List<ActionItem> = { emptyList() },
backgroundColor: Color,
contentColor: Color,
elevation: Dp,
searchText: String?,
search: ((String) -> Unit)?,
searchSubmit: (() -> Unit)?,
) {
Surface(
modifier = modifier,
@@ -118,9 +172,153 @@ fun Toolbar(
}
Row {
actions()
ActionMenu(actions()) { onClick: () -> Unit, name: String, icon: ImageVector, enabled: Boolean ->
TextActionIcon(
onClick = onClick,
text = name,
icon = icon,
enabled = enabled
)
}
if (closable) {
TextActionIcon(onClick = onClose, stringResource(MR.strings.action_close), Icons.Rounded.Close)
TextActionIcon(
onClick = onClose,
text = stringResource(MR.strings.action_close),
icon = Icons.Rounded.Close
)
}
}
}
}
}
@Composable
private fun ThinToolbar(
name: String,
closable: Boolean,
onClose: () -> Unit,
modifier: Modifier,
actions: @Composable () -> List<ActionItem> = { emptyList() },
backgroundColor: Color,
contentColor: Color,
elevation: Dp,
searchText: String?,
search: ((String) -> Unit)?,
searchSubmit: (() -> Unit)?,
) {
var searchMode by remember { mutableStateOf(!searchText.isNullOrEmpty()) }
Surface(
color = backgroundColor,
contentColor = contentColor,
elevation = elevation,
shape = RectangleShape,
modifier = modifier
) {
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Row(
Modifier.fillMaxWidth()
.padding(AppBarDefaults.ContentPadding)
.height(56.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
if (!closable && !searchMode) {
Spacer(Modifier.width(12.dp))
} else {
Row(Modifier.width(68.dp), verticalAlignment = Alignment.CenterVertically) {
CompositionLocalProvider(
LocalContentAlpha provides ContentAlpha.high,
) {
IconButton(
onClick = {
if (searchMode) {
search?.invoke("")
searchSubmit?.invoke()
searchMode = false
} else {
onClose()
}
}
) {
Icon(
Icons.Rounded.ArrowBack,
stringResource(MR.strings.action_close)
)
}
}
}
}
if (searchMode) {
Row(
Modifier.fillMaxHeight().weight(1f),
verticalAlignment = Alignment.CenterVertically
) {
BasicTextField(
value = searchText.orEmpty(),
onValueChange = search ?: {},
modifier = Modifier.fillMaxWidth()
.keyboardHandler(singleLine = true) {
searchSubmit?.invoke()
it.clearFocus()
},
textStyle = LocalTextStyle.current.copy(color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium)),
cursorBrush = SolidColor(MaterialTheme.colors.primary),
keyboardActions = KeyboardActions { searchSubmit?.invoke() },
maxLines = 1,
singleLine = true,
decorationBox = { innerTextField ->
Box(contentAlignment = Alignment.CenterStart) {
if (searchText.isNullOrEmpty()) {
Text(stringResource(MR.strings.action_searching))
}
innerTextField()
}
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search)
)
}
} else {
Row(
Modifier.fillMaxHeight().weight(1f),
verticalAlignment = Alignment.CenterVertically
) {
ProvideTextStyle(value = MaterialTheme.typography.h6) {
CompositionLocalProvider(
LocalContentAlpha provides ContentAlpha.high,
) {
Text(name)
}
}
}
}
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
Row(
Modifier.fillMaxHeight(),
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
) {
if (search != null && !searchMode) {
IconButton(onClick = { searchMode = true }) {
Icon(Icons.Rounded.Search, stringResource(MR.strings.action_search))
}
}
ActionMenu(
actions(),
if (searchMode) {
1
} else {
3
}
) { onClick: () -> Unit, name: String, icon: ImageVector, enabled: Boolean ->
IconButton(onClick = onClick, enabled = enabled) {
Icon(icon, name)
}
}
}
}
}
}
@@ -147,17 +345,11 @@ private fun SearchBox(
searchText.orEmpty(),
onValueChange = { search?.invoke(it) },
singleLine = true,
modifier = Modifier.fillMaxWidth().then(
if (searchSubmit != null) {
Modifier.onPreviewKeyEvent { event ->
(event.key == Key.Enter && event.type == KeyEventType.KeyDown).also {
if (it) {
searchSubmit()
}
}
}
} else Modifier
),
modifier = Modifier.fillMaxWidth()
.keyboardHandler(singleLine = true) {
searchSubmit?.invoke()
it.clearFocus()
},
textStyle = TextStyle(contentColor, 18.sp),
cursorBrush = SolidColor(contentColor.copy(alpha = 0.50F)),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search)

View File

@@ -35,6 +35,7 @@ import androidx.compose.material.icons.rounded.MoreVert
import androidx.compose.material.icons.rounded.Pause
import androidx.compose.material.icons.rounded.PlayArrow
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -46,7 +47,7 @@ import ca.gosyer.data.download.model.DownloadChapter
import ca.gosyer.data.download.model.DownloaderStatus
import ca.gosyer.data.models.Chapter
import ca.gosyer.i18n.MR
import ca.gosyer.ui.base.navigation.ActionIcon
import ca.gosyer.ui.base.navigation.ActionItem
import ca.gosyer.ui.base.navigation.Toolbar
import ca.gosyer.uicore.components.DropdownIconButton
import ca.gosyer.uicore.components.MangaListItem
@@ -74,12 +75,12 @@ fun DownloadsScreenContent(
Toolbar(
stringResource(MR.strings.location_downloads),
actions = {
if (downloadStatus == DownloaderStatus.Started) {
ActionIcon(onClick = pauseDownloading, stringResource(MR.strings.action_pause), Icons.Rounded.Pause)
} else {
ActionIcon(onClick = startDownloading, stringResource(MR.strings.action_continue), Icons.Rounded.PlayArrow)
}
ActionIcon(onClick = clearQueue, stringResource(MR.strings.action_clear_queue), Icons.Rounded.ClearAll)
getActionItems(
downloadStatus = downloadStatus,
startDownloading = startDownloading,
pauseDownloading = pauseDownloading,
clearQueue = clearQueue
)
}
)
}
@@ -174,3 +175,29 @@ fun DownloadsItem(
Spacer(Modifier.width(16.dp))
}
}
@Stable
@Composable
private fun getActionItems(
downloadStatus: DownloaderStatus,
startDownloading: () -> Unit,
pauseDownloading: () -> Unit,
clearQueue: () -> Unit,
) : List<ActionItem> {
return listOf(
if (downloadStatus == DownloaderStatus.Started) {
ActionItem(
stringResource(MR.strings.action_pause),
Icons.Rounded.Pause,
doAction = pauseDownloading
)
} else {
ActionItem(
stringResource(MR.strings.action_continue),
Icons.Rounded.PlayArrow,
doAction = startDownloading
)
},
ActionItem(stringResource(MR.strings.action_clear_queue), Icons.Rounded.ClearAll, doAction = clearQueue)
)
}

View File

@@ -32,6 +32,7 @@ import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Translate
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@@ -48,7 +49,7 @@ import ca.gosyer.data.models.Extension
import ca.gosyer.i18n.MR
import ca.gosyer.presentation.build.BuildKonfig
import ca.gosyer.ui.base.WindowDialog
import ca.gosyer.ui.base.navigation.TextActionIcon
import ca.gosyer.ui.base.navigation.ActionItem
import ca.gosyer.ui.base.navigation.Toolbar
import ca.gosyer.uicore.components.LoadingScreen
import ca.gosyer.uicore.image.KamelImage
@@ -132,15 +133,10 @@ fun ExtensionsToolbar(
searchText = searchText,
search = search,
actions = {
TextActionIcon(
{
val enabledLangs = MutableStateFlow(currentEnabledLangs.value)
LanguageDialog(enabledLangs, getSourceLanguages().toList()) {
setEnabledLanguages(enabledLangs.value)
}
},
stringResource(MR.strings.enabled_languages),
Icons.Rounded.Translate
getActionItems(
currentEnabledLangs = currentEnabledLangs,
getSourceLanguages = getSourceLanguages,
setEnabledLanguages = setEnabledLanguages
)
}
)
@@ -235,3 +231,23 @@ fun LanguageDialog(enabledLangsFlow: MutableStateFlow<Set<String>>, availableLan
}
}
}
@Stable
@Composable
private fun getActionItems(
currentEnabledLangs: StateFlow<Set<String>>,
getSourceLanguages: () -> Set<String>,
setEnabledLanguages: (Set<String>) -> Unit
): List<ActionItem> {
return listOf(
ActionItem(
stringResource(MR.strings.enabled_languages),
Icons.Rounded.Translate
) {
val enabledLangs = MutableStateFlow(currentEnabledLangs.value)
LanguageDialog(enabledLangs, getSourceLanguages().toList()) {
setEnabledLanguages(enabledLangs.value)
}
}
)
}

View File

@@ -6,7 +6,6 @@
package ca.gosyer.ui.manga.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.VerticalScrollbar
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
@@ -25,6 +24,7 @@ import androidx.compose.material.icons.rounded.Label
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -32,7 +32,7 @@ import ca.gosyer.data.models.Category
import ca.gosyer.data.models.Manga
import ca.gosyer.i18n.MR
import ca.gosyer.ui.base.chapter.ChapterDownloadItem
import ca.gosyer.ui.base.navigation.TextActionIcon
import ca.gosyer.ui.base.navigation.ActionItem
import ca.gosyer.ui.base.navigation.Toolbar
import ca.gosyer.ui.reader.openReaderMenu
import ca.gosyer.uicore.components.ErrorScreen
@@ -73,28 +73,14 @@ fun MangaScreenContent(
Toolbar(
stringResource(MR.strings.location_manga),
actions = {
AnimatedVisibility(categoriesExist && manga?.inLibrary == true) {
TextActionIcon(
setCategories,
stringResource(MR.strings.edit_categories),
Icons.Rounded.Label
)
}
TextActionIcon(
toggleFavorite,
stringResource(if (manga?.inLibrary == true) MR.strings.action_remove_favorite else MR.strings.action_favorite),
if (manga?.inLibrary == true) {
Icons.Rounded.Favorite
} else {
Icons.Rounded.FavoriteBorder
},
manga != null
)
TextActionIcon(
refreshManga,
stringResource(MR.strings.action_refresh_manga),
Icons.Rounded.Refresh,
!isLoading
getActionItems(
refreshManga = refreshManga,
refreshMangaEnabled = !isLoading,
categoryItemVisible = categoriesExist && manga?.inLibrary == true,
setCategories = setCategories,
inLibrary = manga?.inLibrary == true,
toggleFavorite = toggleFavorite,
favoritesButtonEnabled = manga != null
)
}
)
@@ -150,3 +136,41 @@ fun MangaScreenContent(
}
}
}
@Composable
@Stable
private fun getActionItems(
refreshManga: () -> Unit,
refreshMangaEnabled: Boolean,
categoryItemVisible: Boolean,
setCategories: () -> Unit,
inLibrary: Boolean,
toggleFavorite: () -> Unit,
favoritesButtonEnabled: Boolean
): List<ActionItem> {
return listOfNotNull(
ActionItem(
name = stringResource(MR.strings.action_refresh_manga),
icon = Icons.Rounded.Refresh,
doAction = refreshManga,
enabled = refreshMangaEnabled
),
if (categoryItemVisible) {
ActionItem(
name = stringResource(MR.strings.edit_categories),
icon = Icons.Rounded.Label,
doAction = setCategories
)
} else null,
ActionItem(
name = stringResource(if (inLibrary) MR.strings.action_remove_favorite else MR.strings.action_favorite),
icon = if (inLibrary) {
Icons.Rounded.Favorite
} else {
Icons.Rounded.FavoriteBorder
},
doAction = toggleFavorite,
enabled = favoritesButtonEnabled
)
)
}

View File

@@ -26,6 +26,7 @@ import androidx.compose.material.icons.rounded.NewReleases
import androidx.compose.material.icons.rounded.Settings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
@@ -34,7 +35,7 @@ import androidx.compose.ui.unit.dp
import ca.gosyer.data.models.Manga
import ca.gosyer.data.models.Source
import ca.gosyer.i18n.MR
import ca.gosyer.ui.base.navigation.TextActionIcon
import ca.gosyer.ui.base.navigation.ActionItem
import ca.gosyer.ui.base.navigation.Toolbar
import ca.gosyer.ui.sources.browse.filter.SourceFiltersMenu
import ca.gosyer.ui.sources.browse.filter.model.SourceFiltersView
@@ -149,44 +150,21 @@ fun SourceToolbar(
search = onSearch,
searchSubmit = onSubmitSearch,
actions = {
if (source.isConfigurable) {
TextActionIcon(
{
onSourceSettingsClick(source.id)
},
stringResource(MR.strings.location_settings),
Icons.Rounded.Settings
)
}
if (showFilterButton) {
TextActionIcon(
{
onToggleFiltersClick(!showingFilters)
},
stringResource(MR.strings.filter_source),
Icons.Rounded.FilterList,
!isLatest
)
}
if (showLatestButton) {
TextActionIcon(
{
onClickMode(!isLatest)
},
stringResource(
if (isLatest) {
MR.strings.move_to_browse
} else {
MR.strings.move_to_latest
}
),
if (isLatest) {
Icons.Rounded.Explore
} else {
Icons.Rounded.NewReleases
}
)
}
getActionItems(
isConfigurable = source.isConfigurable,
onSourceSettingsClick = {
onSourceSettingsClick(source.id)
},
isLatest = isLatest,
showLatestButton = showLatestButton,
showFilterButton = showFilterButton,
onToggleFiltersClick = {
onToggleFiltersClick(!showingFilters)
},
onClickMode = {
onClickMode(!isLatest)
}
)
}
)
}
@@ -227,3 +205,50 @@ private fun MangaTable(
}
}
}
@Composable
@Stable
private fun getActionItems(
isConfigurable: Boolean,
onSourceSettingsClick: () -> Unit,
isLatest: Boolean,
showLatestButton: Boolean,
showFilterButton: Boolean,
onToggleFiltersClick: () -> Unit,
onClickMode: () -> Unit
): List<ActionItem> {
return listOfNotNull(
if (isConfigurable) {
ActionItem(
name = stringResource(MR.strings.location_settings),
icon = Icons.Rounded.Settings,
doAction = onSourceSettingsClick
)
} else null,
if (showFilterButton) {
ActionItem(
name = stringResource(MR.strings.filter_source),
icon = Icons.Rounded.FilterList,
doAction = onToggleFiltersClick,
enabled = !isLatest
)
} else null,
if (showLatestButton) {
ActionItem(
name = stringResource(
if (isLatest) {
MR.strings.move_to_browse
} else {
MR.strings.move_to_latest
}
),
icon = if (isLatest) {
Icons.Rounded.Explore
} else {
Icons.Rounded.NewReleases
},
doAction = onClickMode
)
} else null
)
}

View File

@@ -33,6 +33,7 @@ import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Translate
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
@@ -41,7 +42,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import ca.gosyer.data.models.Source
import ca.gosyer.i18n.MR
import ca.gosyer.ui.base.navigation.TextActionIcon
import ca.gosyer.ui.base.navigation.ActionItem
import ca.gosyer.ui.base.navigation.Toolbar
import ca.gosyer.ui.extensions.components.LanguageDialog
import ca.gosyer.uicore.components.LoadingScreen
@@ -109,15 +110,13 @@ fun SourceHomeScreenToolbar(
Toolbar(
stringResource(MR.strings.location_sources),
actions = {
TextActionIcon(
{
getActionItems(
onEnabledLanguagesClick = {
val enabledLangs = MutableStateFlow(sourceLanguages.value)
LanguageDialog(enabledLangs, onGetEnabledLanguages().toList()) {
onSetEnabledLanguages(enabledLangs.value)
}
},
stringResource(MR.strings.enabled_languages),
Icons.Rounded.Translate
}
)
}
)
@@ -176,3 +175,17 @@ fun SourceItem(
}
}
}
@Composable
@Stable
private fun getActionItems(
onEnabledLanguagesClick: () -> Unit
): List<ActionItem> {
return listOf(
ActionItem(
stringResource(MR.strings.enabled_languages),
Icons.Rounded.Translate,
doAction = onEnabledLanguagesClick
)
)
}

View File

@@ -0,0 +1,33 @@
package ca.gosyer.uicore.components
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.platform.LocalFocusManager
/**
* A modifier to handle keyboard keys properly
*/
@OptIn(ExperimentalComposeUiApi::class)
fun Modifier.keyboardHandler(
singleLine: Boolean = false,
action: (FocusManager) -> Unit = { it.moveFocus(FocusDirection.Down) }
) = composed {
val focusManager = LocalFocusManager.current
Modifier.onPreviewKeyEvent {
if (
(it.key == Key.Tab || (singleLine && it.key == Key.Enter)) &&
it.type == KeyEventType.KeyDown
) {
action(focusManager)
true
} else false
}
}