From 3e029b16caaa33eb3d88d3e3d6cf762a2b2f0eeb Mon Sep 17 00:00:00 2001 From: gabriel20xx Date: Tue, 11 Nov 2025 20:23:42 +0100 Subject: [PATCH] feat: implement notification system for list events, including added, edited, and removed notifications with user preferences and background checks --- CollabTableAndroid/app/build.gradle | 4 + .../app/src/main/AndroidManifest.xml | 2 + .../collabtable/app/CollabTableApplication.kt | 47 ++++++ .../data/preferences/PreferencesManager.kt | 51 ++++++ .../app/notifications/NotificationHelper.kt | 93 +++++++++++ .../app/ui/screens/ListsViewModel.kt | 32 ++++ .../app/ui/screens/ServerSetupScreen.kt | 22 ++- .../app/ui/screens/SettingsScreen.kt | 153 ++++++++++++++++++ .../app/work/ListChangeNotificationWorker.kt | 56 +++++++ 9 files changed, 459 insertions(+), 1 deletion(-) create mode 100644 CollabTableAndroid/app/src/main/java/com/collabtable/app/notifications/NotificationHelper.kt create mode 100644 CollabTableAndroid/app/src/main/java/com/collabtable/app/work/ListChangeNotificationWorker.kt diff --git a/CollabTableAndroid/app/build.gradle b/CollabTableAndroid/app/build.gradle index e8d3108..d57dab8 100644 --- a/CollabTableAndroid/app/build.gradle +++ b/CollabTableAndroid/app/build.gradle @@ -54,6 +54,7 @@ dependencies { // Core Android implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2' + implementation 'androidx.lifecycle:lifecycle-process:2.6.2' implementation 'androidx.activity:activity-compose:1.8.1' // Compose @@ -81,6 +82,9 @@ dependencies { // Coroutines implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' + // WorkManager for background checks/notifications + implementation 'androidx.work:work-runtime-ktx:2.9.0' + // Testing testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' diff --git a/CollabTableAndroid/app/src/main/AndroidManifest.xml b/CollabTableAndroid/app/src/main/AndroidManifest.xml index 3966920..a6215ea 100644 --- a/CollabTableAndroid/app/src/main/AndroidManifest.xml +++ b/CollabTableAndroid/app/src/main/AndroidManifest.xml @@ -3,6 +3,8 @@ + + (15, TimeUnit.MINUTES) + .build() + WorkManager.getInstance(this).enqueueUniquePeriodicWork( + "list_change_notifier", + ExistingPeriodicWorkPolicy.UPDATE, + work, + ) } } diff --git a/CollabTableAndroid/app/src/main/java/com/collabtable/app/data/preferences/PreferencesManager.kt b/CollabTableAndroid/app/src/main/java/com/collabtable/app/data/preferences/PreferencesManager.kt index a6e0254..ac52ce9 100644 --- a/CollabTableAndroid/app/src/main/java/com/collabtable/app/data/preferences/PreferencesManager.kt +++ b/CollabTableAndroid/app/src/main/java/com/collabtable/app/data/preferences/PreferencesManager.kt @@ -30,6 +30,16 @@ class PreferencesManager(context: Context) { private val _syncPollIntervalMs = MutableStateFlow(getSyncPollIntervalMs()) val syncPollIntervalMs: StateFlow = _syncPollIntervalMs.asStateFlow() + // Notification preferences (per list event type) + private val _notifyListAdded = MutableStateFlow(isNotifyListAddedEnabled()) + val notifyListAdded: StateFlow = _notifyListAdded.asStateFlow() + + private val _notifyListEdited = MutableStateFlow(isNotifyListEditedEnabled()) + val notifyListEdited: StateFlow = _notifyListEdited.asStateFlow() + + private val _notifyListRemoved = MutableStateFlow(isNotifyListRemovedEnabled()) + val notifyListRemoved: StateFlow = _notifyListRemoved.asStateFlow() + fun getServerUrl(): String { return prefs.getString(KEY_SERVER_URL, DEFAULT_SERVER_URL) ?: DEFAULT_SERVER_URL } @@ -141,6 +151,43 @@ class PreferencesManager(context: Context) { _syncPollIntervalMs.value = clamped } + // Last time we checked lists for background notifications + fun getLastListNotifyCheckTimestamp(): Long { + return prefs.getLong(KEY_LAST_LIST_NOTIFY_CHECK_TS, 0L) + } + + fun setLastListNotifyCheckTimestamp(ts: Long) { + prefs.edit().putLong(KEY_LAST_LIST_NOTIFY_CHECK_TS, ts).apply() + } + + // Notification settings accessors + private fun isNotifyListAddedEnabled(): Boolean { + return prefs.getBoolean(KEY_NOTIFY_LIST_ADDED, true) + } + + fun setNotifyListAddedEnabled(enabled: Boolean) { + prefs.edit().putBoolean(KEY_NOTIFY_LIST_ADDED, enabled).apply() + _notifyListAdded.value = enabled + } + + private fun isNotifyListEditedEnabled(): Boolean { + return prefs.getBoolean(KEY_NOTIFY_LIST_EDITED, true) + } + + fun setNotifyListEditedEnabled(enabled: Boolean) { + prefs.edit().putBoolean(KEY_NOTIFY_LIST_EDITED, enabled).apply() + _notifyListEdited.value = enabled + } + + private fun isNotifyListRemovedEnabled(): Boolean { + return prefs.getBoolean(KEY_NOTIFY_LIST_REMOVED, true) + } + + fun setNotifyListRemovedEnabled(enabled: Boolean) { + prefs.edit().putBoolean(KEY_NOTIFY_LIST_REMOVED, enabled).apply() + _notifyListRemoved.value = enabled + } + // Persist per-list column widths (fieldId -> widthDp) // Stored as a JSON object string in SharedPreferences under key: COLUMN_WIDTHS_PREFIX + listId fun getColumnWidths(listId: String): Map { @@ -183,6 +230,10 @@ class PreferencesManager(context: Context) { private const val KEY_AMOLED_DARK = "amoled_dark" private const val KEY_SORT_ORDER = "sort_order" private const val KEY_SYNC_POLL_INTERVAL_MS = "sync_poll_interval_ms" + private const val KEY_NOTIFY_LIST_ADDED = "notify_list_added" + private const val KEY_NOTIFY_LIST_EDITED = "notify_list_edited" + private const val KEY_NOTIFY_LIST_REMOVED = "notify_list_removed" + private const val KEY_LAST_LIST_NOTIFY_CHECK_TS = "last_list_notify_check_ts" private const val COLUMN_WIDTHS_PREFIX = "column_widths_" // + listId private const val DEFAULT_SERVER_URL = "http://10.0.2.2:3000/api/" private const val DEFAULT_SYNC_POLL_INTERVAL_MS = 250L diff --git a/CollabTableAndroid/app/src/main/java/com/collabtable/app/notifications/NotificationHelper.kt b/CollabTableAndroid/app/src/main/java/com/collabtable/app/notifications/NotificationHelper.kt new file mode 100644 index 0000000..0c5ff2d --- /dev/null +++ b/CollabTableAndroid/app/src/main/java/com/collabtable/app/notifications/NotificationHelper.kt @@ -0,0 +1,93 @@ +package com.collabtable.app.notifications + +import android.app.NotificationManager +import android.content.Context +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import com.collabtable.app.R + +object NotificationHelper { + const val CHANNEL_LIST_EVENTS = "list_events" + private const val GROUP_LIST_EVENTS = "group_list_events" + private const val SUMMARY_ID = 1000 + + // Keep a small rolling buffer of recent event lines for the summary + private val recentEventLines = ArrayDeque() + @Volatile private var totalEventCount: Int = 0 + private val postedIds = mutableSetOf() + + private fun builder(context: Context, title: String, text: String): NotificationCompat.Builder { + return NotificationCompat.Builder(context, CHANNEL_LIST_EVENTS) + .setSmallIcon(R.mipmap.ic_launcher) + .setContentTitle(title) + .setContentText(text) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setAutoCancel(true) + } + + private fun notify(context: Context, id: Int, builder: NotificationCompat.Builder) { + with(NotificationManagerCompat.from(context)) { + notify(id, builder.build()) + } + } + + fun showListAdded(context: Context, listId: String, name: String) = showEvent(context, listId, "add", "List added", "\"$name\" was created") + fun showListEdited(context: Context, listId: String, name: String) = showEvent(context, listId, "edit", "List edited", "\"$name\" was renamed/updated") + fun showListRemoved(context: Context, listId: String, name: String) = showEvent(context, listId, "remove", "List removed", "\"$name\" was deleted") + + private fun showEvent(context: Context, listId: String, type: String, title: String, text: String) { + val b = builder(context, title, text).setGroup(GROUP_LIST_EVENTS) + val id = (type + listId).hashCode() + synchronized(postedIds) { postedIds.add(id) } + notify(context, id, b) + recordEventLine(type, text) + postSummary(context) + } + + private fun postSummary(context: Context) { + val inbox = NotificationCompat.InboxStyle() + synchronized(recentEventLines) { + recentEventLines.take(5).forEach { line -> inbox.addLine(line) } + inbox.setSummaryText("${totalEventCount} total event" + if (totalEventCount == 1) "" else "s") + } + val summary = NotificationCompat.Builder(context, CHANNEL_LIST_EVENTS) + .setSmallIcon(R.mipmap.ic_launcher) + .setContentTitle("List activity") + .setContentText("Recent list changes") + .setStyle(inbox) + .setGroup(GROUP_LIST_EVENTS) + .setGroupSummary(true) + .setAutoCancel(true) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setNumber(totalEventCount) + notify(context, SUMMARY_ID, summary) + } + + private fun recordEventLine(type: String, text: String) { + val line = when (type) { + "add" -> "Added • $text" + "edit" -> "Edited • $text" + "remove" -> "Removed • $text" + else -> text + } + synchronized(recentEventLines) { + recentEventLines.addFirst(line) + // Keep only the last 8 lines to bound memory and UI clutter + while (recentEventLines.size > 8) recentEventLines.removeLast() + } + totalEventCount += 1 + } + + fun clearListEventNotifications(context: Context) { + val mgr = NotificationManagerCompat.from(context) + synchronized(postedIds) { + postedIds.forEach { id -> mgr.cancel(id) } + postedIds.clear() + } + mgr.cancel(SUMMARY_ID) + synchronized(recentEventLines) { + recentEventLines.clear() + totalEventCount = 0 + } + } +} diff --git a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ListsViewModel.kt b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ListsViewModel.kt index f1a026d..abb0679 100644 --- a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ListsViewModel.kt +++ b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ListsViewModel.kt @@ -6,6 +6,9 @@ import androidx.lifecycle.viewModelScope import com.collabtable.app.data.database.CollabTableDatabase import com.collabtable.app.data.model.CollabList import com.collabtable.app.data.repository.SyncRepository +import com.collabtable.app.notifications.NotificationHelper +import androidx.lifecycle.ProcessLifecycleOwner +import androidx.lifecycle.Lifecycle import com.collabtable.app.utils.Logger import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -128,6 +131,7 @@ class ListsViewModel( database.listDao().insertList(newList) // Sync immediately after creating performSync() + maybeNotifyListAdded(newList) } } @@ -147,6 +151,7 @@ class ListsViewModel( ) // Sync immediately after renaming performSync() + maybeNotifyListEdited(list.copy(name = newName.trim())) } } } @@ -164,6 +169,7 @@ class ListsViewModel( database.itemDao().softDeleteItemsByList(listId, ts) // Sync immediately after deleting performSync() + maybeNotifyListRemoved(list) } } } @@ -176,4 +182,30 @@ class ListsViewModel( _isLoading.value = false } } + + private fun isInForeground(): Boolean { + val state = ProcessLifecycleOwner.get().lifecycle.currentState + return state.isAtLeast(Lifecycle.State.STARTED) + } + + private fun maybeNotifyListAdded(list: CollabList) { + val prefs = com.collabtable.app.data.preferences.PreferencesManager.getInstance(context) + if (prefs.notifyListAdded.value && !isInForeground()) { + NotificationHelper.showListAdded(context, list.id, list.name) + } + } + + private fun maybeNotifyListEdited(list: CollabList) { + val prefs = com.collabtable.app.data.preferences.PreferencesManager.getInstance(context) + if (prefs.notifyListEdited.value && !isInForeground()) { + NotificationHelper.showListEdited(context, list.id, list.name) + } + } + + private fun maybeNotifyListRemoved(list: CollabList) { + val prefs = com.collabtable.app.data.preferences.PreferencesManager.getInstance(context) + if (prefs.notifyListRemoved.value && !isInForeground()) { + NotificationHelper.showListRemoved(context, list.id, list.name) + } + } } diff --git a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ServerSetupScreen.kt b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ServerSetupScreen.kt index 0472a11..a979023 100644 --- a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ServerSetupScreen.kt +++ b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ServerSetupScreen.kt @@ -41,7 +41,10 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import com.collabtable.app.data.preferences.PreferencesManager +import android.os.Build @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -57,9 +60,26 @@ fun ServerSetupScreen(onSetupComplete: () -> Unit) { val validationResult by viewModel.validationResult.collectAsState() val validationError by viewModel.validationError.collectAsState() + val permissionLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> + // If granted, enable all; else disable all + preferencesManager.setNotifyListAddedEnabled(granted) + preferencesManager.setNotifyListEditedEnabled(granted) + preferencesManager.setNotifyListRemovedEnabled(granted) + onSetupComplete() + } + LaunchedEffect(validationResult) { if (validationResult == true) { - onSetupComplete() + // On Android 13+ prompt for notifications; otherwise default to enabled + if (Build.VERSION.SDK_INT >= 33) { + permissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS) + } else { + preferencesManager.setNotifyListAddedEnabled(true) + preferencesManager.setNotifyListEditedEnabled(true) + preferencesManager.setNotifyListRemovedEnabled(true) + onSetupComplete() + } } } diff --git a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/SettingsScreen.kt b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/SettingsScreen.kt index 2ac96f1..1839a55 100644 --- a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/SettingsScreen.kt +++ b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/SettingsScreen.kt @@ -29,6 +29,8 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -36,6 +38,8 @@ import androidx.compose.material3.TextField import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -47,6 +51,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import android.Manifest +import android.content.pm.PackageManager +import android.os.Build +import androidx.core.content.ContextCompat import com.collabtable.app.R import com.collabtable.app.data.api.ApiClient import com.collabtable.app.data.database.CollabTableDatabase @@ -70,11 +78,15 @@ fun SettingsScreen( val preferencesManager = remember { PreferencesManager.getInstance(context) } val syncRepository = remember { SyncRepository(context) } val coroutineScope = rememberCoroutineScope() + val snackbarHostState = remember { SnackbarHostState() } val serverUrl = preferencesManager.getServerUrl() val themeMode by preferencesManager.themeMode.collectAsState() val dynamicColor by preferencesManager.dynamicColor.collectAsState() val amoledDark by preferencesManager.amoledDark.collectAsState() + val notifyListAdded by preferencesManager.notifyListAdded.collectAsState() + val notifyListEdited by preferencesManager.notifyListEdited.collectAsState() + val notifyListRemoved by preferencesManager.notifyListRemoved.collectAsState() val displayUrl = remember(serverUrl) { formatServerUrlForDisplay(serverUrl) } var showLeaveDialog by remember { mutableStateOf(false) } var isLeaving by remember { mutableStateOf(false) } @@ -83,6 +95,38 @@ fun SettingsScreen( var syncIntervalError by remember { mutableStateOf(null) } // Authentication UI removed + // Permission launcher for Android 13+ notification permission + var pendingPermissionAction by remember { mutableStateOf<(() -> Unit)?>(null) } + val permissionLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> + if (granted) { + pendingPermissionAction?.invoke() + } else { + coroutineScope.launch { + snackbarHostState.showSnackbar( + message = "Notification permission denied. Enable it in system settings to turn this on.", + ) + } + } + pendingPermissionAction = null + } + + fun ensureNotificationPermission(onGranted: () -> Unit) { + if (Build.VERSION.SDK_INT < 33) { + onGranted() + return + } + val granted = + ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == + PackageManager.PERMISSION_GRANTED + if (granted) { + onGranted() + } else { + pendingPermissionAction = onGranted + permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } + } + // Removed test connectivity logic; the connection indicator is shown via ConnectionStatusAction Scaffold( @@ -104,6 +148,7 @@ fun SettingsScreen( ), ) }, + snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, ) { padding -> Column( modifier = @@ -372,6 +417,114 @@ fun SettingsScreen( } + // Notifications section (placed after main content but before dialogs) + Column( + modifier = + Modifier + .fillMaxWidth() + .padding(padding) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Text( + text = "Notifications", + style = MaterialTheme.typography.titleMedium, + ) + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant), + ) { + Column( + modifier = Modifier.padding(12.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.weight(1f)) { + Text("List added") + Text( + text = "Notify when a new list/table is created", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + Switch( + checked = notifyListAdded, + onCheckedChange = { checked -> + if (checked) { + ensureNotificationPermission { + preferencesManager.setNotifyListAddedEnabled(true) + } + } else { + preferencesManager.setNotifyListAddedEnabled(false) + } + }, + ) + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.weight(1f)) { + Text("List edited") + Text( + text = "Notify when a list/table name changes", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + Switch( + checked = notifyListEdited, + onCheckedChange = { checked -> + if (checked) { + ensureNotificationPermission { + preferencesManager.setNotifyListEditedEnabled(true) + } + } else { + preferencesManager.setNotifyListEditedEnabled(false) + } + }, + ) + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.weight(1f)) { + Text("List removed") + Text( + text = "Notify when a list/table is deleted", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + Switch( + checked = notifyListRemoved, + onCheckedChange = { checked -> + if (checked) { + ensureNotificationPermission { + preferencesManager.setNotifyListRemovedEnabled(true) + } + } else { + preferencesManager.setNotifyListRemovedEnabled(false) + } + }, + ) + } + Text( + text = "Notifications fire only when the app is in background or not visible.", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } + if (showLeaveDialog) { AlertDialog( onDismissRequest = { showLeaveDialog = false }, diff --git a/CollabTableAndroid/app/src/main/java/com/collabtable/app/work/ListChangeNotificationWorker.kt b/CollabTableAndroid/app/src/main/java/com/collabtable/app/work/ListChangeNotificationWorker.kt new file mode 100644 index 0000000..b04db8f --- /dev/null +++ b/CollabTableAndroid/app/src/main/java/com/collabtable/app/work/ListChangeNotificationWorker.kt @@ -0,0 +1,56 @@ +package com.collabtable.app.work + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import com.collabtable.app.data.database.CollabTableDatabase +import com.collabtable.app.data.model.CollabList +import com.collabtable.app.data.preferences.PreferencesManager +import com.collabtable.app.notifications.NotificationHelper + +class ListChangeNotificationWorker( + appContext: Context, + workerParams: WorkerParameters, +) : CoroutineWorker(appContext, workerParams) { + override suspend fun doWork(): Result { + val prefs = PreferencesManager.getInstance(applicationContext) + val db = CollabTableDatabase.getDatabase(applicationContext) + + val since = prefs.getLastListNotifyCheckTimestamp() + val now = System.currentTimeMillis() + return try { + val changed = db.listDao().getListsUpdatedSince(since) + if (changed.isNotEmpty()) { + changed.forEach { list -> + emitNotificationsFor(list, prefs) + } + } + // Update checkpoint regardless to avoid duplicate spam + prefs.setLastListNotifyCheckTimestamp(now) + Result.success() + } catch (e: Exception) { + Result.retry() + } + } + + private fun emitNotificationsFor(list: CollabList, prefs: PreferencesManager) { + // Determine event type + when { + list.isDeleted -> { + if (prefs.notifyListRemoved.value) { + NotificationHelper.showListRemoved(applicationContext, list.id, list.name) + } + } + list.createdAt == list.updatedAt -> { + if (prefs.notifyListAdded.value) { + NotificationHelper.showListAdded(applicationContext, list.id, list.name) + } + } + else -> { + if (prefs.notifyListEdited.value) { + NotificationHelper.showListEdited(applicationContext, list.id, list.name) + } + } + } + } +}