mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-21 12:02:33 +01:00
Thin screen toolbar with search
This commit is contained in:
@@ -39,6 +39,8 @@
|
|||||||
<string name="action_refresh_manga">Refresh</string>
|
<string name="action_refresh_manga">Refresh</string>
|
||||||
<string name="action_retry">Retry</string>
|
<string name="action_retry">Retry</string>
|
||||||
<string name="action_close">Close</string>
|
<string name="action_close">Close</string>
|
||||||
|
<string name="action_search">Search</string>
|
||||||
|
<string name="action_searching">Search…</string>
|
||||||
|
|
||||||
<!-- Locations -->
|
<!-- Locations -->
|
||||||
<string name="location_library">Library</string>
|
<string name="location_library">Library</string>
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -14,9 +14,10 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
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.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
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.layout.width
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.AppBarDefaults
|
import androidx.compose.material.AppBarDefaults
|
||||||
import androidx.compose.material.Card
|
import androidx.compose.material.Card
|
||||||
import androidx.compose.material.ContentAlpha
|
import androidx.compose.material.ContentAlpha
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.LocalContentAlpha
|
||||||
import androidx.compose.material.LocalContentColor
|
import androidx.compose.material.LocalContentColor
|
||||||
|
import androidx.compose.material.LocalTextStyle
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.ProvideTextStyle
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.contentColorFor
|
import androidx.compose.material.contentColorFor
|
||||||
import androidx.compose.material.icons.Icons
|
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.Close
|
||||||
|
import androidx.compose.material.icons.rounded.Search
|
||||||
import androidx.compose.material.icons.rounded.Sort
|
import androidx.compose.material.icons.rounded.Sort
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.Composable
|
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.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.RectangleShape
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
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.semantics.Role
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
@@ -64,6 +70,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.uicore.components.BoxWithTooltipSurface
|
import ca.gosyer.uicore.components.BoxWithTooltipSurface
|
||||||
|
import ca.gosyer.uicore.components.keyboardHandler
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
@@ -75,13 +82,60 @@ fun Toolbar(
|
|||||||
closable: Boolean = (navigator?.size ?: 0) > 1,
|
closable: Boolean = (navigator?.size ?: 0) > 1,
|
||||||
onClose: () -> Unit = { navigator?.pop() },
|
onClose: () -> Unit = { navigator?.pop() },
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
actions: @Composable RowScope.() -> Unit = {},
|
actions: @Composable () -> List<ActionItem> = { emptyList() },
|
||||||
backgroundColor: Color = MaterialTheme.colors.surface, // CustomColors.current.bars,
|
backgroundColor: Color = MaterialTheme.colors.surface, // CustomColors.current.bars,
|
||||||
contentColor: Color = contentColorFor(backgroundColor), // CustomColors.current.onBars,
|
contentColor: Color = contentColorFor(backgroundColor), // CustomColors.current.onBars,
|
||||||
elevation: Dp = AppBarDefaults.TopAppBarElevation,
|
elevation: Dp = Dp.Hairline,
|
||||||
searchText: String? = null,
|
searchText: String? = null,
|
||||||
search: ((String) -> Unit)? = null,
|
search: ((String) -> Unit)? = null,
|
||||||
searchSubmit: (() -> 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(
|
Surface(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@@ -118,9 +172,153 @@ fun Toolbar(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
actions()
|
ActionMenu(actions()) { onClick: () -> Unit, name: String, icon: ImageVector, enabled: Boolean ->
|
||||||
|
TextActionIcon(
|
||||||
|
onClick = onClick,
|
||||||
|
text = name,
|
||||||
|
icon = icon,
|
||||||
|
enabled = enabled
|
||||||
|
)
|
||||||
|
}
|
||||||
if (closable) {
|
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(),
|
searchText.orEmpty(),
|
||||||
onValueChange = { search?.invoke(it) },
|
onValueChange = { search?.invoke(it) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth().then(
|
modifier = Modifier.fillMaxWidth()
|
||||||
if (searchSubmit != null) {
|
.keyboardHandler(singleLine = true) {
|
||||||
Modifier.onPreviewKeyEvent { event ->
|
searchSubmit?.invoke()
|
||||||
(event.key == Key.Enter && event.type == KeyEventType.KeyDown).also {
|
it.clearFocus()
|
||||||
if (it) {
|
},
|
||||||
searchSubmit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else Modifier
|
|
||||||
),
|
|
||||||
textStyle = TextStyle(contentColor, 18.sp),
|
textStyle = TextStyle(contentColor, 18.sp),
|
||||||
cursorBrush = SolidColor(contentColor.copy(alpha = 0.50F)),
|
cursorBrush = SolidColor(contentColor.copy(alpha = 0.50F)),
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search)
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import androidx.compose.material.icons.rounded.MoreVert
|
|||||||
import androidx.compose.material.icons.rounded.Pause
|
import androidx.compose.material.icons.rounded.Pause
|
||||||
import androidx.compose.material.icons.rounded.PlayArrow
|
import androidx.compose.material.icons.rounded.PlayArrow
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.download.model.DownloaderStatus
|
||||||
import ca.gosyer.data.models.Chapter
|
import ca.gosyer.data.models.Chapter
|
||||||
import ca.gosyer.i18n.MR
|
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.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.uicore.components.DropdownIconButton
|
import ca.gosyer.uicore.components.DropdownIconButton
|
||||||
import ca.gosyer.uicore.components.MangaListItem
|
import ca.gosyer.uicore.components.MangaListItem
|
||||||
@@ -74,12 +75,12 @@ fun DownloadsScreenContent(
|
|||||||
Toolbar(
|
Toolbar(
|
||||||
stringResource(MR.strings.location_downloads),
|
stringResource(MR.strings.location_downloads),
|
||||||
actions = {
|
actions = {
|
||||||
if (downloadStatus == DownloaderStatus.Started) {
|
getActionItems(
|
||||||
ActionIcon(onClick = pauseDownloading, stringResource(MR.strings.action_pause), Icons.Rounded.Pause)
|
downloadStatus = downloadStatus,
|
||||||
} else {
|
startDownloading = startDownloading,
|
||||||
ActionIcon(onClick = startDownloading, stringResource(MR.strings.action_continue), Icons.Rounded.PlayArrow)
|
pauseDownloading = pauseDownloading,
|
||||||
}
|
clearQueue = clearQueue
|
||||||
ActionIcon(onClick = clearQueue, stringResource(MR.strings.action_clear_queue), Icons.Rounded.ClearAll)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -174,3 +175,29 @@ fun DownloadsItem(
|
|||||||
Spacer(Modifier.width(16.dp))
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ import androidx.compose.material.Text
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Translate
|
import androidx.compose.material.icons.rounded.Translate
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -48,7 +49,7 @@ import ca.gosyer.data.models.Extension
|
|||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.presentation.build.BuildKonfig
|
import ca.gosyer.presentation.build.BuildKonfig
|
||||||
import ca.gosyer.ui.base.WindowDialog
|
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.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.uicore.components.LoadingScreen
|
import ca.gosyer.uicore.components.LoadingScreen
|
||||||
import ca.gosyer.uicore.image.KamelImage
|
import ca.gosyer.uicore.image.KamelImage
|
||||||
@@ -132,15 +133,10 @@ fun ExtensionsToolbar(
|
|||||||
searchText = searchText,
|
searchText = searchText,
|
||||||
search = search,
|
search = search,
|
||||||
actions = {
|
actions = {
|
||||||
TextActionIcon(
|
getActionItems(
|
||||||
{
|
currentEnabledLangs = currentEnabledLangs,
|
||||||
val enabledLangs = MutableStateFlow(currentEnabledLangs.value)
|
getSourceLanguages = getSourceLanguages,
|
||||||
LanguageDialog(enabledLangs, getSourceLanguages().toList()) {
|
setEnabledLanguages = setEnabledLanguages
|
||||||
setEnabledLanguages(enabledLangs.value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stringResource(MR.strings.enabled_languages),
|
|
||||||
Icons.Rounded.Translate
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.manga.components
|
package ca.gosyer.ui.manga.components
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.foundation.VerticalScrollbar
|
import androidx.compose.foundation.VerticalScrollbar
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
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.material.icons.rounded.Refresh
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
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.data.models.Manga
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.chapter.ChapterDownloadItem
|
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.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.reader.openReaderMenu
|
import ca.gosyer.ui.reader.openReaderMenu
|
||||||
import ca.gosyer.uicore.components.ErrorScreen
|
import ca.gosyer.uicore.components.ErrorScreen
|
||||||
@@ -73,28 +73,14 @@ fun MangaScreenContent(
|
|||||||
Toolbar(
|
Toolbar(
|
||||||
stringResource(MR.strings.location_manga),
|
stringResource(MR.strings.location_manga),
|
||||||
actions = {
|
actions = {
|
||||||
AnimatedVisibility(categoriesExist && manga?.inLibrary == true) {
|
getActionItems(
|
||||||
TextActionIcon(
|
refreshManga = refreshManga,
|
||||||
setCategories,
|
refreshMangaEnabled = !isLoading,
|
||||||
stringResource(MR.strings.edit_categories),
|
categoryItemVisible = categoriesExist && manga?.inLibrary == true,
|
||||||
Icons.Rounded.Label
|
setCategories = setCategories,
|
||||||
)
|
inLibrary = manga?.inLibrary == true,
|
||||||
}
|
toggleFavorite = toggleFavorite,
|
||||||
TextActionIcon(
|
favoritesButtonEnabled = manga != null
|
||||||
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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import androidx.compose.material.icons.rounded.NewReleases
|
|||||||
import androidx.compose.material.icons.rounded.Settings
|
import androidx.compose.material.icons.rounded.Settings
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.FilterQuality
|
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.Manga
|
||||||
import ca.gosyer.data.models.Source
|
import ca.gosyer.data.models.Source
|
||||||
import ca.gosyer.i18n.MR
|
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.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.sources.browse.filter.SourceFiltersMenu
|
import ca.gosyer.ui.sources.browse.filter.SourceFiltersMenu
|
||||||
import ca.gosyer.ui.sources.browse.filter.model.SourceFiltersView
|
import ca.gosyer.ui.sources.browse.filter.model.SourceFiltersView
|
||||||
@@ -149,45 +150,22 @@ fun SourceToolbar(
|
|||||||
search = onSearch,
|
search = onSearch,
|
||||||
searchSubmit = onSubmitSearch,
|
searchSubmit = onSubmitSearch,
|
||||||
actions = {
|
actions = {
|
||||||
if (source.isConfigurable) {
|
getActionItems(
|
||||||
TextActionIcon(
|
isConfigurable = source.isConfigurable,
|
||||||
{
|
onSourceSettingsClick = {
|
||||||
onSourceSettingsClick(source.id)
|
onSourceSettingsClick(source.id)
|
||||||
},
|
},
|
||||||
stringResource(MR.strings.location_settings),
|
isLatest = isLatest,
|
||||||
Icons.Rounded.Settings
|
showLatestButton = showLatestButton,
|
||||||
)
|
showFilterButton = showFilterButton,
|
||||||
}
|
onToggleFiltersClick = {
|
||||||
if (showFilterButton) {
|
|
||||||
TextActionIcon(
|
|
||||||
{
|
|
||||||
onToggleFiltersClick(!showingFilters)
|
onToggleFiltersClick(!showingFilters)
|
||||||
},
|
},
|
||||||
stringResource(MR.strings.filter_source),
|
onClickMode = {
|
||||||
Icons.Rounded.FilterList,
|
|
||||||
!isLatest
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (showLatestButton) {
|
|
||||||
TextActionIcon(
|
|
||||||
{
|
|
||||||
onClickMode(!isLatest)
|
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
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ import androidx.compose.material.Text
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Translate
|
import androidx.compose.material.icons.rounded.Translate
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
@@ -41,7 +42,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ca.gosyer.data.models.Source
|
import ca.gosyer.data.models.Source
|
||||||
import ca.gosyer.i18n.MR
|
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.base.navigation.Toolbar
|
||||||
import ca.gosyer.ui.extensions.components.LanguageDialog
|
import ca.gosyer.ui.extensions.components.LanguageDialog
|
||||||
import ca.gosyer.uicore.components.LoadingScreen
|
import ca.gosyer.uicore.components.LoadingScreen
|
||||||
@@ -109,15 +110,13 @@ fun SourceHomeScreenToolbar(
|
|||||||
Toolbar(
|
Toolbar(
|
||||||
stringResource(MR.strings.location_sources),
|
stringResource(MR.strings.location_sources),
|
||||||
actions = {
|
actions = {
|
||||||
TextActionIcon(
|
getActionItems(
|
||||||
{
|
onEnabledLanguagesClick = {
|
||||||
val enabledLangs = MutableStateFlow(sourceLanguages.value)
|
val enabledLangs = MutableStateFlow(sourceLanguages.value)
|
||||||
LanguageDialog(enabledLangs, onGetEnabledLanguages().toList()) {
|
LanguageDialog(enabledLangs, onGetEnabledLanguages().toList()) {
|
||||||
onSetEnabledLanguages(enabledLangs.value)
|
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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user