mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-02 08:23:00 +01:00
Add improved notification settings when muted.
This commit is contained in:
@@ -62,7 +62,7 @@ class ChatArchiveExporter(private val cursor: Cursor, private val db: SignalData
|
||||
expireTimerVersion = cursor.requireInt(RecipientTable.MESSAGE_EXPIRATION_TIME_VERSION),
|
||||
muteUntilMs = cursor.requireLong(RecipientTable.MUTE_UNTIL).takeIf { it > 0 },
|
||||
markedUnread = ThreadTable.ReadStatus.deserialize(cursor.requireInt(ThreadTable.READ)) == ThreadTable.ReadStatus.FORCED_UNREAD,
|
||||
dontNotifyForMentionsIfMuted = RecipientTable.MentionSetting.DO_NOT_NOTIFY.id == cursor.requireInt(RecipientTable.MENTION_SETTING),
|
||||
dontNotifyForMentionsIfMuted = RecipientTable.NotificationSetting.DO_NOT_NOTIFY.id == cursor.requireInt(RecipientTable.MENTION_SETTING),
|
||||
style = ChatStyleConverter.constructRemoteChatStyle(
|
||||
db = db,
|
||||
chatColors = chatColors,
|
||||
|
||||
@@ -60,7 +60,7 @@ object ChatArchiveImporter {
|
||||
.update(
|
||||
RecipientTable.TABLE_NAME,
|
||||
contentValuesOf(
|
||||
RecipientTable.MENTION_SETTING to (if (chat.dontNotifyForMentionsIfMuted) RecipientTable.MentionSetting.DO_NOT_NOTIFY.id else RecipientTable.MentionSetting.ALWAYS_NOTIFY.id),
|
||||
RecipientTable.MENTION_SETTING to (if (chat.dontNotifyForMentionsIfMuted) RecipientTable.NotificationSetting.DO_NOT_NOTIFY.id else RecipientTable.NotificationSetting.ALWAYS_NOTIFY.id),
|
||||
RecipientTable.MUTE_UNTIL to (chat.muteUntilMs ?: 0),
|
||||
RecipientTable.MESSAGE_EXPIRATION_TIME to (chat.expirationTimerMs?.milliseconds?.inWholeSeconds ?: 0),
|
||||
RecipientTable.MESSAGE_EXPIRATION_TIME_VERSION to chat.expireTimerVersion,
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,6 +168,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
const val STORAGE_SERVICE_ID = "storage_service_id"
|
||||
const val STORAGE_SERVICE_PROTO = "storage_service_proto"
|
||||
const val MENTION_SETTING = "mention_setting"
|
||||
const val CALL_NOTIFICATION_SETTING = "call_notification_setting"
|
||||
const val REPLY_NOTIFICATION_SETTING = "reply_notification_setting"
|
||||
const val CAPABILITIES = "capabilities"
|
||||
const val LAST_SESSION_RESET = "last_session_reset"
|
||||
const val WALLPAPER = "wallpaper"
|
||||
@@ -241,7 +243,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
$SEALED_SENDER_MODE INTEGER DEFAULT 0,
|
||||
$STORAGE_SERVICE_ID TEXT UNIQUE DEFAULT NULL,
|
||||
$STORAGE_SERVICE_PROTO TEXT DEFAULT NULL,
|
||||
$MENTION_SETTING INTEGER DEFAULT ${MentionSetting.ALWAYS_NOTIFY.id},
|
||||
$MENTION_SETTING INTEGER DEFAULT ${NotificationSetting.ALWAYS_NOTIFY.id},
|
||||
$CAPABILITIES INTEGER DEFAULT 0,
|
||||
$LAST_SESSION_RESET BLOB DEFAULT NULL,
|
||||
$WALLPAPER BLOB DEFAULT NULL,
|
||||
@@ -264,7 +266,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
$NICKNAME_JOINED_NAME TEXT DEFAULT NULL,
|
||||
$NOTE TEXT DEFAULT NULL,
|
||||
$MESSAGE_EXPIRATION_TIME_VERSION INTEGER DEFAULT 1 NOT NULL,
|
||||
$KEY_TRANSPARENCY_DATA BLOB DEFAULT NULL
|
||||
$KEY_TRANSPARENCY_DATA BLOB DEFAULT NULL,
|
||||
$CALL_NOTIFICATION_SETTING INTEGER DEFAULT ${NotificationSetting.ALWAYS_NOTIFY.id},
|
||||
$REPLY_NOTIFICATION_SETTING INTEGER DEFAULT ${NotificationSetting.ALWAYS_NOTIFY.id}
|
||||
)
|
||||
"""
|
||||
|
||||
@@ -313,6 +317,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
SEALED_SENDER_MODE,
|
||||
STORAGE_SERVICE_ID,
|
||||
MENTION_SETTING,
|
||||
CALL_NOTIFICATION_SETTING,
|
||||
REPLY_NOTIFICATION_SETTING,
|
||||
CAPABILITIES,
|
||||
WALLPAPER,
|
||||
WALLPAPER_URI,
|
||||
@@ -1645,7 +1651,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
}
|
||||
}
|
||||
|
||||
fun setMentionSetting(id: RecipientId, mentionSetting: MentionSetting) {
|
||||
fun setMentionSetting(id: RecipientId, mentionSetting: NotificationSetting) {
|
||||
val values = ContentValues().apply {
|
||||
put(MENTION_SETTING, mentionSetting.id)
|
||||
}
|
||||
@@ -1656,6 +1662,30 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
}
|
||||
}
|
||||
|
||||
fun setCallNotificationSetting(id: RecipientId, setting: NotificationSetting) {
|
||||
val values = ContentValues().apply {
|
||||
put(CALL_NOTIFICATION_SETTING, setting.id)
|
||||
}
|
||||
if (update(id, values)) {
|
||||
// TODO rotate storageId once this is actually synced in storage service
|
||||
// rotateStorageId(id)
|
||||
AppDependencies.databaseObserver.notifyRecipientChanged(id)
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
}
|
||||
}
|
||||
|
||||
fun setReplyNotificationSetting(id: RecipientId, setting: NotificationSetting) {
|
||||
val values = ContentValues().apply {
|
||||
put(REPLY_NOTIFICATION_SETTING, setting.id)
|
||||
}
|
||||
if (update(id, values)) {
|
||||
// TODO rotate storageId once this is actually synced in storage service
|
||||
// rotateStorageId(id)
|
||||
AppDependencies.databaseObserver.notifyRecipientChanged(id)
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the profile key.
|
||||
*
|
||||
@@ -4147,7 +4177,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
SYSTEM_CONTACT_URI to secondaryRecord.systemContactUri,
|
||||
PROFILE_SHARING to (primaryRecord.profileSharing || secondaryRecord.profileSharing),
|
||||
CAPABILITIES to max(primaryRecord.capabilities.rawBits, secondaryRecord.capabilities.rawBits),
|
||||
MENTION_SETTING to if (primaryRecord.mentionSetting != MentionSetting.ALWAYS_NOTIFY) primaryRecord.mentionSetting.id else secondaryRecord.mentionSetting.id,
|
||||
MENTION_SETTING to if (primaryRecord.mentionSetting != NotificationSetting.ALWAYS_NOTIFY) primaryRecord.mentionSetting.id else secondaryRecord.mentionSetting.id,
|
||||
PNI_SIGNATURE_VERIFIED to pniVerified.toInt()
|
||||
)
|
||||
|
||||
@@ -4280,7 +4310,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
put(BLOCKED, if (groupV2.proto.blocked) "1" else "0")
|
||||
put(MUTE_UNTIL, groupV2.proto.mutedUntilTimestamp)
|
||||
put(STORAGE_SERVICE_ID, Base64.encodeWithPadding(groupV2.id.raw))
|
||||
put(MENTION_SETTING, if (groupV2.proto.dontNotifyForMentionsIfMuted) MentionSetting.DO_NOT_NOTIFY.id else MentionSetting.ALWAYS_NOTIFY.id)
|
||||
put(MENTION_SETTING, if (groupV2.proto.dontNotifyForMentionsIfMuted) NotificationSetting.DO_NOT_NOTIFY.id else NotificationSetting.ALWAYS_NOTIFY.id)
|
||||
|
||||
if (groupV2.proto.hasUnknownFields()) {
|
||||
put(STORAGE_SERVICE_PROTO, Base64.encodeWithPadding(groupV2.serializedUnknowns!!))
|
||||
@@ -4861,12 +4891,12 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
}
|
||||
}
|
||||
|
||||
enum class MentionSetting(val id: Int) {
|
||||
enum class NotificationSetting(val id: Int) {
|
||||
ALWAYS_NOTIFY(0),
|
||||
DO_NOT_NOTIFY(1);
|
||||
|
||||
companion object {
|
||||
fun fromId(id: Int): MentionSetting {
|
||||
fun fromId(id: Int): NotificationSetting {
|
||||
return entries[id]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,9 @@ object RecipientTableCursorUtil {
|
||||
sealedSenderAccessMode = RecipientTable.SealedSenderAccessMode.fromMode(cursor.requireInt(RecipientTable.SEALED_SENDER_MODE)),
|
||||
capabilities = readCapabilities(cursor),
|
||||
storageId = Base64.decodeNullableOrThrow(cursor.requireString(RecipientTable.STORAGE_SERVICE_ID)),
|
||||
mentionSetting = RecipientTable.MentionSetting.fromId(cursor.requireInt(RecipientTable.MENTION_SETTING)),
|
||||
mentionSetting = RecipientTable.NotificationSetting.fromId(cursor.requireInt(RecipientTable.MENTION_SETTING)),
|
||||
callNotificationSetting = RecipientTable.NotificationSetting.fromId(cursor.requireInt(RecipientTable.CALL_NOTIFICATION_SETTING)),
|
||||
replyNotificationSetting = RecipientTable.NotificationSetting.fromId(cursor.requireInt(RecipientTable.REPLY_NOTIFICATION_SETTING)),
|
||||
wallpaper = chatWallpaper,
|
||||
chatColors = chatColors,
|
||||
avatarColor = avatarColor,
|
||||
|
||||
@@ -157,6 +157,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V300_AddKeyTranspar
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V301_RemoveCallLinkEpoch
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V302_AddDeletedByColumn
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V303_CaseInsensitiveUsernames
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V304_CallAndReplyNotificationSettings
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase as SignalSqliteDatabase
|
||||
|
||||
/**
|
||||
@@ -320,10 +321,11 @@ object SignalDatabaseMigrations {
|
||||
300 to V300_AddKeyTransparencyColumn,
|
||||
301 to V301_RemoveCallLinkEpoch,
|
||||
302 to V302_AddDeletedByColumn,
|
||||
303 to V303_CaseInsensitiveUsernames
|
||||
303 to V303_CaseInsensitiveUsernames,
|
||||
304 to V304_CallAndReplyNotificationSettings
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 303
|
||||
const val DATABASE_VERSION = 304
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SignalSqliteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase
|
||||
|
||||
/**
|
||||
* Adds per-conversation notification settings for calls and replies when muted.
|
||||
*/
|
||||
@Suppress("ClassName")
|
||||
object V304_CallAndReplyNotificationSettings : SignalDatabaseMigration {
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL("ALTER TABLE recipient ADD COLUMN call_notification_setting INTEGER DEFAULT 0")
|
||||
db.execSQL("ALTER TABLE recipient ADD COLUMN reply_notification_setting INTEGER DEFAULT 0")
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
||||
import org.thoughtcrime.securesms.database.IdentityTable.VerifiedStatus
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.NotificationSetting
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.PhoneNumberSharingState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.SealedSenderAccessMode
|
||||
@@ -64,7 +64,9 @@ data class RecipientRecord(
|
||||
val sealedSenderAccessMode: SealedSenderAccessMode,
|
||||
val capabilities: Capabilities,
|
||||
val storageId: ByteArray?,
|
||||
val mentionSetting: MentionSetting,
|
||||
val mentionSetting: NotificationSetting,
|
||||
val callNotificationSetting: NotificationSetting,
|
||||
val replyNotificationSetting: NotificationSetting,
|
||||
val wallpaper: ChatWallpaper?,
|
||||
val chatColors: ChatColors?,
|
||||
val avatarColor: AvatarColor,
|
||||
|
||||
@@ -14,11 +14,11 @@ import androidx.core.util.Consumer;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.NotificationSetting;
|
||||
|
||||
public final class GroupMentionSettingDialog {
|
||||
|
||||
public static void show(@NonNull Context context, @NonNull MentionSetting mentionSetting, @Nullable Consumer<MentionSetting> callback) {
|
||||
public static void show(@NonNull Context context, @NonNull NotificationSetting mentionSetting, @Nullable Consumer<NotificationSetting> callback) {
|
||||
SelectionCallback selectionCallback = new SelectionCallback(mentionSetting, callback);
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
@@ -30,7 +30,7 @@ public final class GroupMentionSettingDialog {
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
private static View getView(@NonNull Context context, @NonNull MentionSetting mentionSetting, @NonNull SelectionCallback selectionCallback) {
|
||||
private static View getView(@NonNull Context context, @NonNull NotificationSetting mentionSetting, @NonNull SelectionCallback selectionCallback) {
|
||||
View root = LayoutInflater.from(context).inflate(R.layout.group_mention_setting_dialog, null, false);
|
||||
CheckedTextView alwaysNotify = root.findViewById(R.id.group_mention_setting_always_notify);
|
||||
CheckedTextView dontNotify = root.findViewById(R.id.group_mention_setting_dont_notify);
|
||||
@@ -40,9 +40,9 @@ public final class GroupMentionSettingDialog {
|
||||
dontNotify.setChecked(dontNotify == v);
|
||||
|
||||
if (alwaysNotify.isChecked()) {
|
||||
selectionCallback.selection = MentionSetting.ALWAYS_NOTIFY;
|
||||
selectionCallback.selection = NotificationSetting.ALWAYS_NOTIFY;
|
||||
} else if (dontNotify.isChecked()) {
|
||||
selectionCallback.selection = MentionSetting.DO_NOT_NOTIFY;
|
||||
selectionCallback.selection = NotificationSetting.DO_NOT_NOTIFY;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -63,11 +63,11 @@ public final class GroupMentionSettingDialog {
|
||||
|
||||
private static class SelectionCallback implements DialogInterface.OnClickListener {
|
||||
|
||||
@NonNull private final MentionSetting previousMentionSetting;
|
||||
@NonNull private MentionSetting selection;
|
||||
@Nullable private final Consumer<MentionSetting> callback;
|
||||
@NonNull private final NotificationSetting previousMentionSetting;
|
||||
@NonNull private NotificationSetting selection;
|
||||
@Nullable private final Consumer<NotificationSetting> callback;
|
||||
|
||||
public SelectionCallback(@NonNull MentionSetting previousMentionSetting, @Nullable Consumer<MentionSetting> callback) {
|
||||
public SelectionCallback(@NonNull NotificationSetting previousMentionSetting, @Nullable Consumer<NotificationSetting> callback) {
|
||||
this.previousMentionSetting = previousMentionSetting;
|
||||
this.selection = previousMentionSetting;
|
||||
this.callback = callback;
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.core.util.CursorUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.signal.core.ui.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -27,9 +28,31 @@ public final class DoNotDisturbUtil {
|
||||
private DoNotDisturbUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the user should be disturbed with a call from the given recipient,
|
||||
* taking into account the recipient's mute and call notification settings as well as
|
||||
* the system Do Not Disturb state.
|
||||
*
|
||||
* For group recipients, only the system interruption filter is checked (no contact priority).
|
||||
* For 1:1 recipients, the full DND policy including contact priority is evaluated.
|
||||
*/
|
||||
@WorkerThread
|
||||
@SuppressLint("SwitchIntDef")
|
||||
public static boolean shouldDisturbUserWithCall(@NonNull Context context) {
|
||||
public static boolean shouldDisturbUserWithCall(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
if (recipient.isMuted() && recipient.getCallNotificationSetting() == RecipientTable.NotificationSetting.DO_NOT_NOTIFY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (recipient.isGroup()) {
|
||||
return checkSystemDnd(context);
|
||||
} else {
|
||||
return checkSystemDndWithContactPriority(context, recipient);
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@SuppressLint("SwitchIntDef")
|
||||
private static boolean checkSystemDnd(@NonNull Context context) {
|
||||
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
|
||||
|
||||
switch (notificationManager.getCurrentInterruptionFilter()) {
|
||||
@@ -43,7 +66,7 @@ public final class DoNotDisturbUtil {
|
||||
|
||||
@WorkerThread
|
||||
@SuppressLint("SwitchIntDef")
|
||||
public static boolean shouldDisturbUserWithCall(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
private static boolean checkSystemDndWithContactPriority(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
|
||||
|
||||
switch (notificationManager.getCurrentInterruptionFilter()) {
|
||||
|
||||
@@ -167,13 +167,13 @@ object NotificationStateProvider {
|
||||
isUnreadMessage &&
|
||||
!messageRecord.isOutgoing &&
|
||||
isGroupStoryReply &&
|
||||
(isParentStorySentBySelf || messageRecord.hasSelfMentionOrQuoteOfSelf() || (hasSelfRepliedToStory && !messageRecord.isStoryReaction()))
|
||||
(isParentStorySentBySelf || messageRecord.hasGroupQuoteOrSelfMention() || (hasSelfRepliedToStory && !messageRecord.isStoryReaction()))
|
||||
|
||||
fun includeMessage(notificationProfile: NotificationProfile?): MessageInclusion {
|
||||
return if (isUnreadIncoming || stickyThread || isNotifiableGroupStoryMessage || isIncomingMissedCall) {
|
||||
if (threadRecipient.isMuted && (threadRecipient.isDoNotNotifyMentions || !messageRecord.shouldBreakThroughMute(threadRecipient))) {
|
||||
if (threadRecipient.isMuted && !breaksThroughMute()) {
|
||||
MessageInclusion.MUTE_FILTERED
|
||||
} else if (notificationProfile != null && !notificationProfile.isRecipientAllowed(threadRecipient.id) && !(notificationProfile.allowAllMentions && messageRecord.shouldBreakThroughMute(threadRecipient))) {
|
||||
} else if (notificationProfile != null && !notificationProfile.isRecipientAllowed(threadRecipient.id) && !(notificationProfile.allowAllMentions && messageRecord.hasGroupQuoteOrSelfMention())) {
|
||||
MessageInclusion.PROFILE_FILTERED
|
||||
} else {
|
||||
MessageInclusion.INCLUDE
|
||||
@@ -183,6 +183,19 @@ object NotificationStateProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private fun breaksThroughMute(): Boolean {
|
||||
return when {
|
||||
isIncomingMissedCall -> threadRecipient.callNotificationSetting == RecipientTable.NotificationSetting.ALWAYS_NOTIFY
|
||||
messageRecord.hasSelfMention() -> threadRecipient.mentionSetting == RecipientTable.NotificationSetting.ALWAYS_NOTIFY
|
||||
messageRecord.isQuoteOfSelf() -> threadRecipient.replyNotificationSetting == RecipientTable.NotificationSetting.ALWAYS_NOTIFY
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessageRecord.isQuoteOfSelf(): Boolean {
|
||||
return this is MmsMessageRecord && quote?.author == Recipient.self().id
|
||||
}
|
||||
|
||||
fun includeReaction(reaction: ReactionRecord, notificationProfile: NotificationProfile?): MessageInclusion {
|
||||
return if (threadRecipient.isMuted) {
|
||||
MessageInclusion.MUTE_FILTERED
|
||||
@@ -207,19 +220,12 @@ object NotificationStateProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private val Recipient.isDoNotNotifyMentions: Boolean
|
||||
get() = mentionSetting == RecipientTable.MentionSetting.DO_NOT_NOTIFY
|
||||
|
||||
private fun MessageRecord.shouldBreakThroughMute(threadRecipient: Recipient): Boolean {
|
||||
private fun MessageRecord.hasGroupQuoteOrSelfMention(): Boolean {
|
||||
if (!threadRecipient.isGroup) {
|
||||
return false
|
||||
}
|
||||
return hasSelfMention() || (this is MmsMessageRecord && quote?.author == Recipient.self().id)
|
||||
}
|
||||
|
||||
private fun MessageRecord.hasSelfMentionOrQuoteOfSelf(): Boolean {
|
||||
return hasSelfMention() || (this is MmsMessageRecord && quote?.author == Recipient.self().id)
|
||||
}
|
||||
}
|
||||
|
||||
private enum class MessageInclusion {
|
||||
|
||||
@@ -28,8 +28,8 @@ import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors.Id.Auto
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.MissingRecipientException
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.NotificationSetting
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.PhoneNumberSharingState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.SealedSenderAccessMode
|
||||
@@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.phonenumbers.NumberUtil
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName
|
||||
import org.thoughtcrime.securesms.recipients.Recipient.Companion.external
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.SignalE164Util
|
||||
import org.thoughtcrime.securesms.util.UsernameUtil.isValidUsernameForSearch
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
|
||||
@@ -103,7 +104,9 @@ class Recipient(
|
||||
private val sealedSenderAccessModeValue: SealedSenderAccessMode = SealedSenderAccessMode.UNKNOWN,
|
||||
private val capabilities: RecipientRecord.Capabilities = RecipientRecord.Capabilities.UNKNOWN,
|
||||
val storageId: ByteArray? = null,
|
||||
val mentionSetting: MentionSetting = MentionSetting.ALWAYS_NOTIFY,
|
||||
val mentionSetting: NotificationSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
private val callNotificationSettingValue: NotificationSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
private val replyNotificationSettingValue: NotificationSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
private val wallpaperValue: ChatWallpaper? = null,
|
||||
private val chatColorsValue: ChatColors? = null,
|
||||
val avatarColor: AvatarColor = AvatarColor.UNKNOWN,
|
||||
@@ -329,6 +332,14 @@ class Recipient(
|
||||
/** The notification channel, if both set and supported by the system. Otherwise null. */
|
||||
val notificationChannel: String? = if (!NotificationChannels.supported()) null else notificationChannelValue
|
||||
|
||||
/** Whether calls should break through mute for this recipient. */
|
||||
val callNotificationSetting: NotificationSetting
|
||||
get() = if (RemoteConfig.internalUser) callNotificationSettingValue else NotificationSetting.ALWAYS_NOTIFY
|
||||
|
||||
/** Whether replies should break through mute for this recipient. Only applicable to groups. */
|
||||
val replyNotificationSetting: NotificationSetting
|
||||
get() = if (groupIdValue == null) NotificationSetting.DO_NOT_NOTIFY else if (RemoteConfig.internalUser) replyNotificationSettingValue else mentionSetting
|
||||
|
||||
/** The state around whether we can send sealed sender to this user. */
|
||||
val sealedSenderAccessMode: SealedSenderAccessMode = if (pni.isPresent && pni == serviceId) {
|
||||
SealedSenderAccessMode.DISABLED
|
||||
@@ -810,6 +821,8 @@ class Recipient(
|
||||
notificationChannelValue == other.notificationChannelValue &&
|
||||
sealedSenderAccessModeValue == other.sealedSenderAccessModeValue &&
|
||||
mentionSetting == other.mentionSetting &&
|
||||
callNotificationSettingValue == other.callNotificationSettingValue &&
|
||||
replyNotificationSettingValue == other.replyNotificationSettingValue &&
|
||||
wallpaperValue == other.wallpaperValue &&
|
||||
chatColorsValue == other.chatColorsValue &&
|
||||
avatarColor == other.avatarColor &&
|
||||
|
||||
@@ -185,6 +185,8 @@ object RecipientCreator {
|
||||
capabilities = record.capabilities,
|
||||
storageId = record.storageId,
|
||||
mentionSetting = record.mentionSetting,
|
||||
callNotificationSettingValue = record.callNotificationSetting,
|
||||
replyNotificationSettingValue = record.replyNotificationSetting,
|
||||
wallpaperValue = record.wallpaper?.validate(),
|
||||
chatColorsValue = record.chatColors,
|
||||
avatarColor = avatarColor ?: record.avatarColor,
|
||||
|
||||
@@ -13,6 +13,8 @@ import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.CallParticipantId;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.notifications.DoNotDisturbUtil;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.signal.core.util.Util;
|
||||
@@ -116,8 +118,11 @@ public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor {
|
||||
|
||||
|
||||
boolean isRemoteVideoOffer = currentState.getCallSetupState(remotePeer).isRemoteVideoOffer();
|
||||
Recipient recipient = remotePeer.getRecipient();
|
||||
|
||||
webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_CONNECTING, remotePeer, isRemoteVideoOffer);
|
||||
if (DoNotDisturbUtil.shouldDisturbUserWithCall(context.getApplicationContext(), recipient)) {
|
||||
webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_CONNECTING, remotePeer, isRemoteVideoOffer);
|
||||
}
|
||||
webRtcInteractor.retrieveTurnServers(remotePeer);
|
||||
webRtcInteractor.initializeAudioForCall();
|
||||
|
||||
|
||||
@@ -240,18 +240,23 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor {
|
||||
CallTable.Direction.INCOMING,
|
||||
CallTable.Event.ONGOING);
|
||||
|
||||
if (!shouldDisturbUserWithCall) {
|
||||
Log.i(TAG, "Silently ignoring call due to mute settings.");
|
||||
return currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_INCOMING)
|
||||
.build();
|
||||
}
|
||||
|
||||
if (shouldDisturbUserWithCall) {
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.INTERACTIVE);
|
||||
boolean started = webRtcInteractor.startWebRtcCallActivityIfPossible();
|
||||
if (!started) {
|
||||
Log.i(TAG, "Unable to start call activity due to OS version or not being in the foreground");
|
||||
AppForegroundObserver.addListener(webRtcInteractor.getForegroundListener());
|
||||
}
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.INTERACTIVE);
|
||||
boolean started = webRtcInteractor.startWebRtcCallActivityIfPossible();
|
||||
if (!started) {
|
||||
Log.i(TAG, "Unable to start call activity due to OS version or not being in the foreground");
|
||||
AppForegroundObserver.addListener(webRtcInteractor.getForegroundListener());
|
||||
}
|
||||
|
||||
boolean isCallNotificationsEnabled = SignalStore.settings().isCallNotificationsEnabled() && NotificationChannels.getInstance().areNotificationsEnabled();
|
||||
if (shouldDisturbUserWithCall && isCallNotificationsEnabled) {
|
||||
if (isCallNotificationsEnabled) {
|
||||
Uri ringtone = recipient.resolve().getCallRingtone();
|
||||
RecipientTable.VibrateState vibrateState = recipient.resolve().getCallVibrate();
|
||||
|
||||
|
||||
@@ -121,33 +121,36 @@ public final class IncomingGroupCallActionProcessor extends DeviceAwareActionPro
|
||||
|
||||
currentState = WebRtcVideoUtil.initializeVideo(context, webRtcInteractor.getCameraEventListener(), currentState, RemotePeer.GROUP_CALL_ID.longValue());
|
||||
|
||||
webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_RINGING, remotePeerGroup, true);
|
||||
webRtcInteractor.initializeAudioForCall();
|
||||
|
||||
boolean shouldDisturbUserWithCall = DoNotDisturbUtil.shouldDisturbUserWithCall(context.getApplicationContext());
|
||||
if (shouldDisturbUserWithCall) {
|
||||
boolean shouldDisturbUserWithCall = DoNotDisturbUtil.shouldDisturbUserWithCall(context.getApplicationContext(), recipient.resolve());
|
||||
|
||||
if (!shouldDisturbUserWithCall) {
|
||||
Log.i(TAG, "Silently ignoring group ring due to mute settings.");
|
||||
} else {
|
||||
webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_RINGING, remotePeerGroup, true);
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.INTERACTIVE);
|
||||
boolean started = webRtcInteractor.startWebRtcCallActivityIfPossible();
|
||||
if (!started) {
|
||||
Log.i(TAG, "Unable to start call activity due to OS version or not being in the foreground");
|
||||
AppForegroundObserver.addListener(webRtcInteractor.getForegroundListener());
|
||||
}
|
||||
}
|
||||
|
||||
boolean isCallNotificationsEnabled = SignalStore.settings().isCallNotificationsEnabled() && NotificationChannels.getInstance().areNotificationsEnabled();
|
||||
if (shouldDisturbUserWithCall && isCallNotificationsEnabled) {
|
||||
Uri ringtone = recipient.resolve().getCallRingtone();
|
||||
RecipientTable.VibrateState vibrateState = recipient.resolve().getCallVibrate();
|
||||
boolean isCallNotificationsEnabled = SignalStore.settings().isCallNotificationsEnabled() && NotificationChannels.getInstance().areNotificationsEnabled();
|
||||
if (isCallNotificationsEnabled) {
|
||||
Uri ringtone = recipient.resolve().getCallRingtone();
|
||||
RecipientTable.VibrateState vibrateState = recipient.resolve().getCallVibrate();
|
||||
|
||||
if (ringtone == null) {
|
||||
ringtone = SignalStore.settings().getCallRingtone();
|
||||
if (ringtone == null) {
|
||||
ringtone = SignalStore.settings().getCallRingtone();
|
||||
}
|
||||
|
||||
webRtcInteractor.startIncomingRinger(ringtone, vibrateState == RecipientTable.VibrateState.ENABLED || (vibrateState == RecipientTable.VibrateState.DEFAULT && SignalStore.settings().isCallVibrateEnabled()));
|
||||
}
|
||||
|
||||
webRtcInteractor.startIncomingRinger(ringtone, vibrateState == RecipientTable.VibrateState.ENABLED || (vibrateState == RecipientTable.VibrateState.DEFAULT && SignalStore.settings().isCallVibrateEnabled()));
|
||||
webRtcInteractor.registerPowerButtonReceiver();
|
||||
}
|
||||
|
||||
webRtcInteractor.registerPowerButtonReceiver();
|
||||
|
||||
return currentState.builder()
|
||||
.changeCallSetupState(RemotePeer.GROUP_CALL_ID)
|
||||
.isRemoteVideoOffer(true)
|
||||
|
||||
@@ -249,7 +249,7 @@ object StorageSyncModels {
|
||||
archived = recipient.syncExtras.isArchived
|
||||
markedUnread = recipient.syncExtras.isForcedUnread
|
||||
mutedUntilTimestamp = recipient.muteUntil
|
||||
dontNotifyForMentionsIfMuted = recipient.mentionSetting == RecipientTable.MentionSetting.DO_NOT_NOTIFY
|
||||
dontNotifyForMentionsIfMuted = recipient.mentionSetting == RecipientTable.NotificationSetting.DO_NOT_NOTIFY
|
||||
hideStory = recipient.extras != null && recipient.extras.hideStory()
|
||||
avatarColor = localToRemoteAvatarColor(recipient.avatarColor)
|
||||
storySendMode = when (groups.getShowAsStoryState(groupId)) {
|
||||
|
||||
@@ -66,6 +66,20 @@
|
||||
|
||||
</action>
|
||||
|
||||
<action
|
||||
android:id="@+id/action_conversationSettingsFragment_to_soundsAndNotificationsSettingsFragment2"
|
||||
app:destination="@id/soundsAndNotificationsSettingsFragment2"
|
||||
app:enterAnim="@anim/fragment_open_enter"
|
||||
app:exitAnim="@anim/fragment_open_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit">
|
||||
|
||||
<argument
|
||||
android:name="recipient_id"
|
||||
app:argType="org.thoughtcrime.securesms.recipients.RecipientId" />
|
||||
|
||||
</action>
|
||||
|
||||
<action
|
||||
android:id="@+id/action_conversationSettingsFragment_to_permissionsSettingsFragment"
|
||||
app:destination="@id/permissionsSettingsFragment"
|
||||
@@ -120,7 +134,8 @@
|
||||
|
||||
<fragment
|
||||
android:id="@+id/soundsAndNotificationsSettingsFragment"
|
||||
android:name="org.thoughtcrime.securesms.components.settings.conversation.sounds.SoundsAndNotificationsSettingsFragment">
|
||||
android:name="org.thoughtcrime.securesms.components.settings.conversation.sounds.SoundsAndNotificationsSettingsFragment"
|
||||
tools:layout="@layout/dsl_settings_fragment">
|
||||
|
||||
<argument
|
||||
android:name="recipient_id"
|
||||
@@ -136,6 +151,24 @@
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/soundsAndNotificationsSettingsFragment2"
|
||||
android:name="org.thoughtcrime.securesms.components.settings.conversation.sounds.SoundsAndNotificationsSettingsFragment2">
|
||||
|
||||
<argument
|
||||
android:name="recipient_id"
|
||||
app:argType="org.thoughtcrime.securesms.recipients.RecipientId" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_soundsAndNotificationsSettingsFragment2_to_customNotificationsSettingsFragment"
|
||||
app:destination="@id/customNotificationsSettingsFragment"
|
||||
app:enterAnim="@anim/fragment_open_enter"
|
||||
app:exitAnim="@anim/fragment_open_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/internalDetailsSettingsFragment"
|
||||
android:name="org.thoughtcrime.securesms.components.settings.conversation.InternalConversationSettingsFragment">
|
||||
|
||||
@@ -392,6 +392,16 @@
|
||||
<item>@string/SoundsAndNotificationsSettingsFragment__do_not_notify</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="SoundsAndNotificationsSettingsFragment__call_labels">
|
||||
<item>@string/SoundsAndNotificationsSettingsFragment__always_notify</item>
|
||||
<item>@string/SoundsAndNotificationsSettingsFragment__do_not_notify</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="SoundsAndNotificationsSettingsFragment__reply_labels">
|
||||
<item>@string/SoundsAndNotificationsSettingsFragment__always_notify</item>
|
||||
<item>@string/SoundsAndNotificationsSettingsFragment__do_not_notify</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="PlaybackSpeedToggleTextView__speeds">
|
||||
<item>100</item>
|
||||
<item>150</item>
|
||||
|
||||
@@ -6006,13 +6006,30 @@
|
||||
<string name="PermissionsSettingsFragment__who_can_send_messages">Who can send messages and start calls?</string>
|
||||
|
||||
<!-- SoundsAndNotificationsSettingsFragment -->
|
||||
<!-- Label for the setting to mute notifications for a conversation -->
|
||||
<string name="SoundsAndNotificationsSettingsFragment__mute_notifications">Mute notifications</string>
|
||||
<!-- Summary text shown when a conversation is not muted -->
|
||||
<string name="SoundsAndNotificationsSettingsFragment__not_muted">Not muted</string>
|
||||
<!-- Label for the mentions notification setting in conversation sounds and notifications -->
|
||||
<string name="SoundsAndNotificationsSettingsFragment__mentions">Mentions</string>
|
||||
<!-- Dialog option to always send notifications for the selected category even when the conversation is muted -->
|
||||
<string name="SoundsAndNotificationsSettingsFragment__always_notify">Always notify</string>
|
||||
<!-- Dialog option to not send notifications if mentioned when the conversation is muted -->
|
||||
<!-- Dialog option to not send notifications for the selected category when the conversation is muted -->
|
||||
<string name="SoundsAndNotificationsSettingsFragment__do_not_notify">Do not notify</string>
|
||||
<!-- Label for the setting to configure custom notification sounds and vibration for a conversation -->
|
||||
<string name="SoundsAndNotificationsSettingsFragment__custom_notifications">Custom notifications</string>
|
||||
<!-- Section header for settings that control which notifications still come through when a conversation is muted -->
|
||||
<string name="SoundsAndNotificationsSettingsFragment__when_muted">When muted</string>
|
||||
<!-- Label for the calls notification setting in conversation sounds and notifications -->
|
||||
<string name="SoundsAndNotificationsSettingsFragment__calls">Calls</string>
|
||||
<!-- Explanatory text shown in the calls notification setting dialog describing what the setting controls -->
|
||||
<string name="SoundsAndNotificationsSettingsFragment__calls_dialog_message">Ring and receive notifications when a call is started in muted chats.</string>
|
||||
<!-- Explanatory text shown in the mentions notification setting dialog describing what the setting controls -->
|
||||
<string name="SoundsAndNotificationsSettingsFragment__mentions_dialog_message">Receive notifications when you are mentioned in muted chats.</string>
|
||||
<!-- Label for the replies notification setting in conversation sounds and notifications -->
|
||||
<string name="SoundsAndNotificationsSettingsFragment__replies_to_you">Replies to you</string>
|
||||
<!-- Explanatory text shown in the replies notification setting dialog describing what the setting controls -->
|
||||
<string name="SoundsAndNotificationsSettingsFragment__replies_dialog_message">Receive notifications when someone replies to your messages in muted chats.</string>
|
||||
|
||||
<!-- StickerKeyboard -->
|
||||
<string name="StickerKeyboard__recently_used">Recently used</string>
|
||||
|
||||
@@ -61,7 +61,7 @@ object RecipientDatabaseTestUtils {
|
||||
sealedSenderAccessMode: RecipientTable.SealedSenderAccessMode = RecipientTable.SealedSenderAccessMode.UNKNOWN,
|
||||
capabilities: Long = 0L,
|
||||
storageId: ByteArray? = null,
|
||||
mentionSetting: RecipientTable.MentionSetting = RecipientTable.MentionSetting.ALWAYS_NOTIFY,
|
||||
mentionSetting: RecipientTable.NotificationSetting = RecipientTable.NotificationSetting.ALWAYS_NOTIFY,
|
||||
wallpaper: ChatWallpaper? = null,
|
||||
chatColors: ChatColors? = null,
|
||||
avatarColor: AvatarColor = AvatarColor.A100,
|
||||
@@ -128,6 +128,8 @@ object RecipientDatabaseTestUtils {
|
||||
),
|
||||
storageId = storageId,
|
||||
mentionSetting = mentionSetting,
|
||||
callNotificationSetting = RecipientTable.NotificationSetting.ALWAYS_NOTIFY,
|
||||
replyNotificationSetting = RecipientTable.NotificationSetting.ALWAYS_NOTIFY,
|
||||
wallpaper = wallpaper,
|
||||
chatColors = chatColors,
|
||||
avatarColor = avatarColor,
|
||||
|
||||
Reference in New Issue
Block a user