diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 2d24f681..81990716 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -620,6 +620,8 @@ Change the order of pinned items Frequently used Show frequently used items in favorites + Edit button + Show a button to rearrange the favorites You haven\'t connected a Nextcloud account yet diff --git a/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt b/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt index 4623665a..11e1d7a6 100644 --- a/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt +++ b/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt @@ -59,6 +59,7 @@ fun createFactorySettings(context: Context): Settings { .setEnabled(true) .setFrequentlyUsed(true) .setFrequentlyUsedRows(1) + .setEditButton(true) ) .setFileSearch( Settings.FilesSearchSettings diff --git a/preferences/src/main/java/de/mm20/launcher2/preferences/migrations/Migration_9_10.kt b/preferences/src/main/java/de/mm20/launcher2/preferences/migrations/Migration_9_10.kt index 949fffa2..192fe122 100644 --- a/preferences/src/main/java/de/mm20/launcher2/preferences/migrations/Migration_9_10.kt +++ b/preferences/src/main/java/de/mm20/launcher2/preferences/migrations/Migration_9_10.kt @@ -8,6 +8,7 @@ class Migration_9_10: VersionedMigration(9, 10) { builder.favorites.toBuilder() .setFrequentlyUsed(true) .setFrequentlyUsedRows(1) + .setEditButton(true) ) } } \ No newline at end of file diff --git a/preferences/src/main/proto/settings.proto b/preferences/src/main/proto/settings.proto index 21776fde..441656b0 100644 --- a/preferences/src/main/proto/settings.proto +++ b/preferences/src/main/proto/settings.proto @@ -128,6 +128,7 @@ message Settings { bool enabled = 1; bool frequently_used = 2; int32 frequently_used_rows = 3; + bool edit_button = 4; } FavoritesSettings favorites = 8; diff --git a/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt index 67453239..fb966556 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt @@ -22,6 +22,8 @@ open class FavoritesVM : ViewModel(), KoinComponent { val selectedTag = MutableStateFlow(null) + val showEditButton = dataStore.data.map { it.favorites.editButton } + val pinnedTags = favoritesRepository.getFavorites( includeTypes = listOf("tag"), manuallySorted = true, diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt index 80e5b818..25e71ba9 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt @@ -2,14 +2,39 @@ package de.mm20.launcher2.ui.launcher.search import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyItemScope +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.* -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material.icons.rounded.Edit +import androidx.compose.material.icons.rounded.Person +import androidx.compose.material.icons.rounded.Star +import androidx.compose.material.icons.rounded.Tag +import androidx.compose.material.icons.rounded.Work +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FloatingActionButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.SmallFloatingActionButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue 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.Modifier import androidx.compose.ui.draw.clipToBounds @@ -82,6 +107,7 @@ fun SearchColumn( val pinnedTags by favoritesVM.pinnedTags.collectAsState(emptyList()) val selectedTag by favoritesVM.selectedTag.collectAsState(null) val tagsScrollState = rememberScrollState() + val favoritesEditButton by favoritesVM.showEditButton.collectAsState(false) LazyColumn( state = state, @@ -115,56 +141,62 @@ fun SearchColumn( ) } } else null, - after = { - Row( - modifier = Modifier - .fillMaxWidth() - .padding( - top = if (reverse) 8.dp else 4.dp, - bottom = if (reverse) 4.dp else 8.dp, - end = 8.dp - ), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - ) { + after = if (pinnedTags.isEmpty() && !favoritesEditButton) { + null + } else { + { Row( modifier = Modifier - .weight(1f) - .horizontalScroll(tagsScrollState) - .padding(end = 12.dp), + .fillMaxWidth() + .padding( + top = if (reverse) 8.dp else 4.dp, + bottom = if (reverse) 4.dp else 8.dp, + end = if (favoritesEditButton) 8.dp else 0.dp + ), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, ) { - FilterChip( - modifier = Modifier.padding(start = 16.dp), - selected = selectedTag == null, - onClick = { favoritesVM.selectTag(null) }, - leadingIcon = { - Icon( - imageVector = Icons.Rounded.Star, - contentDescription = null - ) - }, - label = { Text(stringResource(R.string.favorites)) } - ) - for (tag in pinnedTags) { + Row( + modifier = Modifier + .weight(1f) + .horizontalScroll(tagsScrollState) + .padding(end = 12.dp), + ) { FilterChip( - modifier = Modifier.padding(start = 8.dp), - selected = selectedTag == tag.tag, - onClick = { favoritesVM.selectTag(tag.tag) }, + modifier = Modifier.padding(start = 16.dp), + selected = selectedTag == null, + onClick = { favoritesVM.selectTag(null) }, leadingIcon = { Icon( - imageVector = Icons.Rounded.Tag, + imageVector = Icons.Rounded.Star, contentDescription = null ) }, - label = { Text(tag.label) } + label = { Text(stringResource(R.string.favorites)) } ) + for (tag in pinnedTags) { + FilterChip( + modifier = Modifier.padding(start = 8.dp), + selected = selectedTag == tag.tag, + onClick = { favoritesVM.selectTag(tag.tag) }, + leadingIcon = { + Icon( + imageVector = Icons.Rounded.Tag, + contentDescription = null + ) + }, + label = { Text(tag.label) } + ) + } + } + if (favoritesEditButton) { + SmallFloatingActionButton( + elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation(), + onClick = { showEditFavoritesDialog = true } + ) { + Icon(imageVector = Icons.Rounded.Edit, contentDescription = null) + } } - } - SmallFloatingActionButton( - elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation(), - onClick = { showEditFavoritesDialog = true } - ) { - Icon(imageVector = Icons.Rounded.Edit, contentDescription = null) } } } @@ -221,22 +253,24 @@ fun SearchColumn( } else null ) ListResults( - before = if (missingShortcutsPermission && !isSearchEmpty){{ - MissingPermissionBanner( - modifier = Modifier.padding(8.dp), - text = stringResource(R.string.missing_permission_appshortcuts_search), - onClick = { viewModel.requestAppShortcutPermission(context as AppCompatActivity) }, - secondaryAction = { - OutlinedButton(onClick = { - viewModel.disableAppShortcutSearch() - }) { - Text( - stringResource(R.string.turn_off), - ) + before = if (missingShortcutsPermission && !isSearchEmpty) { + { + MissingPermissionBanner( + modifier = Modifier.padding(8.dp), + text = stringResource(R.string.missing_permission_appshortcuts_search), + onClick = { viewModel.requestAppShortcutPermission(context as AppCompatActivity) }, + secondaryAction = { + OutlinedButton(onClick = { + viewModel.disableAppShortcutSearch() + }) { + Text( + stringResource(R.string.turn_off), + ) + } } - } - ) - }} else null, + ) + } + } else null, items = appShortcuts.toImmutableList(), reverse = reverse, key = "shortcuts" @@ -254,42 +288,47 @@ fun SearchColumn( } } ListResults( - before = if (missingCalendarPermission && !isSearchEmpty){{ - MissingPermissionBanner( - modifier = Modifier.padding(8.dp), - text = stringResource(R.string.missing_permission_calendar_search), - onClick = { viewModel.requestCalendarPermission(context as AppCompatActivity) },secondaryAction = { - OutlinedButton(onClick = { - viewModel.disableCalendarSearch() - }) { - Text( - stringResource(R.string.turn_off), - ) + before = if (missingCalendarPermission && !isSearchEmpty) { + { + MissingPermissionBanner( + modifier = Modifier.padding(8.dp), + text = stringResource(R.string.missing_permission_calendar_search), + onClick = { viewModel.requestCalendarPermission(context as AppCompatActivity) }, + secondaryAction = { + OutlinedButton(onClick = { + viewModel.disableCalendarSearch() + }) { + Text( + stringResource(R.string.turn_off), + ) + } } - } - ) - }} else null, + ) + } + } else null, items = events.toImmutableList(), reverse = reverse, key = "events" ) ListResults( - before = if (missingContactsPermission && !isSearchEmpty){{ - MissingPermissionBanner( - modifier = Modifier.padding(8.dp), - text = stringResource(R.string.missing_permission_contact_search), - onClick = { viewModel.requestContactsPermission(context as AppCompatActivity) }, - secondaryAction = { - OutlinedButton(onClick = { - viewModel.disableContactsSearch() - }) { - Text( - stringResource(R.string.turn_off), - ) + before = if (missingContactsPermission && !isSearchEmpty) { + { + MissingPermissionBanner( + modifier = Modifier.padding(8.dp), + text = stringResource(R.string.missing_permission_contact_search), + onClick = { viewModel.requestContactsPermission(context as AppCompatActivity) }, + secondaryAction = { + OutlinedButton(onClick = { + viewModel.disableContactsSearch() + }) { + Text( + stringResource(R.string.turn_off), + ) + } } - } - ) - }} else null, + ) + } + } else null, items = contacts.toImmutableList(), reverse = reverse, key = "contacts" @@ -307,22 +346,24 @@ fun SearchColumn( } } ListResults( - before = if (missingFilesPermission && !isSearchEmpty){{ - MissingPermissionBanner( - modifier = Modifier.padding(8.dp), - text = stringResource(R.string.missing_permission_files_search), - onClick = { viewModel.requestFilesPermission(context as AppCompatActivity) }, - secondaryAction = { - OutlinedButton(onClick = { - viewModel.disableFilesSearch() - }) { - Text( - stringResource(R.string.turn_off), - ) + before = if (missingFilesPermission && !isSearchEmpty) { + { + MissingPermissionBanner( + modifier = Modifier.padding(8.dp), + text = stringResource(R.string.missing_permission_files_search), + onClick = { viewModel.requestFilesPermission(context as AppCompatActivity) }, + secondaryAction = { + OutlinedButton(onClick = { + viewModel.disableFilesSearch() + }) { + Text( + stringResource(R.string.turn_off), + ) + } } - } - ) - }} else null, + ) + } + } else null, items = files.toImmutableList(), reverse = reverse, key = "files" diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidget.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidget.kt index b444cf32..9c492ff8 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidget.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidget.kt @@ -1,14 +1,28 @@ package de.mm20.launcher2.ui.launcher.widgets.favorites import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Edit import androidx.compose.material.icons.rounded.Star import androidx.compose.material.icons.rounded.Tag -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FloatingActionButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SmallFloatingActionButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +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.res.stringResource @@ -25,6 +39,7 @@ fun FavoritesWidget() { val pinnedTags by viewModel.pinnedTags.collectAsState(emptyList()) val selectedTag by viewModel.selectedTag.collectAsState(null) var showEditFavoritesDialog by remember { mutableStateOf(false) } + val favoritesEditButton by viewModel.showEditButton.collectAsState(false) Column { if (favorites.isNotEmpty()) { @@ -41,54 +56,59 @@ fun FavoritesWidget() { color = MaterialTheme.colorScheme.outline ) } - Row( - modifier = Modifier - .fillMaxWidth() - .padding( - top = 4.dp, - bottom = 8.dp, - end = 8.dp - ), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - ) { + if (pinnedTags.isNotEmpty() || favoritesEditButton) { Row( modifier = Modifier - .weight(1f) - .horizontalScroll(rememberScrollState()), + .fillMaxWidth() + .padding( + top = 4.dp, + bottom = 8.dp, + end = if (favoritesEditButton) 8.dp else 0.dp + ), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, ) { - FilterChip( - modifier = Modifier.padding(start = 16.dp), - selected = selectedTag == null, - onClick = { viewModel.selectTag(null) }, - leadingIcon = { - Icon( - imageVector = Icons.Rounded.Star, - contentDescription = null - ) - }, - label = { Text(stringResource(R.string.favorites)) } - ) - for (tag in pinnedTags) { + Row( + modifier = Modifier + .weight(1f) + .horizontalScroll(rememberScrollState()) + .padding(end = 12.dp), + ) { FilterChip( - modifier = Modifier.padding(start = 8.dp), - selected = selectedTag == tag.tag, - onClick = { viewModel.selectTag(tag.tag) }, + modifier = Modifier.padding(start = 16.dp), + selected = selectedTag == null, + onClick = { viewModel.selectTag(null) }, leadingIcon = { Icon( - imageVector = Icons.Rounded.Tag, + imageVector = Icons.Rounded.Star, contentDescription = null ) }, - label = { Text(tag.label) } + label = { Text(stringResource(R.string.favorites)) } ) + for (tag in pinnedTags) { + FilterChip( + modifier = Modifier.padding(start = 8.dp), + selected = selectedTag == tag.tag, + onClick = { viewModel.selectTag(tag.tag) }, + leadingIcon = { + Icon( + imageVector = Icons.Rounded.Tag, + contentDescription = null + ) + }, + label = { Text(tag.label) } + ) + } + } + if (favoritesEditButton) { + SmallFloatingActionButton( + elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation(), + onClick = { showEditFavoritesDialog = true } + ) { + Icon(imageVector = Icons.Rounded.Edit, contentDescription = null) + } } - } - SmallFloatingActionButton( - elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation(), - onClick = { showEditFavoritesDialog = true } - ) { - Icon(imageVector = Icons.Rounded.Edit, contentDescription = null) } } } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/favorites/FavoritesSettingsScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/favorites/FavoritesSettingsScreen.kt index be71e1f7..f6f0f852 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/favorites/FavoritesSettingsScreen.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/favorites/FavoritesSettingsScreen.kt @@ -58,6 +58,19 @@ fun FavoritesSettingsScreen() { ) } } + item { + val editButton by viewModel.editButton.observeAsState() + PreferenceCategory { + SwitchPreference( + title = stringResource(R.string.preference_edit_button), + summary = stringResource(R.string.preference_favorites_edit_button_summary), + value = editButton == true, + onValueChanged = { + viewModel.setEditButton(it) + } + ) + } + } } if (showEditSheet) { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/favorites/FavoritesSettingsScreenVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/favorites/FavoritesSettingsScreenVM.kt index daa95021..6800ca1f 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/favorites/FavoritesSettingsScreenVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/favorites/FavoritesSettingsScreenVM.kt @@ -39,4 +39,18 @@ class FavoritesSettingsScreenVM: ViewModel(), KoinComponent { } } } + + val editButton = dataStore.data.map { it.favorites.editButton }.asLiveData() + fun setEditButton(editButton: Boolean) { + viewModelScope.launch { + dataStore.updateData { + it.toBuilder() + .setFavorites( + it.favorites.toBuilder() + .setEditButton(editButton) + ) + .build() + } + } + } } \ No newline at end of file