mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 16:14:40 +00:00
feat: extract notification settings into dedicated screen
Group every notification-related preference under a new "Notifications" entry in the settings menu, rendered with the modern card/section design used by SecurityFiltersScreen. Moves out of "UI preferences": - Push notification provider (UnifiedPush, fdroid) - Always-on notification service + battery optimization banner - Split notifications by Follows The fdroid PushNotificationSettingsRow becomes PushNotificationProviderTile built around SettingsBlockTile; the play stub gains a matching HasPushNotificationProvider() so the push section is hidden on Play builds.
This commit is contained in:
+26
-15
@@ -59,7 +59,7 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.service.notifications.PushDistributorHandler
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsBlockTile
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@@ -208,23 +208,34 @@ fun LoadDistributors(onInner: @Composable (String, ImmutableList<String>, Immuta
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PushNotificationSettingsRow(sharedPrefs: UiSettingsFlow) {
|
||||
fun HasPushNotificationProvider(): Boolean = true
|
||||
|
||||
@Composable
|
||||
fun PushNotificationProviderTile(sharedPrefs: UiSettingsFlow) {
|
||||
val context = LocalContext.current
|
||||
|
||||
LoadDistributors { currentDistributor, list, readableListWithExplainer ->
|
||||
SettingsRow(
|
||||
R.string.push_server_title,
|
||||
R.string.push_server_explainer,
|
||||
selectedItems = readableListWithExplainer,
|
||||
selectedIndex = list.indexOf(currentDistributor),
|
||||
) { index ->
|
||||
if (list[index] == "None") {
|
||||
sharedPrefs.dontAskForNotificationPermissions()
|
||||
sharedPrefs.dontShowPushNotificationSelector()
|
||||
PushDistributorHandler.forceRemoveDistributor(context)
|
||||
} else {
|
||||
PushDistributorHandler.saveDistributor(list[index])
|
||||
}
|
||||
val selectedIndex = list.indexOf(currentDistributor).coerceAtLeast(0)
|
||||
SettingsBlockTile(
|
||||
icon = MaterialSymbols.CloudSync,
|
||||
title = stringRes(R.string.push_server_title),
|
||||
description = stringRes(R.string.push_server_explainer),
|
||||
) {
|
||||
TextSpinner(
|
||||
label = null,
|
||||
placeholder = readableListWithExplainer[selectedIndex].title,
|
||||
options = readableListWithExplainer,
|
||||
onSelect = { index ->
|
||||
if (list[index] == "None") {
|
||||
sharedPrefs.dontAskForNotificationPermissions()
|
||||
sharedPrefs.dontShowPushNotificationSelector()
|
||||
PushDistributorHandler.forceRemoveDistributor(context)
|
||||
} else {
|
||||
PushDistributorHandler.saveDistributor(list[index])
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.HomeTabsSettingsSc
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.MutedThreadsScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NIP47SetupScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NamecoinSettingsScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NotificationSettingsScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.OtsSettingsScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.ProfileUiSettingsScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.ReactionsSettingsScreen
|
||||
@@ -326,6 +327,7 @@ fun BuildNavigation(
|
||||
composableFromEnd<Route.ProfileUiSettings> { ProfileUiSettingsScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.VideoPlayerSettings> { VideoPlayerSettingsScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.CallSettings> { CallSettingsScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.NotificationSettings> { NotificationSettingsScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.ImportFollowsSelectUser> { ImportFollowListSelectUserScreen(accountViewModel, nav) }
|
||||
composableFromEndArgs<Route.ImportFollowsPickFollows> {
|
||||
ImportFollowListPickFollowsScreen(it.userHex, accountViewModel, nav)
|
||||
|
||||
@@ -239,6 +239,8 @@ sealed class Route {
|
||||
|
||||
@Serializable object CallSettings : Route()
|
||||
|
||||
@Serializable object NotificationSettings : Route()
|
||||
|
||||
@Serializable object Lists : Route()
|
||||
|
||||
@Serializable data class MyPeopleListView(
|
||||
|
||||
+6
@@ -218,6 +218,12 @@ fun AllSettingsScreen(
|
||||
onClick = { nav.nav(Route.Settings) },
|
||||
)
|
||||
SettingsDivider()
|
||||
SettingsItem(
|
||||
title = R.string.notification_settings,
|
||||
icon = MaterialSymbols.Notifications,
|
||||
onClick = { nav.nav(Route.NotificationSettings) },
|
||||
)
|
||||
SettingsDivider()
|
||||
SettingsItem(
|
||||
title = R.string.compose_settings,
|
||||
icon = MaterialSymbols.Edit,
|
||||
|
||||
+2
-104
@@ -31,12 +31,8 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@@ -46,14 +42,11 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.BuildConfig
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||
@@ -65,8 +58,6 @@ import com.vitorpamplona.amethyst.model.parseConnectivityType
|
||||
import com.vitorpamplona.amethyst.model.parseFeatureSetType
|
||||
import com.vitorpamplona.amethyst.model.parseGalleryType
|
||||
import com.vitorpamplona.amethyst.model.parseThemeType
|
||||
import com.vitorpamplona.amethyst.service.notifications.BatteryOptimizationHelper
|
||||
import com.vitorpamplona.amethyst.ui.components.PushNotificationSettingsRow
|
||||
import com.vitorpamplona.amethyst.ui.components.TextSpinner
|
||||
import com.vitorpamplona.amethyst.ui.components.TitleExplainer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
@@ -97,7 +88,7 @@ fun SettingsScreen(
|
||||
},
|
||||
) {
|
||||
Column(Modifier.padding(it)) {
|
||||
SettingsScreen(accountViewModel.settings.uiSettingsFlow, accountViewModel)
|
||||
SettingsScreen(accountViewModel.settings.uiSettingsFlow)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,10 +102,7 @@ fun SettingsScreenPreview() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
sharedPrefs: UiSettingsFlow,
|
||||
accountViewModel: AccountViewModel? = null,
|
||||
) {
|
||||
fun SettingsScreen(sharedPrefs: UiSettingsFlow) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
@@ -128,18 +116,11 @@ fun SettingsScreen(
|
||||
ShowImagePreviewChoice(sharedPrefs)
|
||||
ShowVideoPlaybackChoice(sharedPrefs)
|
||||
AutoplayVideosChoice(sharedPrefs)
|
||||
if (BuildConfig.FLAVOR == "play") {
|
||||
}
|
||||
ShowUrlPreviewChoice(sharedPrefs)
|
||||
ShowProfilePictureChoice(sharedPrefs)
|
||||
ImmersiveScrollingChoice(sharedPrefs)
|
||||
FeatureSetChoice(sharedPrefs)
|
||||
GalleryChoice(sharedPrefs)
|
||||
PushNotificationSettingsRow(sharedPrefs)
|
||||
if (accountViewModel != null) {
|
||||
AlwaysOnNotificationServiceChoice(accountViewModel)
|
||||
SplitNotificationsChoice(accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,86 +470,3 @@ fun SettingsRow(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AlwaysOnNotificationServiceChoice(accountViewModel: AccountViewModel) {
|
||||
val enabled by accountViewModel.account.settings.alwaysOnNotificationService
|
||||
.collectAsStateWithLifecycle()
|
||||
|
||||
SettingsRow(
|
||||
R.string.always_on_notif_setting_title,
|
||||
R.string.always_on_notif_setting_description,
|
||||
) {
|
||||
Switch(
|
||||
checked = enabled,
|
||||
onCheckedChange = {
|
||||
accountViewModel.account.settings.toggleAlwaysOnNotificationService()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
BatteryOptimizationBanner()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SplitNotificationsChoice(accountViewModel: AccountViewModel) {
|
||||
val enabled by accountViewModel.account.settings.splitNotificationsEnabled
|
||||
.collectAsStateWithLifecycle()
|
||||
|
||||
SettingsRow(
|
||||
R.string.split_notifications_setting_title,
|
||||
R.string.split_notifications_setting_description,
|
||||
) {
|
||||
Switch(
|
||||
checked = enabled,
|
||||
onCheckedChange = {
|
||||
accountViewModel.account.settings.toggleSplitNotificationsEnabled()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BatteryOptimizationBanner() {
|
||||
val context = LocalContext.current
|
||||
val isExempt =
|
||||
remember {
|
||||
BatteryOptimizationHelper.isIgnoringBatteryOptimizations(context)
|
||||
}
|
||||
|
||||
if (!isExempt) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors =
|
||||
CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||
),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.battery_optimization_title),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||
)
|
||||
Text(
|
||||
text = stringRes(R.string.battery_optimization_description),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||
)
|
||||
Button(
|
||||
onClick = {
|
||||
BatteryOptimizationHelper.requestBatteryOptimizationExemption(context)
|
||||
},
|
||||
) {
|
||||
Text(stringRes(R.string.battery_optimization_fix_now))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.service.notifications.BatteryOptimizationHelper
|
||||
import com.vitorpamplona.amethyst.ui.components.HasPushNotificationProvider
|
||||
import com.vitorpamplona.amethyst.ui.components.PushNotificationProviderTile
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
|
||||
@Composable
|
||||
fun NotificationSettingsScreen(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { TopBarWithBackButton(stringRes(id = R.string.notification_settings), nav) },
|
||||
) { padding ->
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(padding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(20.dp),
|
||||
) {
|
||||
if (HasPushNotificationProvider()) {
|
||||
SettingsSection(R.string.notification_settings_section_push) {
|
||||
PushNotificationProviderTile(accountViewModel.settings.uiSettingsFlow)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsSection(R.string.notification_settings_section_in_app) {
|
||||
AlwaysOnServiceTile(accountViewModel)
|
||||
SettingsDivider()
|
||||
SplitByFollowsTile(accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AlwaysOnServiceTile(accountViewModel: AccountViewModel) {
|
||||
val enabled by accountViewModel.account.settings.alwaysOnNotificationService
|
||||
.collectAsStateWithLifecycle()
|
||||
|
||||
SwitchTile(
|
||||
icon = MaterialSymbols.Notifications,
|
||||
title = R.string.always_on_notif_setting_title,
|
||||
description = R.string.always_on_notif_setting_description,
|
||||
checked = enabled,
|
||||
onCheckedChange = { accountViewModel.account.settings.toggleAlwaysOnNotificationService() },
|
||||
)
|
||||
|
||||
if (enabled) {
|
||||
BatteryOptimizationBanner()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SplitByFollowsTile(accountViewModel: AccountViewModel) {
|
||||
val enabled by accountViewModel.account.settings.splitNotificationsEnabled
|
||||
.collectAsStateWithLifecycle()
|
||||
|
||||
SwitchTile(
|
||||
icon = MaterialSymbols.Forum,
|
||||
title = R.string.split_notifications_setting_title,
|
||||
description = R.string.split_notifications_setting_description,
|
||||
checked = enabled,
|
||||
onCheckedChange = { accountViewModel.account.settings.toggleSplitNotificationsEnabled() },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SwitchTile(
|
||||
icon: MaterialSymbol,
|
||||
@StringRes title: Int,
|
||||
@StringRes description: Int,
|
||||
checked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
) {
|
||||
SettingsControlRow(
|
||||
icon = icon,
|
||||
title = stringRes(title),
|
||||
description = stringRes(description),
|
||||
onClick = { onCheckedChange(!checked) },
|
||||
) {
|
||||
Switch(checked = checked, onCheckedChange = onCheckedChange)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BatteryOptimizationBanner() {
|
||||
val context = LocalContext.current
|
||||
var isExempt by remember {
|
||||
mutableStateOf(BatteryOptimizationHelper.isIgnoringBatteryOptimizations(context))
|
||||
}
|
||||
|
||||
if (!isExempt) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
colors =
|
||||
CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||
),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.battery_optimization_title),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||
)
|
||||
Text(
|
||||
text = stringRes(R.string.battery_optimization_description),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||
)
|
||||
Button(
|
||||
onClick = {
|
||||
BatteryOptimizationHelper.requestBatteryOptimizationExemption(context)
|
||||
isExempt = BatteryOptimizationHelper.isIgnoringBatteryOptimizations(context)
|
||||
},
|
||||
) {
|
||||
Text(stringRes(R.string.battery_optimization_fix_now))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun NotificationSettingsScreenPreview() {
|
||||
ThemeComparisonColumn {
|
||||
NotificationSettingsScreen(mockAccountViewModel(), EmptyNav())
|
||||
}
|
||||
}
|
||||
@@ -1674,9 +1674,13 @@
|
||||
<string name="read_only_user">Read-only user</string>
|
||||
<string name="no_reactions_setup">No reactions setup</string>
|
||||
|
||||
<string name="notification_settings">Notifications</string>
|
||||
<string name="notification_settings_section_in_app">In-app notifications</string>
|
||||
<string name="notification_settings_section_push">Push notifications</string>
|
||||
|
||||
<string name="select_push_server">Select a UnifiedPush App</string>
|
||||
<string name="push_server_title">Push Notification</string>
|
||||
<string name="push_server_explainer">From installed UnifiedPush apps</string>
|
||||
<string name="push_server_title">Push provider</string>
|
||||
<string name="push_server_explainer">Pick a UnifiedPush app to deliver notifications when Amethyst is closed.</string>
|
||||
<string name="push_server_none">None</string>
|
||||
<string name="push_server_none_explainer">Disables Push Notifications</string>
|
||||
<string name="push_server_uses_app_explainer">Uses app %1$s</string>
|
||||
|
||||
+4
-1
@@ -52,4 +52,7 @@ fun SelectNotificationProvider(sharedPrefs: UiSettingsFlow) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PushNotificationSettingsRow(sharedPrefs: UiSettingsFlow) {}
|
||||
fun PushNotificationProviderTile(sharedPrefs: UiSettingsFlow) {}
|
||||
|
||||
@Composable
|
||||
fun HasPushNotificationProvider(): Boolean = false
|
||||
|
||||
Reference in New Issue
Block a user