From 2c41d05ed301daf883fadbc7905e4d3266f6e042 Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sat, 21 May 2022 19:29:29 +0200 Subject: [PATCH] Display undo snackbar after hiding an item --- .../launcher2/ui/launcher/LauncherActivity.kt | 17 +++++++++---- .../ui/launcher/search/apps/AppItem.kt | 17 +++++++++++++ .../launcher/search/calendar/CalendarItem.kt | 18 ++++++++++++++ .../launcher/search/common/list/ListItem.kt | 24 ++++++++++++++++++- .../launcher/search/contacts/ContactItem.kt | 18 ++++++++++++++ .../ui/launcher/search/files/FileItem.kt | 23 ++++++++++++++---- .../launcher/search/shortcut/ShortcutItem.kt | 19 +++++++++++++++ .../launcher2/ui/locals/CompositionLocals.kt | 3 +++ 8 files changed, 129 insertions(+), 10 deletions(-) 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 26f13b4d..09b2bb35 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 @@ -7,14 +7,14 @@ import android.os.Bundle import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.* +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Size @@ -38,6 +38,7 @@ import de.mm20.launcher2.ui.launcher.modals.EditFavoritesView import de.mm20.launcher2.ui.launcher.modals.HiddenItemsSheet import de.mm20.launcher2.ui.launcher.transitions.HomeTransitionManager import de.mm20.launcher2.ui.launcher.transitions.LocalHomeTransitionManager +import de.mm20.launcher2.ui.locals.LocalSnackbarHostState import de.mm20.launcher2.ui.locals.LocalWindowSize import de.mm20.launcher2.ui.theme.LauncherTheme import org.koin.android.ext.android.inject @@ -61,9 +62,11 @@ class LauncherActivity : BaseActivity() { viewModel.setDarkMode(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) setContent { + val snackbarHostState = remember { SnackbarHostState() } CompositionLocalProvider( LocalHomeTransitionManager provides homeTransitionManager, - LocalWindowSize provides windowSize + LocalWindowSize provides windowSize, + LocalSnackbarHostState provides snackbarHostState ) { LauncherTheme { ProvideSettings { @@ -111,6 +114,10 @@ class LauncherActivity : BaseActivity() { } else -> {} } + SnackbarHost( + snackbarHostState, + modifier = Modifier.navigationBarsPadding().imePadding() + ) } val showHiddenItems by viewModel.isHiddenItemsShown.observeAsState(false) if (showHiddenItems) { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt index 5e63595e..9a1331a0 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt @@ -8,16 +8,19 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.* import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.core.app.NotificationCompat +import androidx.lifecycle.lifecycleScope import coil.compose.rememberImagePainter import com.google.accompanist.flowlayout.FlowRow import de.mm20.launcher2.search.data.Application @@ -27,6 +30,7 @@ import de.mm20.launcher2.ui.ktx.toDp import de.mm20.launcher2.ui.ktx.toPixels import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled import de.mm20.launcher2.ui.locals.LocalGridIconSize +import de.mm20.launcher2.ui.locals.LocalSnackbarHostState import de.mm20.launcher2.ui.modifier.scale import kotlinx.coroutines.launch import kotlin.math.min @@ -42,6 +46,9 @@ fun AppItem( ) { val viewModel = remember { AppItemVM(app) } val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + val snackbarHostState = LocalSnackbarHostState.current + val scope = rememberCoroutineScope() Column( modifier = modifier @@ -275,6 +282,16 @@ fun AppItem( action = { viewModel.hide() onBack() + lifecycleOwner.lifecycleScope.launch { + val result = snackbarHostState.showSnackbar( + message = context.getString(R.string.msg_item_hidden, app.label), + actionLabel = context.getString(R.string.action_undo), + + ) + if(result == SnackbarResult.ActionPerformed) { + viewModel.unhide() + } + } }) } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt index acfa753f..6a1dfcf7 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt @@ -11,6 +11,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.* import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -23,8 +24,10 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.lifecycleScope import de.mm20.launcher2.search.data.CalendarEvent import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.animation.animateTextStyleAsState @@ -33,6 +36,8 @@ import de.mm20.launcher2.ui.component.Toolbar import de.mm20.launcher2.ui.component.ToolbarAction import de.mm20.launcher2.ui.ktx.toDp import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled +import de.mm20.launcher2.ui.locals.LocalSnackbarHostState +import kotlinx.coroutines.launch @Composable fun CalendarItem( @@ -44,6 +49,9 @@ fun CalendarItem( val context = LocalContext.current val viewModel = remember(calendar.key) { CalendarItemVM(calendar) } + val lifecycleOwner = LocalLifecycleOwner.current + val snackbarHostState = LocalSnackbarHostState.current + Row( modifier = modifier .drawBehind { @@ -190,6 +198,16 @@ fun CalendarItem( action = { viewModel.hide() onBack() + lifecycleOwner.lifecycleScope.launch { + val result = snackbarHostState.showSnackbar( + message = context.getString(R.string.msg_item_hidden, calendar.label), + actionLabel = context.getString(R.string.action_undo), + + ) + if(result == SnackbarResult.ActionPerformed) { + viewModel.unhide() + } + } }) } toolbarActions.add(hideAction) diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/ListItem.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/ListItem.kt index b8b2df8a..afce4b41 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/ListItem.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/ListItem.kt @@ -16,6 +16,7 @@ import androidx.compose.material.icons.rounded.StarOutline import androidx.compose.material.icons.rounded.Visibility import androidx.compose.material.icons.rounded.VisibilityOff import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarResult import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -24,8 +25,12 @@ import androidx.compose.ui.geometry.Rect import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.lifecycleScope import de.mm20.launcher2.search.data.* +import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.InnerCard import de.mm20.launcher2.ui.launcher.search.calendar.CalendarItem import de.mm20.launcher2.ui.launcher.search.contacts.ContactItem @@ -33,6 +38,8 @@ import de.mm20.launcher2.ui.launcher.search.files.FileItem import de.mm20.launcher2.ui.launcher.search.shortcut.AppShortcutItem import de.mm20.launcher2.ui.locals.LocalCardStyle import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled +import de.mm20.launcher2.ui.locals.LocalSnackbarHostState +import kotlinx.coroutines.launch @OptIn(ExperimentalMaterialApi::class, androidx.compose.foundation.ExperimentalFoundationApi::class) @Composable @@ -44,6 +51,9 @@ fun ListItem(modifier: Modifier = Modifier, item: Searchable) { val isPinned by viewModel.isPinned.collectAsState(false) val isHidden by viewModel.isHidden.collectAsState(false) + val lifecycleOwner = LocalLifecycleOwner.current + val snackbarHostState = LocalSnackbarHostState.current + val dismissState = rememberDismissState( confirmStateChange = { when (it) { @@ -54,7 +64,19 @@ fun ListItem(modifier: Modifier = Modifier, item: Searchable) { } DismissValue.DismissedToStart -> { if (isHidden) viewModel.unhide() - else viewModel.hide() + else { + viewModel.hide() + lifecycleOwner.lifecycleScope.launch { + val result = snackbarHostState.showSnackbar( + message = context.getString(R.string.msg_item_hidden, item.label), + actionLabel = context.getString(R.string.action_undo), + + ) + if(result == SnackbarResult.ActionPerformed) { + viewModel.unhide() + } + } + } } } it == DismissValue.DismissedToStart diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/contacts/ContactItem.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/contacts/ContactItem.kt index 1d9a39f8..4a9d3843 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/contacts/ContactItem.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/contacts/ContactItem.kt @@ -12,6 +12,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.* import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -22,10 +23,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.ExperimentalUnitApi import androidx.compose.ui.unit.dp +import androidx.lifecycle.lifecycleScope import de.mm20.launcher2.search.data.Contact import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.animation.animateTextStyleAsState @@ -36,7 +39,9 @@ import de.mm20.launcher2.ui.ktx.toDp import de.mm20.launcher2.ui.ktx.toPixels import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled import de.mm20.launcher2.ui.locals.LocalGridIconSize +import de.mm20.launcher2.ui.locals.LocalSnackbarHostState import de.mm20.launcher2.ui.modifier.scale +import kotlinx.coroutines.launch @OptIn(ExperimentalUnitApi::class) @Composable @@ -49,6 +54,9 @@ fun ContactItem( val context = LocalContext.current val viewModel = remember(contact) { ContactItemVM(contact) } + val lifecycleOwner = LocalLifecycleOwner.current + val snackbarHostState = LocalSnackbarHostState.current + val transition = updateTransition(showDetails, label = "ContactItem") Column( @@ -257,6 +265,16 @@ fun ContactItem( action = { viewModel.hide() onBack() + lifecycleOwner.lifecycleScope.launch { + val result = snackbarHostState.showSnackbar( + message = context.getString(R.string.msg_item_hidden, contact.label), + actionLabel = context.getString(R.string.action_undo), + + ) + if(result == SnackbarResult.ActionPerformed) { + viewModel.unhide() + } + } }) } toolbarActions.add(hideAction) diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/files/FileItem.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/files/FileItem.kt index bca07a90..c12c75f3 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/files/FileItem.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/files/FileItem.kt @@ -8,20 +8,19 @@ import androidx.compose.animation.core.updateTransition import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.* -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton +import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.ExperimentalUnitApi import androidx.compose.ui.unit.dp +import androidx.lifecycle.lifecycleScope import de.mm20.launcher2.search.data.File import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.animation.animateTextStyleAsState @@ -33,7 +32,9 @@ import de.mm20.launcher2.ui.ktx.toDp import de.mm20.launcher2.ui.ktx.toPixels import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled import de.mm20.launcher2.ui.locals.LocalGridIconSize +import de.mm20.launcher2.ui.locals.LocalSnackbarHostState import de.mm20.launcher2.ui.modifier.scale +import kotlinx.coroutines.launch import java.text.DecimalFormat import kotlin.math.roundToInt @@ -48,6 +49,9 @@ fun FileItem( val context = LocalContext.current val viewModel = remember(file.key) { FileItemVM(file) } + val lifecycleOwner = LocalLifecycleOwner.current + val snackbarHostState = LocalSnackbarHostState.current + val transition = updateTransition(showDetails, label = "ContactItem") Column( @@ -235,6 +239,17 @@ fun FileItem( action = { viewModel.hide() onBack() + lifecycleOwner.lifecycleScope.launch { + val result = snackbarHostState.showSnackbar( + message = context.getString(R.string.msg_item_hidden, file.label), + actionLabel = context.getString(R.string.action_undo), + + ) + if(result == SnackbarResult.ActionPerformed) { + viewModel.unhide() + } + } + }) } toolbarActions.add(hideAction) diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/shortcut/ShortcutItem.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/shortcut/ShortcutItem.kt index 5200f8fb..118d3995 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/shortcut/ShortcutItem.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/shortcut/ShortcutItem.kt @@ -8,6 +8,7 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.* import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -17,9 +18,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.lifecycle.lifecycleScope import de.mm20.launcher2.search.data.AppShortcut import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.animation.animateTextStyleAsState @@ -31,7 +34,9 @@ import de.mm20.launcher2.ui.ktx.toDp import de.mm20.launcher2.ui.ktx.toPixels import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled import de.mm20.launcher2.ui.locals.LocalGridIconSize +import de.mm20.launcher2.ui.locals.LocalSnackbarHostState import de.mm20.launcher2.ui.modifier.scale +import kotlinx.coroutines.launch import kotlin.math.pow import kotlin.math.roundToInt @@ -46,6 +51,9 @@ fun AppShortcutItem( val viewModel = remember { ShortcutItemVM(shortcut) } val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + val snackbarHostState = LocalSnackbarHostState.current + val transition = updateTransition(showDetails, label = "AppShortcutItem") Column( @@ -143,6 +151,17 @@ fun AppShortcutItem( action = { viewModel.hide() onBack() + + lifecycleOwner.lifecycleScope.launch { + val result = snackbarHostState.showSnackbar( + message = context.getString(R.string.msg_item_hidden, shortcut.label), + actionLabel = context.getString(R.string.action_undo), + + ) + if(result == SnackbarResult.ActionPerformed) { + viewModel.unhide() + } + } }) } toolbarActions.add(hideAction) diff --git a/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt b/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt index 1641b915..05b115fb 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt @@ -1,6 +1,7 @@ package de.mm20.launcher2.ui.locals import android.appwidget.AppWidgetHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.compositionLocalOf import androidx.compose.ui.geometry.Size import androidx.compose.ui.platform.LocalContext @@ -22,6 +23,8 @@ val LocalGridColumns = compositionLocalOf { 5 } val LocalGridIconSize = compositionLocalOf { 48.dp } +val LocalSnackbarHostState = compositionLocalOf { SnackbarHostState() } + /** * Workaround a bug in Jetpack Compose which incorrectly places popups * that are nested inside other popups.