From a7c55b2668ce5336e30d2c801f89b551f463b0cf Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Fri, 27 May 2022 20:29:00 +0200 Subject: [PATCH] New hidden items UI --- .../launcher2/applications/AppRepository.kt | 22 +- i18n/src/main/res/values-de/strings.xml | 1 - i18n/src/main/res/values-es/strings.xml | 1 - i18n/src/main/res/values-fr/strings.xml | 1 - i18n/src/main/res/values-it/strings.xml | 1 - i18n/src/main/res/values-pl/strings.xml | 1 - i18n/src/main/res/values-pt-rBR/strings.xml | 1 - i18n/src/main/res/values-ru/strings.xml | 1 - i18n/src/main/res/values-sv/strings.xml | 1 - i18n/src/main/res/values-zh-rCN/strings.xml | 1 - i18n/src/main/res/values/strings.xml | 5 +- .../launcher2/ui/launcher/LauncherActivity.kt | 6 - .../ui/launcher/LauncherActivityVM.kt | 10 - .../ui/launcher/modals/HiddenItemsSheet.kt | 116 ---------- .../ui/launcher/modals/HiddenItemsVM.kt | 17 -- .../launcher2/ui/launcher/search/SearchBar.kt | 9 - .../ui/launcher/search/SearchColumn.kt | 57 +++++ .../launcher2/ui/launcher/search/SearchVM.kt | 19 ++ .../launcher2/ui/settings/SettingsActivity.kt | 16 +- .../hiddenitems/HiddenItemsSettingsScreen.kt | 205 ++++++++++++++++++ .../HiddenItemsSettingsScreenVM.kt | 90 ++++++++ .../settings/search/SearchSettingsScreen.kt | 7 + ui/src/main/res/menu/menu_launcher.xml | 18 -- 23 files changed, 413 insertions(+), 193 deletions(-) delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/HiddenItemsSheet.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/HiddenItemsVM.kt create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreen.kt create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt delete mode 100644 ui/src/main/res/menu/menu_launcher.xml diff --git a/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt b/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt index e2bcfd62..f2849f8c 100644 --- a/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt +++ b/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt @@ -9,7 +9,6 @@ import android.content.pm.PackageInstaller import android.content.pm.ShortcutInfo import android.os.Process import android.os.UserHandle -import android.os.UserManager import android.util.Log import com.github.promeg.pinyinhelper.Pinyin import de.mm20.launcher2.hiddenitems.HiddenItemsRepository @@ -24,6 +23,7 @@ import java.util.* interface AppRepository { fun search(query: String): Flow> + fun getAllInstalledApps(includeHidden: Boolean = false): Flow> fun getSuspendedPackages(): Flow> } @@ -85,7 +85,8 @@ internal class AppRepositoryImpl( } override fun onPackageRemoved(packageName: String, user: UserHandle) { - installedApps.value = installedApps.value.filter { packageName != (it.`package`) || it.getUser() != user } + installedApps.value = + installedApps.value.filter { packageName != (it.`package`) || it.getUser() != user } } override fun onShortcutsChanged( @@ -109,7 +110,8 @@ internal class AppRepositoryImpl( ) { super.onPackagesUnsuspended(packageNames, user) packageNames ?: return - suspendedPackages.value = suspendedPackages.value.filter { packageNames.contains(it) } + suspendedPackages.value = + suspendedPackages.value.filter { packageNames.contains(it) } } }) @@ -216,6 +218,20 @@ internal class AppRepositoryImpl( } } + override fun getAllInstalledApps(includeHidden: Boolean): Flow> { + return if (!includeHidden) { + channelFlow { + hiddenItems.collectLatest { hidden -> + installedApps.collectLatest { apps -> + send(apps.filter { !hidden.contains(it.key) }) + } + } + } + } else { + installedApps + } + } + private fun matches(label: String, query: String): Boolean { val labelLatin = romanize(label) val fuzzyScore = FuzzyScore(Locale.getDefault()) diff --git a/i18n/src/main/res/values-de/strings.xml b/i18n/src/main/res/values-de/strings.xml index c700448e..5ee0fcce 100644 --- a/i18n/src/main/res/values-de/strings.xml +++ b/i18n/src/main/res/values-de/strings.xml @@ -130,7 +130,6 @@ E-Book Formular Ausblenden - Ausgeblendete Elemente Löschen Nicht ausblenden In Kalender-App anzeigen diff --git a/i18n/src/main/res/values-es/strings.xml b/i18n/src/main/res/values-es/strings.xml index 2d04b88a..2ac9ce49 100644 --- a/i18n/src/main/res/values-es/strings.xml +++ b/i18n/src/main/res/values-es/strings.xml @@ -16,7 +16,6 @@ Iniciar Ocultar No ocultar - Elementos ocultos Eliminar Abrir en la aplicación de contactos Buscar diff --git a/i18n/src/main/res/values-fr/strings.xml b/i18n/src/main/res/values-fr/strings.xml index 1b7745c7..10408dc8 100644 --- a/i18n/src/main/res/values-fr/strings.xml +++ b/i18n/src/main/res/values-fr/strings.xml @@ -163,7 +163,6 @@ Formulaire Cacher Ne pas cacher - Applications cachées Supprimer Ouvrir dans l\'application de calendrier Voir les photos diff --git a/i18n/src/main/res/values-it/strings.xml b/i18n/src/main/res/values-it/strings.xml index 32ef3918..8c6f91de 100644 --- a/i18n/src/main/res/values-it/strings.xml +++ b/i18n/src/main/res/values-it/strings.xml @@ -63,7 +63,6 @@ Apri Nascondi Non nascondere - Elementi nascosti Elimina Apri nell\'app calendario Cerca diff --git a/i18n/src/main/res/values-pl/strings.xml b/i18n/src/main/res/values-pl/strings.xml index 9daf1652..d67cb9f2 100644 --- a/i18n/src/main/res/values-pl/strings.xml +++ b/i18n/src/main/res/values-pl/strings.xml @@ -10,7 +10,6 @@ Otwórz Ukryj Nie ukrywaj - Ukryte elementy Otwórz w aplikacji do kontaktów Otwórz w aplikacji kalendarza Wersja %1$s diff --git a/i18n/src/main/res/values-pt-rBR/strings.xml b/i18n/src/main/res/values-pt-rBR/strings.xml index 8705ff19..d54d1ea3 100644 --- a/i18n/src/main/res/values-pt-rBR/strings.xml +++ b/i18n/src/main/res/values-pt-rBR/strings.xml @@ -4,7 +4,6 @@ Voltar Não ocultar Ocultar - Itens ocultados Deletar Abrir no aplicativo de contatos Abrir no aplicativo de calendário diff --git a/i18n/src/main/res/values-ru/strings.xml b/i18n/src/main/res/values-ru/strings.xml index b0613e39..800be560 100644 --- a/i18n/src/main/res/values-ru/strings.xml +++ b/i18n/src/main/res/values-ru/strings.xml @@ -10,7 +10,6 @@ Запустить Скрыть Не скрывать - Скрытые Убрать Открыть в Контактах Открыть в Календаре diff --git a/i18n/src/main/res/values-sv/strings.xml b/i18n/src/main/res/values-sv/strings.xml index 4e9120ac..c29a0171 100644 --- a/i18n/src/main/res/values-sv/strings.xml +++ b/i18n/src/main/res/values-sv/strings.xml @@ -11,7 +11,6 @@ Öppna Göm Göm ej - Gömda objekt Radera Öppna i Kontakter Öppna i Kalender diff --git a/i18n/src/main/res/values-zh-rCN/strings.xml b/i18n/src/main/res/values-zh-rCN/strings.xml index 49ab88de..9b9926c1 100644 --- a/i18n/src/main/res/values-zh-rCN/strings.xml +++ b/i18n/src/main/res/values-zh-rCN/strings.xml @@ -52,7 +52,6 @@ 打开 隐藏 禁止隐藏 - 隐藏项目 删除 在“联系人”应用中打开 在\"日历\"应用中打开 diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index f8f0c64c..9fd840f0 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -24,7 +24,8 @@ %1$s has been hidden. Undo - Hidden items + Hidden apps + Hidden results Delete @@ -568,6 +569,8 @@ Customize search bar appearance Launch keyboard Automatically show the keyboard when opening the search + Hidden search results + Manage hidden apps and search results Wikipedia URL You haven\'t connected a Nextcloud account yet diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt index 38917429..e9816481 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt @@ -145,12 +145,6 @@ class LauncherActivity : BaseActivity() { .imePadding() ) } - val showHiddenItems by viewModel.isHiddenItemsShown.observeAsState(false) - if (showHiddenItems) { - HiddenItemsSheet(onDismiss = { - viewModel.hideHiddenItems() - }) - } } } } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivityVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivityVM.kt index 2dc1e5a8..3be824c1 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivityVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivityVM.kt @@ -13,7 +13,6 @@ import org.koin.core.component.inject class LauncherActivityVM : ViewModel(), KoinComponent { private val dataStore: LauncherDataStore by inject() - val isHiddenItemsShown = MutableLiveData(false) val isEditFavoritesShown = MutableLiveData(false) private var isDarkInMode = MutableStateFlow(false) @@ -55,14 +54,5 @@ class LauncherActivityVM : ViewModel(), KoinComponent { isEditFavoritesShown.value = false } - - fun showHiddenItems() { - isHiddenItemsShown.value = true - } - - fun hideHiddenItems() { - isHiddenItemsShown.value = false - } - val layout = dataStore.data.map { it.appearance.layout }.asLiveData() } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/HiddenItemsSheet.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/HiddenItemsSheet.kt deleted file mode 100644 index 6a7df559..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/HiddenItemsSheet.kt +++ /dev/null @@ -1,116 +0,0 @@ -package de.mm20.launcher2.ui.launcher.modals - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.MutableTransitionState -import androidx.compose.animation.slideIn -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.FractionalThreshold -import androidx.compose.material.rememberSwipeableState -import androidx.compose.material.swipeable -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties -import androidx.lifecycle.viewmodel.compose.viewModel -import de.mm20.launcher2.ui.R -import de.mm20.launcher2.ui.ktx.toPixels -import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid -import kotlin.math.roundToInt - -@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterialApi::class) -@Composable -fun HiddenItemsSheet( - onDismiss: () -> Unit -) { - val viewModel: HiddenItemsVM = viewModel() - - Dialog( - properties = DialogProperties(usePlatformDefaultWidth = false), - onDismissRequest = { onDismiss() }) { - val animationState = remember { - MutableTransitionState(false).apply { - targetState = true - } - } - - AnimatedVisibility( - animationState, - enter = slideIn { IntOffset(0, it.height) } - ) { - val swipeState = - rememberSwipeableState(initialValue = SwipeState.Default) { - if (it == SwipeState.Dismiss) onDismiss() - return@rememberSwipeableState true - } - Surface( - modifier = Modifier - .fillMaxSize() - .swipeable( - swipeState, - mapOf( - 0f to SwipeState.Default, - 600.dp.toPixels() to SwipeState.Dismiss - ), - orientation = Orientation.Vertical, - thresholds = { _, _ -> FractionalThreshold(0.5f) }, - ) - .offset { IntOffset(0, swipeState.offset.value.roundToInt()) } - - ) { - Column( - modifier = Modifier - .fillMaxSize() - ) { - Text( - stringResource(R.string.menu_hidden_items), - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(24.dp) - - ) - val items by viewModel.hiddenItems.collectAsState(emptyList()) - SearchResultGrid( - items, - modifier = Modifier - .weight(1f) - .padding(8.dp) - .verticalScroll(rememberScrollState()) - ) - Box( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp), - contentAlignment = Alignment.CenterEnd - ) { - TextButton(onClick = { onDismiss() }) { - Text( - stringResource(id = R.string.close), - ) - } - } - } - } - } - - } - -} - -private enum class SwipeState { - Default, Dismiss -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/HiddenItemsVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/HiddenItemsVM.kt deleted file mode 100644 index 24869a43..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/HiddenItemsVM.kt +++ /dev/null @@ -1,17 +0,0 @@ -package de.mm20.launcher2.ui.launcher.modals - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import de.mm20.launcher2.favorites.FavoritesRepository -import de.mm20.launcher2.search.data.Searchable -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.withContext -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -class HiddenItemsVM: ViewModel(), KoinComponent { - private val repository: FavoritesRepository by inject() - - val hiddenItems = repository.getHiddenItems() -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchBar.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchBar.kt index ec86bb01..37841cbf 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchBar.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchBar.kt @@ -112,15 +112,6 @@ fun SearchBar( Text(stringResource(R.string.menu_item_edit_favs)) } ) - DropdownMenuItem( - onClick = { - activityViewModel.showHiddenItems() - onDismissRequest() - }, - text = { - Text(stringResource(R.string.menu_hidden_items)) - } - ) DropdownMenuItem( onClick = { context.startActivity( 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 3d77d832..2b4579a5 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 @@ -1,9 +1,20 @@ package de.mm20.launcher2.ui.launcher.search import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Settings +import androidx.compose.material.icons.rounded.VisibilityOff +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.ui.launcher.search.apps.AppResults import de.mm20.launcher2.ui.launcher.search.appshortcuts.AppShortcutResults import de.mm20.launcher2.ui.launcher.search.calculator.CalculatorResults @@ -16,6 +27,7 @@ import de.mm20.launcher2.ui.launcher.search.website.WebsiteResults import de.mm20.launcher2.ui.launcher.search.wikipedia.WikipediaResults import de.mm20.launcher2.ui.layout.BottomReversed +@OptIn(ExperimentalMaterial3Api::class) @Composable fun SearchColumn( modifier: Modifier = Modifier, @@ -35,5 +47,50 @@ fun SearchColumn( WikipediaResults(reverse) WebsiteResults(reverse) FileResults(reverse) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 4.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End + ) { + val viewModel: SearchVM = viewModel() + val context = LocalContext.current + Surface( + shadowElevation = 2.dp, + color = MaterialTheme.colorScheme.secondaryContainer, + contentColor = MaterialTheme.colorScheme.onSecondaryContainer, + shape = CircleShape, + onClick = { viewModel.showHiddenItems(context) } + ) { + Box( + modifier = Modifier.padding(12.dp), + ) { + Icon( + imageVector = Icons.Rounded.VisibilityOff, + contentDescription = null, + ) + } + + } + Spacer(modifier = Modifier.width(12.dp)) + Surface( + shadowElevation = 2.dp, + color = MaterialTheme.colorScheme.secondaryContainer, + contentColor = MaterialTheme.colorScheme.onSecondaryContainer, + shape = CircleShape, + onClick = { viewModel.openSearchSettings(context) } + ) { + Box( + modifier = Modifier.padding(12.dp), + ) { + Icon( + imageVector = Icons.Rounded.Settings, + contentDescription = null, + ) + } + + } + } } } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt index 598e5ecd..250a9bae 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt @@ -1,5 +1,7 @@ package de.mm20.launcher2.ui.launcher.search +import android.content.Context +import android.content.Intent import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -16,6 +18,7 @@ import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.search.WebsearchRepository import de.mm20.launcher2.search.data.* +import de.mm20.launcher2.ui.settings.SettingsActivity import de.mm20.launcher2.unitconverter.UnitConverterRepository import de.mm20.launcher2.websites.WebsiteRepository import de.mm20.launcher2.widgets.WidgetRepository @@ -231,4 +234,20 @@ class SearchVM : ViewModel(), KoinComponent { } } + fun showHiddenItems(context: Context) { + context.startActivity( + Intent(context, SettingsActivity::class.java).apply { + putExtra(SettingsActivity.EXTRA_ROUTE, "settings/search/hiddenitems") + } + ) + } + + fun openSearchSettings(context: Context) { + context.startActivity( + Intent(context, SettingsActivity::class.java).apply { + putExtra(SettingsActivity.EXTRA_ROUTE, "settings/search") + } + ) + } + } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt index 8c2c3cd9..6d97d4fb 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt @@ -1,5 +1,6 @@ package de.mm20.launcher2.ui.settings +import android.content.Intent import android.os.Bundle import androidx.activity.compose.setContent import androidx.compose.animation.ExperimentalAnimationApi @@ -34,6 +35,7 @@ import de.mm20.launcher2.ui.settings.crashreporter.CrashReporterScreen import de.mm20.launcher2.ui.settings.debug.DebugSettingsScreen import de.mm20.launcher2.ui.settings.easteregg.EasterEggSettingsScreen import de.mm20.launcher2.ui.settings.filesearch.FileSearchSettingsScreen +import de.mm20.launcher2.ui.settings.hiddenitems.HiddenItemsSettingsScreen import de.mm20.launcher2.ui.settings.license.LicenseScreen import de.mm20.launcher2.ui.settings.main.MainSettingsScreen import de.mm20.launcher2.ui.settings.musicwidget.MusicWidgetSettingsScreen @@ -58,9 +60,8 @@ class SettingsActivity : BaseActivity() { setContent { val navController = rememberAnimatedNavController() - LaunchedEffect(intent) { - intent.getStringExtra("de.mm20.launcher2.settings.ROUTE") - ?.let { navController.navigate(it) } + val initialRoute = remember { + intent.getStringExtra("de.mm20.launcher2.settings.ROUTE") ?: "settings" } val cardStyle by remember { @@ -75,7 +76,7 @@ class SettingsActivity : BaseActivity() { LauncherTheme { AnimatedNavHost( navController = navController, - startDestination = "settings", + startDestination = initialRoute, exitTransition = { fadeOut(tween(300, 300)) }, enterTransition = { fadeIn(tween(200)) }, popEnterTransition = { fadeIn(tween(0)) }, @@ -111,6 +112,9 @@ class SettingsActivity : BaseActivity() { composable("settings/search/websearch") { WebSearchSettingsScreen() } + composable("settings/search/hiddenitems") { + HiddenItemsSettingsScreen() + } composable("settings/widgets") { WidgetsSettingsScreen() } @@ -176,4 +180,8 @@ class SettingsActivity : BaseActivity() { } } } + + companion object { + const val EXTRA_ROUTE = "de.mm20.launcher2.settings.ROUTE" + } } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreen.kt new file mode 100644 index 00000000..d3e6eb70 --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreen.kt @@ -0,0 +1,205 @@ +package de.mm20.launcher2.ui.settings.hiddenitems + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Visibility +import androidx.compose.material.icons.rounded.VisibilityOff +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.search.data.Application +import de.mm20.launcher2.ui.R +import de.mm20.launcher2.ui.component.ShapedLauncherIcon +import de.mm20.launcher2.ui.component.preferences.PreferenceScreen + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun HiddenItemsSettingsScreen() { + val viewModel: HiddenItemsSettingsScreenVM = viewModel() + + val context = LocalContext.current + val density = LocalDensity.current + + val apps by viewModel.allApps.observeAsState(emptyList()) + val other by viewModel.hiddenItems.observeAsState(emptyList()) + PreferenceScreen(title = stringResource(R.string.preference_hidden_items)) { + items(apps, key = { it.key }) { searchable -> + val icon by remember(searchable.key) { + viewModel.getIcon(searchable, with(density) { 32.dp.roundToPx() }) + }.collectAsState(null) + + val isHidden by remember(searchable.key) { + viewModel.isHidden(searchable) + }.collectAsState(false) + + var showPopup by remember(searchable.key) { + mutableStateOf(false) + } + + Box { + HiddenItem( + modifier = Modifier + .fillMaxWidth() + .combinedClickable( + onClick = { + viewModel.setHidden(searchable, !isHidden) + }, + onLongClick = { + showPopup = true + } + ), + icon = icon, + label = searchable.label, + isHidden = isHidden, + ) + + DropdownMenu( + expanded = showPopup, + onDismissRequest = { showPopup = false }, + offset = DpOffset(16.dp, 0.dp) + ) { + DropdownMenuItem( + text = { Text(stringResource(R.string.menu_launch)) }, + onClick = { + viewModel.launch(context, searchable) + showPopup = false + }) + + DropdownMenuItem( + text = { Text(stringResource(R.string.menu_app_info)) }, + onClick = { + viewModel.openAppInfo(context, searchable as Application) + showPopup = false + }) + + DropdownMenuItem( + text = { + Text( + stringResource( + if (isHidden) R.string.menu_unhide else R.string.menu_hide + ) + ) + }, + onClick = { + viewModel.setHidden(searchable, !isHidden) + showPopup = false + }) + } + } + } + + item { + Box( + modifier = Modifier + .fillMaxWidth() + .height(0.5.dp) + .background( + MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f) + ) + ) + } + + items(other, key = { it.key }) { searchable -> + val icon by remember(searchable.key) { + viewModel.getIcon(searchable, with(density) { 32.dp.roundToPx() }) + }.collectAsState(null) + + val isHidden by remember(searchable.key) { + viewModel.isHidden(searchable) + }.collectAsState(false) + + var showPopup by remember(searchable.key) { + mutableStateOf(false) + } + + Box { + HiddenItem( + modifier = Modifier + .fillMaxWidth() + .combinedClickable( + onClick = { + viewModel.setHidden(searchable, !isHidden) + }, + onLongClick = { + showPopup = true + } + ), + icon = icon, + label = searchable.label, + isHidden = isHidden, + ) + + DropdownMenu( + expanded = showPopup, + onDismissRequest = { showPopup = false }, + offset = DpOffset(16.dp, 0.dp) + ) { + DropdownMenuItem( + text = { Text(stringResource(R.string.menu_open_file)) }, + onClick = { + viewModel.launch(context, searchable) + showPopup = false + }) + + DropdownMenuItem( + text = { + Text( + stringResource( + if (isHidden) R.string.menu_unhide else R.string.menu_hide + ) + ) + }, + onClick = { + viewModel.setHidden(searchable, !isHidden) + showPopup = false + }) + } + } + } + } +} + +@Composable +fun HiddenItem( + modifier: Modifier, + icon: LauncherIcon?, + label: String, + isHidden: Boolean, +) { + Row( + modifier = modifier + .padding(vertical = 12.dp, horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + ShapedLauncherIcon( + size = 32.dp, + icon = icon, + modifier = Modifier.padding(end = 16.dp) + ) + Text( + label, + modifier = Modifier.weight(1f, fill = true), + style = MaterialTheme.typography.titleMedium + ) + Icon( + modifier = Modifier.alpha(if (isHidden) 0.3f else 1f), + imageVector = if (isHidden) Icons.Rounded.VisibilityOff else Icons.Rounded.Visibility, + tint = if (isHidden) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.primary, + contentDescription = null + ) + } +} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt new file mode 100644 index 00000000..b677e856 --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt @@ -0,0 +1,90 @@ +package de.mm20.launcher2.ui.settings.hiddenitems + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.LauncherApps +import android.net.Uri +import android.os.Bundle +import android.provider.Settings +import androidx.core.content.getSystemService +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.asLiveData +import androidx.lifecycle.liveData +import de.mm20.launcher2.applications.AppRepository +import de.mm20.launcher2.favorites.FavoritesRepository +import de.mm20.launcher2.icons.IconRepository +import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.ktx.isAtLeastApiLevel +import de.mm20.launcher2.ktx.tryStartActivity +import de.mm20.launcher2.search.data.Application +import de.mm20.launcher2.search.data.LauncherApp +import de.mm20.launcher2.search.data.Searchable +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent { + private val appRepository: AppRepository by inject() + private val favoritesRepository: FavoritesRepository by inject() + private val iconRepository: IconRepository by inject() + + val allApps = appRepository.getAllInstalledApps(true).map { + withContext(Dispatchers.Default) { it.sorted() } + }.asLiveData() + val hiddenItems: LiveData> = liveData { + val hidden = withContext(Dispatchers.Default) { + favoritesRepository.getHiddenItems().first().filter { it !is Application }.sorted() + } + emit(hidden) + } + + fun isHidden(searchable: Searchable): Flow { + return favoritesRepository.isHidden(searchable) + } + + fun setHidden(searchable: Searchable, hidden: Boolean) { + if(hidden) { + favoritesRepository.hideItem(searchable) + } else { + favoritesRepository.unhideItem(searchable) + } + } + + fun getIcon(searchable: Searchable, size: Int): Flow { + return iconRepository.getIcon(searchable, size) + } + + fun launch(context: Context, searchable: Searchable) { + val bundle = Bundle() + if (isAtLeastApiLevel(31)) { + bundle.putInt("android.activity.splashScreenStyle", 1) + } + searchable.launch(context, bundle) + } + + fun openAppInfo(context: Context, app: Application) { + val launcherApps = context.getSystemService()!! + + if (app is LauncherApp) { + launcherApps.startAppDetailsActivity( + ComponentName(app.`package`, app.activity), + app.getUser(), + null, + null + ) + } else { + context.tryStartActivity( + Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = Uri.parse("package:${app.`package`}") + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + ) + } + } +} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreen.kt index 524735e5..1fed3425 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreen.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreen.kt @@ -191,6 +191,13 @@ fun SearchSettingsScreen() { onValueChanged = { viewModel.setAutoFocus(it) }) + Preference( + title = stringResource(R.string.preference_hidden_items), + summary = stringResource(R.string.preference_hidden_items_summary), + onClick = { + navController?.navigate("settings/search/hiddenitems") + } + ) } } } diff --git a/ui/src/main/res/menu/menu_launcher.xml b/ui/src/main/res/menu/menu_launcher.xml deleted file mode 100644 index 6fe0a585..00000000 --- a/ui/src/main/res/menu/menu_launcher.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - \ No newline at end of file