mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 09:20:19 +01:00
Add improved notification settings when muted.
This commit is contained in:
@@ -592,11 +592,19 @@ class ConversationSettingsFragment :
|
||||
|
||||
if (!state.recipient.isSelf) {
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.ConversationSettingsFragment__sounds_and_notifications),
|
||||
title = if (RemoteConfig.internalUser) {
|
||||
DSLSettingsText.from("${getString(R.string.ConversationSettingsFragment__sounds_and_notifications)} (Internal Only)")
|
||||
} else {
|
||||
DSLSettingsText.from(R.string.ConversationSettingsFragment__sounds_and_notifications)
|
||||
},
|
||||
icon = DSLSettingsIcon.from(R.drawable.symbol_speaker_24),
|
||||
isEnabled = !state.isDeprecatedOrUnregistered,
|
||||
onClick = {
|
||||
val action = ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToSoundsAndNotificationsSettingsFragment(state.recipient.id)
|
||||
val action = if (RemoteConfig.internalUser) {
|
||||
ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToSoundsAndNotificationsSettingsFragment2(state.recipient.id)
|
||||
} else {
|
||||
ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToSoundsAndNotificationsSettingsFragment(state.recipient.id)
|
||||
}
|
||||
|
||||
navController.safeNavigate(action)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.conversation.sounds
|
||||
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.NotificationSetting
|
||||
|
||||
/**
|
||||
* Represents all user-driven actions that can occur on the Sounds & Notifications settings screen.
|
||||
*/
|
||||
sealed interface SoundsAndNotificationsEvent {
|
||||
|
||||
/**
|
||||
* Mutes notifications for this recipient until the given epoch-millisecond timestamp.
|
||||
*
|
||||
* @param muteUntil Epoch-millisecond timestamp after which notifications should resume.
|
||||
* Use [Long.MAX_VALUE] to mute indefinitely.
|
||||
*/
|
||||
data class SetMuteUntil(val muteUntil: Long) : SoundsAndNotificationsEvent
|
||||
|
||||
/**
|
||||
* Clears any active mute, immediately restoring notifications for this recipient.
|
||||
*/
|
||||
data object Unmute : SoundsAndNotificationsEvent
|
||||
|
||||
/**
|
||||
* Updates the mention notification setting for this recipient.
|
||||
* Only relevant for group conversations that support @mentions.
|
||||
*
|
||||
* @param setting The new [NotificationSetting] to apply for @mention notifications.
|
||||
*/
|
||||
data class SetMentionSetting(val setting: NotificationSetting) : SoundsAndNotificationsEvent
|
||||
|
||||
/**
|
||||
* Updates the call notification setting for this recipient.
|
||||
* Controls whether incoming calls still produce notifications while the conversation is muted.
|
||||
*
|
||||
* @param setting The new [NotificationSetting] to apply for call notifications.
|
||||
*/
|
||||
data class SetCallNotificationSetting(val setting: NotificationSetting) : SoundsAndNotificationsEvent
|
||||
|
||||
/**
|
||||
* Updates the reply notification setting for this recipient.
|
||||
* Controls whether replies directed at the current user still produce notifications while muted.
|
||||
*
|
||||
* @param setting The new [NotificationSetting] to apply for reply notifications.
|
||||
*/
|
||||
data class SetReplyNotificationSetting(val setting: NotificationSetting) : SoundsAndNotificationsEvent
|
||||
|
||||
/**
|
||||
* Signals that the user tapped the "Custom Notifications" row and wishes to navigate to the
|
||||
* [custom notifications settings screen][org.thoughtcrime.securesms.components.settings.conversation.sounds.custom.CustomNotificationsSettingsFragment].
|
||||
*/
|
||||
data object NavigateToCustomNotifications : SoundsAndNotificationsEvent
|
||||
}
|
||||
@@ -82,7 +82,7 @@ class SoundsAndNotificationsSettingsFragment :
|
||||
)
|
||||
|
||||
if (state.hasMentionsSupport) {
|
||||
val mentionSelection = if (state.mentionSetting == RecipientTable.MentionSetting.ALWAYS_NOTIFY) {
|
||||
val mentionSelection = if (state.mentionSetting == RecipientTable.NotificationSetting.ALWAYS_NOTIFY) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
@@ -96,9 +96,9 @@ class SoundsAndNotificationsSettingsFragment :
|
||||
onSelected = {
|
||||
viewModel.setMentionSetting(
|
||||
if (it == 0) {
|
||||
RecipientTable.MentionSetting.ALWAYS_NOTIFY
|
||||
RecipientTable.NotificationSetting.ALWAYS_NOTIFY
|
||||
} else {
|
||||
RecipientTable.MentionSetting.DO_NOT_NOTIFY
|
||||
RecipientTable.NotificationSetting.DO_NOT_NOTIFY
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.conversation.sounds
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.Navigation
|
||||
import org.signal.core.ui.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.MuteDialog
|
||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.Utils.formatMutedUntil
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
class SoundsAndNotificationsSettingsFragment2 : ComposeFragment() {
|
||||
|
||||
private val viewModel: SoundsAndNotificationsSettingsViewModel2 by viewModels(
|
||||
factoryProducer = {
|
||||
val recipientId = SoundsAndNotificationsSettingsFragment2Args.fromBundle(requireArguments()).recipientId
|
||||
SoundsAndNotificationsSettingsViewModel2.Factory(recipientId)
|
||||
}
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
if (!state.channelConsistencyCheckComplete || state.recipientId == Recipient.UNKNOWN.id) {
|
||||
return
|
||||
}
|
||||
|
||||
SoundsAndNotificationsSettingsScreen(
|
||||
state = state,
|
||||
formatMuteUntil = { it.formatMutedUntil(requireContext()) },
|
||||
onEvent = { event ->
|
||||
when (event) {
|
||||
is SoundsAndNotificationsEvent.NavigateToCustomNotifications -> {
|
||||
val action = SoundsAndNotificationsSettingsFragment2Directions
|
||||
.actionSoundsAndNotificationsSettingsFragment2ToCustomNotificationsSettingsFragment(state.recipientId)
|
||||
Navigation.findNavController(requireView()).safeNavigate(action)
|
||||
}
|
||||
else -> viewModel.onEvent(event)
|
||||
}
|
||||
},
|
||||
onNavigationClick = {
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
},
|
||||
onMuteClick = {
|
||||
MuteDialog.show(requireContext(), childFragmentManager, viewLifecycleOwner) { muteUntil ->
|
||||
viewModel.onEvent(SoundsAndNotificationsEvent.SetMuteUntil(muteUntil))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ class SoundsAndNotificationsSettingsRepository(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
fun setMentionSetting(recipientId: RecipientId, mentionSetting: RecipientTable.MentionSetting) {
|
||||
fun setMentionSetting(recipientId: RecipientId, mentionSetting: RecipientTable.NotificationSetting) {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
SignalDatabase.recipients.setMentionSetting(recipientId, mentionSetting)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.conversation.sounds
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.AlertDialogDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Surface
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Dialogs
|
||||
import org.signal.core.ui.compose.Dividers
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.Rows
|
||||
import org.signal.core.ui.compose.Scaffolds
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.signal.core.ui.compose.Texts
|
||||
import org.signal.core.ui.compose.horizontalGutters
|
||||
import org.signal.core.ui.compose.theme.SignalTheme
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.NotificationSetting
|
||||
import org.signal.core.ui.R as CoreUiR
|
||||
|
||||
@Composable
|
||||
fun SoundsAndNotificationsSettingsScreen(
|
||||
state: SoundsAndNotificationsSettingsState2,
|
||||
formatMuteUntil: (Long) -> String,
|
||||
onEvent: (SoundsAndNotificationsEvent) -> Unit,
|
||||
onNavigationClick: () -> Unit,
|
||||
onMuteClick: () -> Unit
|
||||
) {
|
||||
val isMuted = state.muteUntil > 0
|
||||
var showUnmuteDialog by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffolds.Settings(
|
||||
title = stringResource(R.string.ConversationSettingsFragment__sounds_and_notifications),
|
||||
onNavigationClick = onNavigationClick,
|
||||
navigationIcon = SignalIcons.ArrowStart.imageVector,
|
||||
navigationContentDescription = stringResource(R.string.CallScreenTopBar__go_back)
|
||||
) { paddingValues ->
|
||||
LazyColumn(
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
) {
|
||||
// Custom notifications
|
||||
item {
|
||||
val summary = if (state.hasCustomNotificationSettings) {
|
||||
stringResource(R.string.preferences_on)
|
||||
} else {
|
||||
stringResource(R.string.preferences_off)
|
||||
}
|
||||
|
||||
Rows.TextRow(
|
||||
text = stringResource(R.string.SoundsAndNotificationsSettingsFragment__custom_notifications),
|
||||
label = summary,
|
||||
icon = painterResource(R.drawable.ic_speaker_24),
|
||||
onClick = { onEvent(SoundsAndNotificationsEvent.NavigateToCustomNotifications) }
|
||||
)
|
||||
}
|
||||
|
||||
// Mute
|
||||
item {
|
||||
val muteSummary = if (isMuted) {
|
||||
formatMuteUntil(state.muteUntil)
|
||||
} else {
|
||||
stringResource(R.string.SoundsAndNotificationsSettingsFragment__not_muted)
|
||||
}
|
||||
|
||||
val muteIcon = if (isMuted) {
|
||||
R.drawable.ic_bell_disabled_24
|
||||
} else {
|
||||
R.drawable.ic_bell_24
|
||||
}
|
||||
|
||||
Rows.TextRow(
|
||||
text = stringResource(R.string.SoundsAndNotificationsSettingsFragment__mute_notifications),
|
||||
label = muteSummary,
|
||||
icon = painterResource(muteIcon),
|
||||
onClick = {
|
||||
if (isMuted) showUnmuteDialog = true else onMuteClick()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Divider + When muted section
|
||||
item {
|
||||
Dividers.Default()
|
||||
}
|
||||
|
||||
item {
|
||||
Texts.SectionHeader(text = stringResource(R.string.SoundsAndNotificationsSettingsFragment__when_muted))
|
||||
}
|
||||
|
||||
// Calls
|
||||
item {
|
||||
NotificationSettingRow(
|
||||
title = stringResource(R.string.SoundsAndNotificationsSettingsFragment__calls),
|
||||
dialogTitle = stringResource(R.string.SoundsAndNotificationsSettingsFragment__calls),
|
||||
dialogMessage = stringResource(R.string.SoundsAndNotificationsSettingsFragment__calls_dialog_message),
|
||||
icon = painterResource(CoreUiR.drawable.symbol_phone_24),
|
||||
setting = state.callNotificationSetting,
|
||||
onSelected = { onEvent(SoundsAndNotificationsEvent.SetCallNotificationSetting(it)) }
|
||||
)
|
||||
}
|
||||
|
||||
// Mentions (only for groups)
|
||||
if (state.hasMentionsSupport) {
|
||||
item {
|
||||
NotificationSettingRow(
|
||||
title = stringResource(R.string.SoundsAndNotificationsSettingsFragment__mentions),
|
||||
dialogTitle = stringResource(R.string.SoundsAndNotificationsSettingsFragment__mentions),
|
||||
dialogMessage = stringResource(R.string.SoundsAndNotificationsSettingsFragment__mentions_dialog_message),
|
||||
icon = painterResource(R.drawable.ic_at_24),
|
||||
setting = state.mentionSetting,
|
||||
onSelected = { onEvent(SoundsAndNotificationsEvent.SetMentionSetting(it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Replies (only for groups)
|
||||
if (state.hasMentionsSupport) {
|
||||
item {
|
||||
NotificationSettingRow(
|
||||
title = stringResource(R.string.SoundsAndNotificationsSettingsFragment__replies_to_you),
|
||||
dialogTitle = stringResource(R.string.SoundsAndNotificationsSettingsFragment__replies_to_you),
|
||||
dialogMessage = stringResource(R.string.SoundsAndNotificationsSettingsFragment__replies_dialog_message),
|
||||
icon = painterResource(R.drawable.symbol_reply_24),
|
||||
setting = state.replyNotificationSetting,
|
||||
onSelected = { onEvent(SoundsAndNotificationsEvent.SetReplyNotificationSetting(it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showUnmuteDialog) {
|
||||
Dialogs.SimpleAlertDialog(
|
||||
title = Dialogs.NoTitle,
|
||||
body = formatMuteUntil(state.muteUntil),
|
||||
confirm = stringResource(R.string.ConversationSettingsFragment__unmute),
|
||||
dismiss = stringResource(android.R.string.cancel),
|
||||
onConfirm = { onEvent(SoundsAndNotificationsEvent.Unmute) },
|
||||
onDismiss = { showUnmuteDialog = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NotificationSettingRow(
|
||||
title: String,
|
||||
dialogTitle: String,
|
||||
dialogMessage: String,
|
||||
icon: Painter,
|
||||
setting: NotificationSetting,
|
||||
onSelected: (NotificationSetting) -> Unit
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val labels = arrayOf(
|
||||
stringResource(R.string.SoundsAndNotificationsSettingsFragment__always_notify),
|
||||
stringResource(R.string.SoundsAndNotificationsSettingsFragment__do_not_notify)
|
||||
)
|
||||
val selectedLabel = if (setting == NotificationSetting.ALWAYS_NOTIFY) labels[0] else labels[1]
|
||||
|
||||
Rows.TextRow(
|
||||
text = title,
|
||||
label = selectedLabel,
|
||||
icon = icon,
|
||||
onClick = { showDialog = true }
|
||||
)
|
||||
|
||||
if (showDialog) {
|
||||
NotificationSettingDialog(
|
||||
title = dialogTitle,
|
||||
message = dialogMessage,
|
||||
labels = labels,
|
||||
selectedIndex = if (setting == NotificationSetting.ALWAYS_NOTIFY) 0 else 1,
|
||||
onDismiss = { showDialog = false },
|
||||
onSelected = { index ->
|
||||
onSelected(if (index == 0) NotificationSetting.ALWAYS_NOTIFY else NotificationSetting.DO_NOT_NOTIFY)
|
||||
showDialog = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NotificationSettingDialog(
|
||||
title: String,
|
||||
message: String,
|
||||
labels: Array<String>,
|
||||
selectedIndex: Int,
|
||||
onDismiss: () -> Unit,
|
||||
onSelected: (Int) -> Unit
|
||||
) {
|
||||
Dialog(onDismissRequest = onDismiss) {
|
||||
Surface(
|
||||
shape = AlertDialogDefaults.shape,
|
||||
color = SignalTheme.colors.colorSurface2
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier
|
||||
.padding(top = 24.dp)
|
||||
.horizontalGutters()
|
||||
)
|
||||
|
||||
Text(
|
||||
text = message,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.horizontalGutters()
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.padding(top = 16.dp, bottom = 16.dp)) {
|
||||
labels.forEachIndexed { index, label ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = 48.dp)
|
||||
.clickable { onSelected(index) }
|
||||
.horizontalGutters()
|
||||
) {
|
||||
RadioButton(
|
||||
selected = index == selectedIndex,
|
||||
onClick = { onSelected(index) }
|
||||
)
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun SoundsAndNotificationsSettingsScreenMutedPreview() {
|
||||
Previews.Preview {
|
||||
SoundsAndNotificationsSettingsScreen(
|
||||
state = SoundsAndNotificationsSettingsState2(
|
||||
muteUntil = Long.MAX_VALUE,
|
||||
callNotificationSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
mentionSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
replyNotificationSetting = NotificationSetting.DO_NOT_NOTIFY,
|
||||
hasMentionsSupport = true,
|
||||
hasCustomNotificationSettings = false,
|
||||
channelConsistencyCheckComplete = true
|
||||
),
|
||||
formatMuteUntil = { "Always" },
|
||||
onEvent = {},
|
||||
onNavigationClick = {},
|
||||
onMuteClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun SoundsAndNotificationsSettingsScreenUnmutedPreview() {
|
||||
Previews.Preview {
|
||||
SoundsAndNotificationsSettingsScreen(
|
||||
state = SoundsAndNotificationsSettingsState2(
|
||||
muteUntil = 0L,
|
||||
callNotificationSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
mentionSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
replyNotificationSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
hasMentionsSupport = false,
|
||||
hasCustomNotificationSettings = true,
|
||||
channelConsistencyCheckComplete = true
|
||||
),
|
||||
formatMuteUntil = { "" },
|
||||
onEvent = {},
|
||||
onNavigationClick = {},
|
||||
onMuteClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
data class SoundsAndNotificationsSettingsState(
|
||||
val recipientId: RecipientId = Recipient.UNKNOWN.id,
|
||||
val muteUntil: Long = 0L,
|
||||
val mentionSetting: RecipientTable.MentionSetting = RecipientTable.MentionSetting.DO_NOT_NOTIFY,
|
||||
val mentionSetting: RecipientTable.NotificationSetting = RecipientTable.NotificationSetting.DO_NOT_NOTIFY,
|
||||
val hasCustomNotificationSettings: Boolean = false,
|
||||
val hasMentionsSupport: Boolean = false,
|
||||
val channelConsistencyCheckComplete: Boolean = false
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.conversation.sounds
|
||||
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.NotificationSetting
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
data class SoundsAndNotificationsSettingsState2(
|
||||
val recipientId: RecipientId = Recipient.UNKNOWN.id,
|
||||
val muteUntil: Long = 0L,
|
||||
val mentionSetting: NotificationSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
val callNotificationSetting: NotificationSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
val replyNotificationSetting: NotificationSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
val hasCustomNotificationSettings: Boolean = false,
|
||||
val hasMentionsSupport: Boolean = false,
|
||||
val channelConsistencyCheckComplete: Boolean = false
|
||||
) {
|
||||
val isMuted = muteUntil > 0
|
||||
}
|
||||
@@ -38,7 +38,7 @@ class SoundsAndNotificationsSettingsViewModel(
|
||||
repository.setMuteUntil(recipientId, 0L)
|
||||
}
|
||||
|
||||
fun setMentionSetting(mentionSetting: RecipientTable.MentionSetting) {
|
||||
fun setMentionSetting(mentionSetting: RecipientTable.NotificationSetting) {
|
||||
repository.setMentionSetting(recipientId, mentionSetting)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.conversation.sounds
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.NotificationSetting
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
class SoundsAndNotificationsSettingsViewModel2(
|
||||
private val recipientId: RecipientId
|
||||
) : ViewModel(), RecipientForeverObserver {
|
||||
|
||||
private val _state = MutableStateFlow(SoundsAndNotificationsSettingsState2())
|
||||
val state: StateFlow<SoundsAndNotificationsSettingsState2> = _state
|
||||
|
||||
private val liveRecipient = Recipient.live(recipientId)
|
||||
|
||||
init {
|
||||
liveRecipient.observeForever(this)
|
||||
onRecipientChanged(liveRecipient.get())
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
if (NotificationChannels.supported()) {
|
||||
NotificationChannels.getInstance().ensureCustomChannelConsistency()
|
||||
}
|
||||
_state.update { it.copy(channelConsistencyCheckComplete = true) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRecipientChanged(recipient: Recipient) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
recipientId = recipientId,
|
||||
muteUntil = if (recipient.isMuted) recipient.muteUntil else 0L,
|
||||
mentionSetting = recipient.mentionSetting,
|
||||
callNotificationSetting = recipient.callNotificationSetting,
|
||||
replyNotificationSetting = recipient.replyNotificationSetting,
|
||||
hasMentionsSupport = recipient.isPushV2Group,
|
||||
hasCustomNotificationSettings = recipient.notificationChannel != null || !NotificationChannels.supported()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
liveRecipient.removeForeverObserver(this)
|
||||
}
|
||||
|
||||
fun onEvent(event: SoundsAndNotificationsEvent) {
|
||||
when (event) {
|
||||
is SoundsAndNotificationsEvent.SetMuteUntil -> applySetMuteUntil(event.muteUntil)
|
||||
is SoundsAndNotificationsEvent.Unmute -> applySetMuteUntil(0L)
|
||||
is SoundsAndNotificationsEvent.SetMentionSetting -> applySetMentionSetting(event.setting)
|
||||
is SoundsAndNotificationsEvent.SetCallNotificationSetting -> applySetCallNotificationSetting(event.setting)
|
||||
is SoundsAndNotificationsEvent.SetReplyNotificationSetting -> applySetReplyNotificationSetting(event.setting)
|
||||
is SoundsAndNotificationsEvent.NavigateToCustomNotifications -> Unit // Navigation handled by UI
|
||||
}
|
||||
}
|
||||
|
||||
private fun applySetMuteUntil(muteUntil: Long) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
SignalDatabase.recipients.setMuted(recipientId, muteUntil)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applySetMentionSetting(setting: NotificationSetting) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
SignalDatabase.recipients.setMentionSetting(recipientId, setting)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applySetCallNotificationSetting(setting: NotificationSetting) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
SignalDatabase.recipients.setCallNotificationSetting(recipientId, setting)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applySetReplyNotificationSetting(setting: NotificationSetting) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
SignalDatabase.recipients.setReplyNotificationSetting(recipientId, setting)
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(private val recipientId: RecipientId) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return requireNotNull(modelClass.cast(SoundsAndNotificationsSettingsViewModel2(recipientId)))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user