Allow creation of tags from edit favorites sheet
This commit is contained in:
parent
d88e92d02e
commit
5543bacc32
@ -246,6 +246,7 @@
|
|||||||
<string name="edit_favorites_dialog_empty_section">Drag items here</string>
|
<string name="edit_favorites_dialog_empty_section">Drag items here</string>
|
||||||
<string name="edit_favorites_dialog_tags">Tags</string>
|
<string name="edit_favorites_dialog_tags">Tags</string>
|
||||||
<string name="edit_favorites_dialog_tag_section_empty">Pinned tags will appear here</string>
|
<string name="edit_favorites_dialog_tag_section_empty">Pinned tags will appear here</string>
|
||||||
|
<string name="edit_favorites_dialog_new_tag">Create tag…</string>
|
||||||
<!-- Nextcloud login flow, URL-->
|
<!-- Nextcloud login flow, URL-->
|
||||||
<string name="nextcloud_server_url">Nextcloud server URL</string>
|
<string name="nextcloud_server_url">Nextcloud server URL</string>
|
||||||
<!-- Nextcloud/Owncloud login flow, empty URL-->
|
<!-- Nextcloud/Owncloud login flow, empty URL-->
|
||||||
|
|||||||
@ -9,22 +9,64 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.hoverable
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.interaction.collectIsHoveredAsState
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.*
|
import androidx.compose.material.icons.rounded.Add
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material.icons.rounded.ArrowForward
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.material.icons.rounded.Close
|
||||||
|
import androidx.compose.material.icons.rounded.Create
|
||||||
|
import androidx.compose.material.icons.rounded.Delete
|
||||||
|
import androidx.compose.material.icons.rounded.Settings
|
||||||
|
import androidx.compose.material.icons.rounded.Tag
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.FilledTonalIconButton
|
||||||
|
import androidx.compose.material3.FilledTonalIconToggleButton
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.FilterChipDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.MenuDefaults
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.OutlinedCard
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
|
import androidx.compose.material3.SliderDefaults
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
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.draw.drawBehind
|
import androidx.compose.ui.draw.drawBehind
|
||||||
@ -34,6 +76,7 @@ import androidx.compose.ui.graphics.drawOutline
|
|||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@ -51,10 +94,13 @@ import de.mm20.launcher2.ui.component.BottomSheetDialog
|
|||||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||||
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
||||||
import de.mm20.launcher2.ui.ktx.toPixels
|
import de.mm20.launcher2.ui.ktx.toPixels
|
||||||
import de.mm20.launcher2.ui.launcher.helper.*
|
import de.mm20.launcher2.ui.launcher.helper.DraggableItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.helper.LazyDragAndDropRow
|
||||||
|
import de.mm20.launcher2.ui.launcher.helper.LazyVerticalDragAndDropGrid
|
||||||
|
import de.mm20.launcher2.ui.launcher.helper.rememberLazyDragAndDropGridState
|
||||||
|
import de.mm20.launcher2.ui.launcher.helper.rememberLazyDragAndDropListState
|
||||||
import de.mm20.launcher2.ui.locals.LocalGridColumns
|
import de.mm20.launcher2.ui.locals.LocalGridColumns
|
||||||
import kotlinx.coroutines.currentCoroutineContext
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -250,6 +296,7 @@ fun ReorderFavoritesGrid(viewModel: EditFavoritesSheetVM) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is FavoritesSheetGridItem.Divider -> {
|
is FavoritesSheetGridItem.Divider -> {
|
||||||
val title = when (it.section) {
|
val title = when (it.section) {
|
||||||
FavoritesSheetSection.ManuallySorted -> R.string.edit_favorites_dialog_pinned_sorted
|
FavoritesSheetSection.ManuallySorted -> R.string.edit_favorites_dialog_pinned_sorted
|
||||||
@ -379,6 +426,7 @@ fun ReorderFavoritesGrid(viewModel: EditFavoritesSheetVM) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is FavoritesSheetGridItem.EmptySection -> {
|
is FavoritesSheetGridItem.EmptySection -> {
|
||||||
val shape = MaterialTheme.shapes.medium
|
val shape = MaterialTheme.shapes.medium
|
||||||
val color = MaterialTheme.colorScheme.outline
|
val color = MaterialTheme.colorScheme.outline
|
||||||
@ -420,6 +468,7 @@ fun ReorderFavoritesGrid(viewModel: EditFavoritesSheetVM) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is FavoritesSheetGridItem.Spacer -> {
|
is FavoritesSheetGridItem.Spacer -> {
|
||||||
Spacer(
|
Spacer(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -427,113 +476,157 @@ fun ReorderFavoritesGrid(viewModel: EditFavoritesSheetVM) {
|
|||||||
.height(48.dp)
|
.height(48.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is FavoritesSheetGridItem.Tags -> {
|
is FavoritesSheetGridItem.Tags -> {
|
||||||
var showAddMenu by remember { mutableStateOf(false) }
|
var showAddMenu by remember { mutableStateOf(false) }
|
||||||
if (availableTags.isNotEmpty() || pinnedTags.isNotEmpty()) {
|
Column {
|
||||||
Column {
|
Row(
|
||||||
Row(
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
modifier = Modifier.padding(vertical = 8.dp),
|
verticalAlignment = Alignment.CenterVertically
|
||||||
verticalAlignment = Alignment.CenterVertically
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(end = 16.dp),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
text = stringResource(R.string.edit_favorites_dialog_tags),
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
color = MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
Box() {
|
||||||
|
FilledTonalIconButton(
|
||||||
|
modifier = Modifier.offset(x = 4.dp),
|
||||||
|
onClick = {
|
||||||
|
showAddMenu = true
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Add,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = showAddMenu,
|
||||||
|
onDismissRequest = { showAddMenu = false }) {
|
||||||
|
for (tag in availableTags) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(Icons.Rounded.Tag, null)
|
||||||
|
},
|
||||||
|
text = { Text(tag.label) },
|
||||||
|
onClick = {
|
||||||
|
viewModel.pinTag(tag)
|
||||||
|
showAddMenu = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (availableTags.isNotEmpty()) {
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
var newTag by remember { mutableStateOf("") }
|
||||||
|
DropdownMenuItem(
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(Icons.Rounded.Create, null)
|
||||||
|
},
|
||||||
|
contentPadding = PaddingValues(
|
||||||
|
start = MenuDefaults.DropdownMenuItemContentPadding.calculateStartPadding(LocalLayoutDirection.current),
|
||||||
|
end = MenuDefaults.DropdownMenuItemContentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
||||||
|
top = 8.dp,
|
||||||
|
),
|
||||||
|
text = {
|
||||||
|
Box {
|
||||||
|
if (newTag.isEmpty()) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.edit_favorites_dialog_new_tag),
|
||||||
|
color = LocalContentColor.current.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
BasicTextField(
|
||||||
|
value = newTag,
|
||||||
|
onValueChange = { newTag = it.replace(",", "") },
|
||||||
|
textStyle = LocalTextStyle.current,
|
||||||
|
singleLine = true,
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onDone = {
|
||||||
|
viewModel.createNewTag(newTag)
|
||||||
|
showAddMenu = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trailingIcon = {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
viewModel.createNewTag(newTag)
|
||||||
|
showAddMenu = false
|
||||||
|
},
|
||||||
|
imageVector = Icons.Rounded.ArrowForward,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = { }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (pinnedTags.isNotEmpty()) {
|
||||||
|
val rowState = rememberLazyDragAndDropListState(
|
||||||
|
listState = tagsListState,
|
||||||
|
) { from, to ->
|
||||||
|
viewModel.moveTag(from, to)
|
||||||
|
}
|
||||||
|
LazyDragAndDropRow(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
state = rowState
|
||||||
) {
|
) {
|
||||||
Text(
|
items(
|
||||||
modifier = Modifier
|
pinnedTags,
|
||||||
.weight(1f)
|
key = { it.key }
|
||||||
.padding(end = 16.dp),
|
) { tag ->
|
||||||
maxLines = 1,
|
DraggableItem(state = rowState, key = tag.key) { dragged ->
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
text = stringResource(R.string.edit_favorites_dialog_tags),
|
FilterChip(
|
||||||
style = MaterialTheme.typography.titleSmall,
|
modifier = Modifier
|
||||||
color = MaterialTheme.colorScheme.secondary
|
.padding(end = 12.dp)
|
||||||
)
|
.pointerInput(null) {
|
||||||
Box() {
|
val coroutineContext =
|
||||||
FilledTonalIconButton(
|
currentCoroutineContext()
|
||||||
modifier = Modifier.offset(x = 4.dp),
|
|
||||||
enabled = availableTags.isNotEmpty(),
|
},
|
||||||
onClick = {
|
selected = tag.tag == hoveredTag,
|
||||||
showAddMenu = true
|
onClick = {},
|
||||||
}) {
|
label = { Text(tag.label) },
|
||||||
Icon(
|
leadingIcon = {
|
||||||
imageVector = Icons.Rounded.Add,
|
Icon(Icons.Rounded.Tag, null)
|
||||||
contentDescription = null
|
},
|
||||||
|
trailingIcon = {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
viewModel.unpinTag(tag)
|
||||||
|
},
|
||||||
|
imageVector = Icons.Rounded.Close,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
elevation = if (dragged) FilterChipDefaults.elevatedFilterChipElevation() else FilterChipDefaults.filterChipElevation(),
|
||||||
|
colors = if (dragged) FilterChipDefaults.elevatedFilterChipColors()
|
||||||
|
else FilterChipDefaults.filterChipColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
DropdownMenu(
|
|
||||||
expanded = showAddMenu,
|
|
||||||
onDismissRequest = { showAddMenu = false }) {
|
|
||||||
for (tag in availableTags) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(Icons.Rounded.Tag, null)
|
|
||||||
},
|
|
||||||
text = { Text(tag.label) },
|
|
||||||
onClick = {
|
|
||||||
viewModel.pinTag(tag)
|
|
||||||
showAddMenu = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
if (pinnedTags.isNotEmpty()) {
|
|
||||||
val rowState = rememberLazyDragAndDropListState(
|
|
||||||
listState = tagsListState,
|
|
||||||
) { from, to ->
|
|
||||||
viewModel.moveTag(from, to)
|
|
||||||
}
|
|
||||||
LazyDragAndDropRow(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
state = rowState
|
|
||||||
) {
|
|
||||||
items(
|
|
||||||
pinnedTags,
|
|
||||||
key = { it.key }
|
|
||||||
) { tag ->
|
|
||||||
DraggableItem(state = rowState, key = tag.key) { dragged ->
|
|
||||||
|
|
||||||
FilterChip(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = 12.dp)
|
|
||||||
.pointerInput(null) {
|
|
||||||
val coroutineContext =
|
|
||||||
currentCoroutineContext()
|
|
||||||
|
|
||||||
}
|
|
||||||
,
|
|
||||||
selected = tag.tag == hoveredTag,
|
|
||||||
onClick = {},
|
|
||||||
label = { Text(tag.label) },
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(Icons.Rounded.Tag, null)
|
|
||||||
},
|
|
||||||
trailingIcon = {
|
|
||||||
Icon(
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
viewModel.unpinTag(tag)
|
|
||||||
},
|
|
||||||
imageVector = Icons.Rounded.Close,
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
},
|
|
||||||
elevation = if (dragged) FilterChipDefaults.elevatedFilterChipElevation() else FilterChipDefaults.filterChipElevation(),
|
|
||||||
colors = if (dragged) FilterChipDefaults.elevatedFilterChipColors()
|
|
||||||
else FilterChipDefaults.filterChipColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 4.dp),
|
|
||||||
text = stringResource(R.string.edit_favorites_dialog_tag_section_empty),
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
color = MaterialTheme.colorScheme.outline
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp),
|
||||||
|
text = stringResource(R.string.edit_favorites_dialog_tag_section_empty),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.outline
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -306,4 +306,8 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createNewTag(newTag: String) {
|
||||||
|
pinTag(Tag(newTag))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user