mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-09-13 12:07:57 +01:00
Support reaction and while muted notifications.
This commit is contained in:
committed by
Cody Henthorne
parent
e1df942c3c
commit
93d52f7831
@@ -11,6 +11,7 @@ import com.bumptech.glide.Glide
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.database.CallTable
|
||||
import org.thoughtcrime.securesms.database.MessageTypes
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.databinding.CallLogAdapterItemBinding
|
||||
import org.thoughtcrime.securesms.databinding.CallLogCreateCallLinkItemBinding
|
||||
import org.thoughtcrime.securesms.databinding.ConversationListItemClearFilterBinding
|
||||
@@ -335,7 +336,7 @@ class CallLogAdapter(
|
||||
binding.callRecipientAvatar.setAvatar(Glide.with(binding.callRecipientAvatar), recipient, false)
|
||||
binding.callRecipientAvatar.setOnClickListener { onCallClicked(call) }
|
||||
binding.callRecipientBadge.setBadgeFromRecipient(recipient)
|
||||
binding.callRecipientName.text = if (searchQuery != null) {
|
||||
binding.callRecipientName.text = if (!searchQuery.isNullOrEmpty()) {
|
||||
SearchUtil.getHighlightedSpan(
|
||||
Locale.getDefault(),
|
||||
{ arrayOf(TextAppearanceSpan(context, CoreUiR.style.Signal_Text_TitleSmall)) },
|
||||
@@ -346,6 +347,7 @@ class CallLogAdapter(
|
||||
} else {
|
||||
recipient.getDisplayName(context)
|
||||
}
|
||||
binding.callRecipientMuted.visible = recipient.isMuted && recipient.callNotificationSetting == RecipientTable.NotificationSetting.DO_NOT_NOTIFY
|
||||
}
|
||||
|
||||
private fun presentCallInfo(call: CallLogRow.Call, date: Long) {
|
||||
|
||||
+1
@@ -69,6 +69,7 @@ class AppSettingsActivity : DSLSettingsActivity(), GooglePayComponent {
|
||||
AppSettingsRoute.NotificationsRoute.Notifications -> AppSettingsFragmentDirections.actionDirectToNotificationsSettingsFragment()
|
||||
AppSettingsRoute.ChangeNumberRoute.Start -> AppSettingsFragmentDirections.actionDirectToChangeNumberFragment()
|
||||
is AppSettingsRoute.DonationsRoute.Donations -> AppSettingsFragmentDirections.actionDirectToManageDonations().setDirectToCheckoutType(appSettingsRoute.directToCheckoutType)
|
||||
AppSettingsRoute.NotificationsRoute.MutedNotifications -> AppSettingsFragmentDirections.actionDirectToChatsSettingsFragment()
|
||||
AppSettingsRoute.NotificationsRoute.NotificationProfiles -> AppSettingsFragmentDirections.actionDirectToNotificationProfiles()
|
||||
is AppSettingsRoute.NotificationsRoute.EditProfile -> AppSettingsFragmentDirections.actionDirectToCreateNotificationProfiles()
|
||||
is AppSettingsRoute.NotificationsRoute.ProfileDetails -> AppSettingsFragmentDirections.actionDirectToNotificationProfileDetails(
|
||||
|
||||
-1
@@ -12,6 +12,5 @@ sealed interface LabsSettingsEvents {
|
||||
data class ToggleBetterSearch(val enabled: Boolean) : LabsSettingsEvents
|
||||
data class ToggleStarredMessages(val enabled: Boolean) : LabsSettingsEvents
|
||||
data class ToggleStickerReplies(val enabled: Boolean) : LabsSettingsEvents
|
||||
data class ToggleMuteBreakthroughNotifications(val enabled: Boolean) : LabsSettingsEvents
|
||||
data class ToggleImprovedMessageDeletion(val enabled: Boolean) : LabsSettingsEvents
|
||||
}
|
||||
|
||||
-9
@@ -143,15 +143,6 @@ private fun LabsSettingsContent(
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.ToggleRow(
|
||||
checked = state.muteBreakthroughNotifications,
|
||||
text = "Improved Notification Management",
|
||||
label = "Adds per-conversation controls to let calls and replies break through mute. New options in the sounds & notifications settings for a chat.",
|
||||
onCheckChanged = { onEvent(LabsSettingsEvents.ToggleMuteBreakthroughNotifications(it)) }
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.ToggleRow(
|
||||
checked = state.improvedMessageDeletion,
|
||||
|
||||
-1
@@ -15,6 +15,5 @@ data class LabsSettingsState(
|
||||
val betterSearch: Boolean = false,
|
||||
val starredMessages: Boolean = false,
|
||||
val stickerReplies: Boolean = false,
|
||||
val muteBreakthroughNotifications: Boolean = false,
|
||||
val improvedMessageDeletion: Boolean = false
|
||||
)
|
||||
|
||||
-5
@@ -41,10 +41,6 @@ class LabsSettingsViewModel : ViewModel() {
|
||||
SignalStore.labs.stickerReplies = event.enabled
|
||||
_state.value = _state.value.copy(stickerReplies = event.enabled)
|
||||
}
|
||||
is LabsSettingsEvents.ToggleMuteBreakthroughNotifications -> {
|
||||
SignalStore.labs.muteBreakthroughNotifications = event.enabled
|
||||
_state.value = _state.value.copy(muteBreakthroughNotifications = event.enabled)
|
||||
}
|
||||
is LabsSettingsEvents.ToggleImprovedMessageDeletion -> {
|
||||
SignalStore.labs.improvedMessageDeletion = event.enabled
|
||||
_state.value = _state.value.copy(improvedMessageDeletion = event.enabled)
|
||||
@@ -60,7 +56,6 @@ class LabsSettingsViewModel : ViewModel() {
|
||||
betterSearch = SignalStore.labs.betterSearch,
|
||||
starredMessages = SignalStore.labs.starredMessages,
|
||||
stickerReplies = SignalStore.labs.stickerReplies,
|
||||
muteBreakthroughNotifications = SignalStore.labs.muteBreakthroughNotifications,
|
||||
improvedMessageDeletion = SignalStore.labs.improvedMessageDeletion
|
||||
)
|
||||
}
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.notifications
|
||||
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.signal.core.ui.compose.ComposeFragment
|
||||
import org.signal.core.ui.compose.Scaffolds
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.thoughtcrime.securesms.R
|
||||
|
||||
/**
|
||||
* Fragment to control default settings when muted
|
||||
*/
|
||||
class GlobalMutedNotificationsFragment : ComposeFragment() {
|
||||
|
||||
private val viewModel: MutedNotificationsViewModel by viewModels(
|
||||
factoryProducer = { MutedNotificationsViewModel.Factory() }
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
Scaffolds.Settings(
|
||||
title = stringResource(R.string.MutedNotificationsFragment__while),
|
||||
navigationIcon = SignalIcons.ArrowStart.imageVector,
|
||||
onNavigationClick = { requireActivity().onBackPressedDispatcher.onBackPressed() },
|
||||
navigationContentDescription = stringResource(id = R.string.Material3SearchToolbar__close),
|
||||
modifier = Modifier.imePadding()
|
||||
) { paddingValues ->
|
||||
MutedNotificationScreen(
|
||||
state = state,
|
||||
onEvent = viewModel::onEvent,
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.notifications
|
||||
|
||||
/**
|
||||
* Events emitted by [MutedNotificationScreen] and handled by [MutedNotificationsViewModel]
|
||||
*/
|
||||
sealed interface MutedNotificationsEvent {
|
||||
data class CallsToggled(val allowCalls: Boolean) : MutedNotificationsEvent
|
||||
data class MentionsToggled(val allowMentions: Boolean) : MutedNotificationsEvent
|
||||
data class RepliesToggled(val allowReplies: Boolean) : MutedNotificationsEvent
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.notifications
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.Rows
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
|
||||
@Composable
|
||||
fun MutedNotificationScreen(
|
||||
state: MutedNotificationsState,
|
||||
onEvent: (MutedNotificationsEvent) -> Unit = {},
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
) {
|
||||
if (RemoteConfig.internalUser) {
|
||||
Rows.ToggleRow(
|
||||
icon = ImageVector.vectorResource(org.signal.core.ui.R.drawable.symbol_phone_24),
|
||||
checked = state.allowCalls,
|
||||
text = stringResource(R.string.MutedNotificationsFragment__calls),
|
||||
label = if (state.isGlobal) stringResource(R.string.MutedNotificationsFragment__calls_body_global) else stringResource(R.string.MutedNotificationsFragment__calls_body),
|
||||
onCheckChanged = { onEvent(MutedNotificationsEvent.CallsToggled(it)) }
|
||||
)
|
||||
}
|
||||
if (state.showMentions) {
|
||||
Rows.ToggleRow(
|
||||
icon = ImageVector.vectorResource(org.signal.core.ui.R.drawable.symbol_at_24),
|
||||
checked = state.allowMentions,
|
||||
text = stringResource(R.string.MutedNotificationsFragment__mentions),
|
||||
label = if (state.isGlobal) stringResource(R.string.MutedNotificationsFragment__mentions_body_global) else stringResource(R.string.MutedNotificationsFragment__mentions_body),
|
||||
onCheckChanged = { onEvent(MutedNotificationsEvent.MentionsToggled(it)) }
|
||||
)
|
||||
}
|
||||
if (RemoteConfig.internalUser) {
|
||||
if (state.showReplies) {
|
||||
Rows.ToggleRow(
|
||||
icon = ImageVector.vectorResource(R.drawable.symbol_reply_24),
|
||||
checked = state.allowReplies,
|
||||
text = stringResource(R.string.MutedNotificationsFragment__replies),
|
||||
label = if (state.isGlobal) stringResource(R.string.MutedNotificationsFragment__replies_body_global) else stringResource(R.string.MutedNotificationsFragment__replies_body),
|
||||
onCheckChanged = { onEvent(MutedNotificationsEvent.RepliesToggled(it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
fun MutedNotificationScreenGlobalPreview() {
|
||||
Previews.Preview {
|
||||
MutedNotificationScreen(
|
||||
state = MutedNotificationsState(
|
||||
isGlobal = true,
|
||||
allowCalls = true,
|
||||
allowMentions = false,
|
||||
allowReplies = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
fun MutedNotificationScreenChatPreview() {
|
||||
Previews.Preview {
|
||||
MutedNotificationScreen(
|
||||
state = MutedNotificationsState(
|
||||
isGlobal = false,
|
||||
allowCalls = true,
|
||||
allowMentions = false,
|
||||
allowReplies = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.notifications
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.rx3.asFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.signal.core.ui.compose.EventDrivenViewModel
|
||||
import org.signal.core.util.concurrent.SignalDispatchers
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.NotificationSetting
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
class MutedNotificationsViewModel(private val recipientId: RecipientId? = null) : EventDrivenViewModel<MutedNotificationsEvent>(TAG) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(MutedNotificationsViewModel::class)
|
||||
}
|
||||
|
||||
private val _state = MutableStateFlow(MutedNotificationsState())
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
if (recipientId != null) {
|
||||
viewModelScope.launch(SignalDispatchers.Default) {
|
||||
Recipient.observable(recipientId).asFlow().collectLatest { recipient ->
|
||||
_state.update {
|
||||
it.copy(
|
||||
isGlobal = false,
|
||||
allowCalls = recipient.callNotificationSetting == NotificationSetting.ALWAYS_NOTIFY,
|
||||
showMentions = recipient.isPushV2Group,
|
||||
allowMentions = recipient.mentionSetting == NotificationSetting.ALWAYS_NOTIFY,
|
||||
showReplies = recipient.isPushV2Group,
|
||||
allowReplies = recipient.replyNotificationSetting == NotificationSetting.ALWAYS_NOTIFY
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun processEvent(event: MutedNotificationsEvent) {
|
||||
applyEvent(_state.value, event) { _state.value = it }
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
suspend fun applyEvent(state: MutedNotificationsState, event: MutedNotificationsEvent, stateEmitter: (MutedNotificationsState) -> Unit) {
|
||||
when (event) {
|
||||
is MutedNotificationsEvent.CallsToggled -> {
|
||||
if (recipientId != null) {
|
||||
withContext(SignalDispatchers.Default) {
|
||||
SignalDatabase.recipients.setCallNotificationSetting(recipientId, if (event.allowCalls) NotificationSetting.ALWAYS_NOTIFY else NotificationSetting.DO_NOT_NOTIFY)
|
||||
}
|
||||
} else {
|
||||
SignalStore.settings.allowCallsWhileMuted = event.allowCalls
|
||||
}
|
||||
stateEmitter(state.copy(allowCalls = event.allowCalls))
|
||||
}
|
||||
is MutedNotificationsEvent.MentionsToggled -> {
|
||||
if (recipientId != null) {
|
||||
withContext(SignalDispatchers.Default) {
|
||||
SignalDatabase.recipients.setMentionSetting(recipientId, if (event.allowMentions) NotificationSetting.ALWAYS_NOTIFY else NotificationSetting.DO_NOT_NOTIFY)
|
||||
}
|
||||
} else {
|
||||
SignalStore.settings.allowMentionsWhileMuted = event.allowMentions
|
||||
}
|
||||
stateEmitter(state.copy(allowMentions = event.allowMentions))
|
||||
}
|
||||
is MutedNotificationsEvent.RepliesToggled -> {
|
||||
if (recipientId != null) {
|
||||
withContext(SignalDispatchers.Default) {
|
||||
SignalDatabase.recipients.setReplyNotificationSetting(recipientId, if (event.allowReplies) NotificationSetting.ALWAYS_NOTIFY else NotificationSetting.DO_NOT_NOTIFY)
|
||||
}
|
||||
} else {
|
||||
SignalStore.settings.allowRepliesWhileMuted = event.allowReplies
|
||||
}
|
||||
stateEmitter(state.copy(allowReplies = event.allowReplies))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(private val recipientId: RecipientId? = null) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return modelClass.cast(MutedNotificationsViewModel(recipientId))!!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class MutedNotificationsState(
|
||||
val isGlobal: Boolean = true,
|
||||
val allowCalls: Boolean = SignalStore.settings.allowCallsWhileMuted,
|
||||
val showMentions: Boolean = true,
|
||||
val allowMentions: Boolean = SignalStore.settings.allowMentionsWhileMuted,
|
||||
val showReplies: Boolean = true,
|
||||
val allowReplies: Boolean = SignalStore.settings.allowRepliesWhileMuted
|
||||
)
|
||||
+102
-49
@@ -52,8 +52,10 @@ import org.thoughtcrime.securesms.components.PromptBatterySaverDialogFragment
|
||||
import org.thoughtcrime.securesms.components.settings.app.routes.AppSettingsRoute
|
||||
import org.thoughtcrime.securesms.components.settings.app.routes.AppSettingsRouter
|
||||
import org.thoughtcrime.securesms.components.settings.models.Banner
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.notifications.TurnOnNotificationsBottomSheet
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.RingtoneUtil
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
@@ -85,6 +87,10 @@ class NotificationsSettingsFragment : ComposeFragment() {
|
||||
findNavController().safeNavigate(R.id.action_notificationsSettingsFragment_to_notificationProfilesFragment)
|
||||
}
|
||||
|
||||
AppSettingsRoute.NotificationsRoute.MutedNotifications -> {
|
||||
findNavController().safeNavigate(R.id.action_notificationsSettingsFragment_to_mutedNotificationsFragment)
|
||||
}
|
||||
|
||||
else -> error("Unexpected route: ${it.javaClass.name}")
|
||||
}
|
||||
}
|
||||
@@ -263,6 +269,14 @@ open class DefaultNotificationsSettingsCallbacks(
|
||||
override fun setNotifyWhenContactJoinsSignal(enabled: Boolean) {
|
||||
viewModel.setNotifyWhenContactJoinsSignal(enabled)
|
||||
}
|
||||
|
||||
override fun onMutedClicked() {
|
||||
appSettingsRouter.navigateTo(AppSettingsRoute.NotificationsRoute.MutedNotifications)
|
||||
}
|
||||
|
||||
override fun setReactionNotificationEnabled(enabled: Boolean) {
|
||||
viewModel.setReactionNotificationEnabled(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
interface NotificationsSettingsCallbacks {
|
||||
@@ -286,6 +300,8 @@ interface NotificationsSettingsCallbacks {
|
||||
fun setCallVibrateEnabled(enabled: Boolean) = Unit
|
||||
fun onNavigationProfilesClick() = Unit
|
||||
fun setNotifyWhenContactJoinsSignal(enabled: Boolean) = Unit
|
||||
fun onMutedClicked() = Unit
|
||||
fun setReactionNotificationEnabled(enabled: Boolean) = Unit
|
||||
|
||||
object Empty : NotificationsSettingsCallbacks
|
||||
}
|
||||
@@ -314,19 +330,78 @@ fun NotificationsSettingsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Texts.SectionHeader(stringResource(R.string.NotificationsSettingsFragment__messages))
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.ToggleRow(
|
||||
text = stringResource(R.string.preferences__notifications),
|
||||
text = stringResource(R.string.preferences__enable_notifications),
|
||||
enabled = state.messageNotificationsState.canEnableNotifications,
|
||||
checked = state.messageNotificationsState.notificationsEnabled,
|
||||
onCheckChanged = callbacks::setMessageNotificationsEnabled
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.RadioListRow(
|
||||
text = stringResource(R.string.preferences_notifications__show),
|
||||
labels = stringArrayResource(R.array.pref_notification_privacy_entries),
|
||||
values = stringArrayResource(R.array.pref_notification_privacy_values),
|
||||
selectedValue = state.messageNotificationsState.messagePrivacy,
|
||||
enabled = state.messageNotificationsState.notificationsEnabled,
|
||||
onSelected = callbacks::setMessageNotificationPrivacy
|
||||
)
|
||||
}
|
||||
|
||||
if (RemoteConfig.internalUser) {
|
||||
item {
|
||||
Rows.TextRow(
|
||||
text = stringResource(R.string.preferences_notifications__while_muted),
|
||||
label = getWhileMutedString(),
|
||||
enabled = state.messageNotificationsState.notificationsEnabled,
|
||||
onClick = callbacks::onMutedClicked
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.ToggleRow(
|
||||
text = stringResource(R.string.preferences_notifications__reaction),
|
||||
label = stringResource(R.string.preferences_notifications__notify_reaction),
|
||||
enabled = state.messageNotificationsState.canEnableNotifications,
|
||||
checked = state.messageNotificationsState.reactionNotificationEnabled,
|
||||
onCheckChanged = callbacks::setReactionNotificationEnabled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(michelle): Implement unread reminders here
|
||||
|
||||
item {
|
||||
Rows.ToggleRow(
|
||||
text = stringResource(R.string.NotificationsSettingsFragment__contact_joins_signal),
|
||||
label = stringResource(R.string.NotificationsSettingsFragment__notify_contact),
|
||||
enabled = state.messageNotificationsState.canEnableNotifications,
|
||||
checked = state.notifyWhenContactJoinsSignal,
|
||||
onCheckChanged = callbacks::setNotifyWhenContactJoinsSignal
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.RadioListRow(
|
||||
text = stringResource(R.string.preferences__repeat_alerts),
|
||||
labels = stringArrayResource(R.array.pref_repeat_alerts_entries),
|
||||
values = stringArrayResource(R.array.pref_repeat_alerts_values),
|
||||
selectedValue = state.messageNotificationsState.repeatAlerts.toString(),
|
||||
enabled = state.messageNotificationsState.notificationsEnabled,
|
||||
onSelected = callbacks::setMessageRepeatAlerts
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Dividers.Default()
|
||||
}
|
||||
|
||||
item {
|
||||
Texts.SectionHeader(stringResource(R.string.NotificationsSettingsFragment__sounds))
|
||||
}
|
||||
|
||||
if (deviceState.apiLevel >= 30) {
|
||||
item {
|
||||
Rows.TextRow(
|
||||
@@ -403,28 +478,6 @@ fun NotificationsSettingsScreen(
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.RadioListRow(
|
||||
text = stringResource(R.string.preferences__repeat_alerts),
|
||||
labels = stringArrayResource(R.array.pref_repeat_alerts_entries),
|
||||
values = stringArrayResource(R.array.pref_repeat_alerts_values),
|
||||
selectedValue = state.messageNotificationsState.repeatAlerts.toString(),
|
||||
enabled = state.messageNotificationsState.notificationsEnabled,
|
||||
onSelected = callbacks::setMessageRepeatAlerts
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.RadioListRow(
|
||||
text = stringResource(R.string.preferences_notifications__show),
|
||||
labels = stringArrayResource(R.array.pref_notification_privacy_entries),
|
||||
values = stringArrayResource(R.array.pref_notification_privacy_values),
|
||||
selectedValue = state.messageNotificationsState.messagePrivacy,
|
||||
enabled = state.messageNotificationsState.notificationsEnabled,
|
||||
onSelected = callbacks::setMessageNotificationPrivacy
|
||||
)
|
||||
}
|
||||
|
||||
if (deviceState.apiLevel >= 23 && state.messageNotificationsState.troubleshootNotifications) {
|
||||
item {
|
||||
Rows.TextRow(
|
||||
@@ -467,7 +520,7 @@ fun NotificationsSettingsScreen(
|
||||
|
||||
item {
|
||||
Rows.ToggleRow(
|
||||
text = stringResource(R.string.preferences__notifications),
|
||||
text = stringResource(R.string.preferences__call_notifications),
|
||||
enabled = state.callNotificationsState.canEnableNotifications,
|
||||
checked = state.callNotificationsState.notificationsEnabled,
|
||||
onCheckChanged = callbacks::setCallNotificationsEnabled
|
||||
@@ -500,10 +553,6 @@ fun NotificationsSettingsScreen(
|
||||
Dividers.Default()
|
||||
}
|
||||
|
||||
item {
|
||||
Texts.SectionHeader(stringResource(R.string.NotificationsSettingsFragment__notification_profiles))
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.TextRow(
|
||||
text = stringResource(R.string.NotificationsSettingsFragment__profiles),
|
||||
@@ -511,26 +560,29 @@ fun NotificationsSettingsScreen(
|
||||
onClick = callbacks::onNavigationProfilesClick
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Dividers.Default()
|
||||
}
|
||||
|
||||
item {
|
||||
Texts.SectionHeader(stringResource(R.string.NotificationsSettingsFragment__notify_when))
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.ToggleRow(
|
||||
text = stringResource(R.string.NotificationsSettingsFragment__contact_joins_signal),
|
||||
checked = state.notifyWhenContactJoinsSignal,
|
||||
onCheckChanged = callbacks::setNotifyWhenContactJoinsSignal
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getWhileMutedString(): String {
|
||||
val body = mutableListOf<String>()
|
||||
if (SignalStore.settings.allowCallsWhileMuted) {
|
||||
body.add(stringResource(R.string.MutedNotificationsFragment__calls))
|
||||
}
|
||||
if (SignalStore.settings.allowMentionsWhileMuted) {
|
||||
body.add(stringResource(R.string.MutedNotificationsFragment__mentions))
|
||||
}
|
||||
if (SignalStore.settings.allowRepliesWhileMuted) {
|
||||
body.add(stringResource(R.string.MutedNotificationsFragment__replies))
|
||||
}
|
||||
return if (body.isNotEmpty()) {
|
||||
body.joinToString(", ")
|
||||
} else {
|
||||
stringResource(R.string.preferences__none)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getLedColor(ledColorString: String): Color {
|
||||
return when (ledColorString) {
|
||||
@@ -594,7 +646,8 @@ private fun rememberTestState(): NotificationsSettingsState = remember {
|
||||
repeatAlerts = 1,
|
||||
messagePrivacy = "",
|
||||
priority = 1,
|
||||
troubleshootNotifications = true
|
||||
troubleshootNotifications = true,
|
||||
reactionNotificationEnabled = true
|
||||
),
|
||||
callNotificationsState = CallNotificationsState(
|
||||
notificationsEnabled = true,
|
||||
|
||||
+2
-1
@@ -19,7 +19,8 @@ data class MessageNotificationsState(
|
||||
val repeatAlerts: Int,
|
||||
val messagePrivacy: String,
|
||||
val priority: Int,
|
||||
val troubleshootNotifications: Boolean
|
||||
val troubleshootNotifications: Boolean,
|
||||
val reactionNotificationEnabled: Boolean
|
||||
)
|
||||
|
||||
data class CallNotificationsState(
|
||||
|
||||
+7
-1
@@ -111,6 +111,11 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setReactionNotificationEnabled(enabled: Boolean) {
|
||||
SignalStore.settings.reactionNotifications = enabled
|
||||
refresh()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param currentState If provided and [calculateSlowNotifications] = false, then we will copy the slow notification state from it
|
||||
* @param calculateSlowNotifications If true, calculate the true slow notification state (this is not main-thread safe). Otherwise, it will copy from
|
||||
@@ -135,7 +140,8 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer
|
||||
currentState.messageNotificationsState.troubleshootNotifications
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
reactionNotificationEnabled = SignalStore.settings.reactionNotifications
|
||||
),
|
||||
callNotificationsState = CallNotificationsState(
|
||||
notificationsEnabled = SignalStore.settings.isCallNotificationsEnabled && canEnableNotifications(),
|
||||
|
||||
+1
@@ -74,6 +74,7 @@ sealed interface AppSettingsRoute : Parcelable {
|
||||
@Parcelize
|
||||
sealed interface NotificationsRoute : AppSettingsRoute {
|
||||
data object Notifications : NotificationsRoute
|
||||
data object MutedNotifications : NotificationsRoute
|
||||
data object NotificationProfiles : NotificationsRoute
|
||||
data class EditProfile(val profileId: Long = -1L) : NotificationsRoute
|
||||
data class ProfileDetails(val profileId: Long) : NotificationsRoute
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ sealed interface ConversationSettingsAction {
|
||||
data class OpenChatWallpaper(val recipientId: RecipientId) : ConversationSettingsAction
|
||||
|
||||
/** Open the sounds and notifications screen. */
|
||||
data class NavigateToSoundsAndNotifications(val recipientId: RecipientId, val useInternalScreen: Boolean) : ConversationSettingsAction
|
||||
data class NavigateToSoundsAndNotifications(val recipientId: RecipientId) : ConversationSettingsAction
|
||||
|
||||
/** Open the list of starred messages in this chat. */
|
||||
data class OpenStarredMessages(val threadId: Long) : ConversationSettingsAction
|
||||
|
||||
+1
-6
@@ -401,12 +401,7 @@ class ConversationSettingsFragment : ComposeFragment() {
|
||||
startActivity(ChatWallpaperActivity.createIntent(requireContext(), action.recipientId))
|
||||
}
|
||||
is ConversationSettingsAction.NavigateToSoundsAndNotifications -> {
|
||||
val directions = if (action.useInternalScreen) {
|
||||
ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToSoundsAndNotificationsSettingsFragment2(action.recipientId)
|
||||
} else {
|
||||
ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToSoundsAndNotificationsSettingsFragment(action.recipientId)
|
||||
}
|
||||
|
||||
val directions = ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToSoundsAndNotificationsSettingsFragment(action.recipientId)
|
||||
navController.safeNavigate(directions)
|
||||
}
|
||||
is ConversationSettingsAction.OpenStarredMessages -> {
|
||||
|
||||
-4
@@ -85,10 +85,6 @@ class ConversationSettingsRepository(
|
||||
return SignalStore.labs.starredMessages
|
||||
}
|
||||
|
||||
fun isInternalUser(): Boolean {
|
||||
return RemoteConfig.internalUser
|
||||
}
|
||||
|
||||
fun isAddToStoryAvailable(): Boolean {
|
||||
return !SignalStore.story.isFeatureDisabled
|
||||
}
|
||||
|
||||
-1
@@ -184,7 +184,6 @@ fun GroupSettingsScreen(
|
||||
|
||||
item {
|
||||
SoundsAndNotificationsRow(
|
||||
isInternalUser = state.isInternalUser,
|
||||
enabled = !state.isDeprecatedOrUnregistered,
|
||||
onClick = { onEvent(GroupSettingsEvent.SoundsAndNotificationsClicked) }
|
||||
)
|
||||
|
||||
-1
@@ -25,7 +25,6 @@ data class GroupSettingsState(
|
||||
val threadId: Long = -1L,
|
||||
val storyViewState: StoryViewState = StoryViewState.NONE,
|
||||
val isDeprecatedOrUnregistered: Boolean = false,
|
||||
val isInternalUser: Boolean = false,
|
||||
val displayInternalRecipientDetails: Boolean = false,
|
||||
val starredMessagesEnabled: Boolean = false,
|
||||
val disappearingMessagesLifespan: Int = 0,
|
||||
|
||||
+1
-2
@@ -57,7 +57,6 @@ class GroupSettingsViewModel(
|
||||
groupId = groupId,
|
||||
isDeprecatedOrUnregistered = repository.isDeprecatedOrUnregistered(),
|
||||
starredMessagesEnabled = repository.isStarredMessagesEnabled(),
|
||||
isInternalUser = repository.isInternalUser(),
|
||||
displayInternalRecipientDetails = repository.isInternalRecipientDetailsEnabled()
|
||||
)
|
||||
)
|
||||
@@ -188,7 +187,7 @@ class GroupSettingsViewModel(
|
||||
GroupSettingsEvent.ChatColorAndWallpaperClicked -> _actions.send(ConversationSettingsAction.OpenChatWallpaper(state.recipient.id))
|
||||
|
||||
GroupSettingsEvent.SoundsAndNotificationsClicked -> {
|
||||
_actions.send(ConversationSettingsAction.NavigateToSoundsAndNotifications(state.recipient.id, state.isInternalUser))
|
||||
_actions.send(ConversationSettingsAction.NavigateToSoundsAndNotifications(state.recipient.id))
|
||||
}
|
||||
|
||||
GroupSettingsEvent.StarredMessagesClicked -> _actions.send(ConversationSettingsAction.OpenStarredMessages(state.threadId))
|
||||
|
||||
-1
@@ -146,7 +146,6 @@ fun IndividualSettingsScreen(
|
||||
|
||||
item {
|
||||
SoundsAndNotificationsRow(
|
||||
isInternalUser = state.isInternalUser,
|
||||
enabled = !state.isDeprecatedOrUnregistered,
|
||||
onClick = { onEvent(IndividualSettingsEvent.SoundsAndNotificationsClicked) }
|
||||
)
|
||||
|
||||
-1
@@ -20,7 +20,6 @@ data class IndividualSettingsState(
|
||||
val threadId: Long = -1L,
|
||||
val storyViewState: StoryViewState = StoryViewState.NONE,
|
||||
val isDeprecatedOrUnregistered: Boolean = false,
|
||||
val isInternalUser: Boolean = false,
|
||||
val displayInternalRecipientDetails: Boolean = false,
|
||||
val starredMessagesEnabled: Boolean = false,
|
||||
val disappearingMessagesLifespan: Int = 0,
|
||||
|
||||
+1
-2
@@ -59,7 +59,6 @@ class IndividualSettingsViewModel(
|
||||
IndividualSettingsState(
|
||||
isDeprecatedOrUnregistered = repository.isDeprecatedOrUnregistered(),
|
||||
starredMessagesEnabled = repository.isStarredMessagesEnabled(),
|
||||
isInternalUser = repository.isInternalUser(),
|
||||
displayInternalRecipientDetails = repository.isInternalRecipientDetailsEnabled()
|
||||
)
|
||||
)
|
||||
@@ -176,7 +175,7 @@ class IndividualSettingsViewModel(
|
||||
_actions.send(ConversationSettingsAction.OpenChatWallpaper(recipientId))
|
||||
}
|
||||
IndividualSettingsEvent.SoundsAndNotificationsClicked -> {
|
||||
_actions.send(ConversationSettingsAction.NavigateToSoundsAndNotifications(recipientId, state.isInternalUser))
|
||||
_actions.send(ConversationSettingsAction.NavigateToSoundsAndNotifications(recipientId))
|
||||
}
|
||||
IndividualSettingsEvent.StarredMessagesClicked -> {
|
||||
_actions.send(ConversationSettingsAction.OpenStarredMessages(state.threadId))
|
||||
|
||||
-1
@@ -106,7 +106,6 @@ fun ReleaseNotesSettingsScreen(
|
||||
|
||||
item {
|
||||
SoundsAndNotificationsRow(
|
||||
isInternalUser = state.isInternalUser,
|
||||
enabled = !state.isDeprecatedOrUnregistered,
|
||||
onClick = { onEvent(IndividualSettingsEvent.SoundsAndNotificationsClicked) }
|
||||
)
|
||||
|
||||
+1
-5
@@ -64,13 +64,10 @@ fun ChatColorAndWallpaperRow(
|
||||
fun SoundsAndNotificationsRow(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isInternalUser: Boolean = false,
|
||||
enabled: Boolean = true
|
||||
) {
|
||||
val label = stringResource(R.string.ConversationSettingsFragment__sounds_and_notifications)
|
||||
|
||||
Rows.TextRow(
|
||||
text = if (isInternalUser) "$label (Internal Only)" else label,
|
||||
text = stringResource(R.string.ConversationSettingsFragment__sounds_and_notifications),
|
||||
icon = painterResource(CoreUiR.drawable.symbol_speaker_24),
|
||||
enabled = enabled,
|
||||
onClick = onClick,
|
||||
@@ -179,7 +176,6 @@ private fun ChatSettingsRowsPreview() {
|
||||
DisappearingMessagesRow(lifespanSeconds = 7.days.inWholeSeconds.toInt(), enabled = false, onClick = {})
|
||||
ChatColorAndWallpaperRow(onClick = {})
|
||||
SoundsAndNotificationsRow(onClick = {})
|
||||
SoundsAndNotificationsRow(isInternalUser = true, onClick = {})
|
||||
SoundsAndNotificationsRow(enabled = false, onClick = {})
|
||||
StarredMessagesRow(onClick = {})
|
||||
}
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package org.thoughtcrime.securesms.components.settings.conversation.sounds
|
||||
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.signal.core.ui.compose.ComposeFragment
|
||||
import org.signal.core.ui.compose.Scaffolds
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.app.notifications.MutedNotificationScreen
|
||||
import org.thoughtcrime.securesms.components.settings.app.notifications.MutedNotificationsViewModel
|
||||
|
||||
/**
|
||||
* Fragment to control while muted settings for a specific chat
|
||||
*/
|
||||
class MutedNotificationsFragment : ComposeFragment() {
|
||||
|
||||
private val viewModel: MutedNotificationsViewModel by viewModels(
|
||||
factoryProducer = {
|
||||
MutedNotificationsViewModel.Factory(MutedNotificationsFragmentArgs.fromBundle(requireArguments()).recipientId)
|
||||
}
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
Scaffolds.Settings(
|
||||
title = stringResource(R.string.MutedNotificationsFragment__while),
|
||||
navigationIcon = SignalIcons.ArrowStart.imageVector,
|
||||
onNavigationClick = { requireActivity().onBackPressedDispatcher.onBackPressed() },
|
||||
navigationContentDescription = stringResource(id = R.string.Material3SearchToolbar__close),
|
||||
modifier = Modifier.imePadding()
|
||||
) { paddingValues ->
|
||||
MutedNotificationScreen(
|
||||
state = state,
|
||||
onEvent = viewModel::onEvent,
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -54,4 +54,9 @@ sealed interface SoundsAndNotificationsEvent {
|
||||
* [custom notifications settings screen][org.thoughtcrime.securesms.components.settings.conversation.sounds.custom.CustomNotificationsSettingsFragment].
|
||||
*/
|
||||
data object NavigateToCustomNotifications : SoundsAndNotificationsEvent
|
||||
|
||||
/**
|
||||
* User tapped "When Muted" and navigates to [MutedNotificationsFragment]
|
||||
*/
|
||||
data object NavigateToMutedNotifications : SoundsAndNotificationsEvent
|
||||
}
|
||||
|
||||
+35
-101
@@ -1,125 +1,59 @@
|
||||
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 com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.signal.core.ui.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.MuteDialog
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.Utils.formatMutedUntil
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
class SoundsAndNotificationsSettingsFragment :
|
||||
DSLSettingsFragment(
|
||||
titleId = R.string.ConversationSettingsFragment__sounds_and_notifications
|
||||
) {
|
||||
|
||||
private val mentionLabels: Array<String> by lazy {
|
||||
resources.getStringArray(R.array.SoundsAndNotificationsSettingsFragment__mention_labels)
|
||||
}
|
||||
class SoundsAndNotificationsSettingsFragment : ComposeFragment() {
|
||||
|
||||
private val viewModel: SoundsAndNotificationsSettingsViewModel by viewModels(
|
||||
factoryProducer = {
|
||||
val recipientId = SoundsAndNotificationsSettingsFragmentArgs.fromBundle(requireArguments()).recipientId
|
||||
val repository = SoundsAndNotificationsSettingsRepository(requireContext())
|
||||
|
||||
SoundsAndNotificationsSettingsViewModel.Factory(recipientId, repository)
|
||||
SoundsAndNotificationsSettingsViewModel.Factory(recipientId)
|
||||
}
|
||||
)
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.channelConsistencyCheck()
|
||||
}
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
override fun bindAdapter(adapter: MappingAdapter) {
|
||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||
if (state.channelConsistencyCheckComplete && state.recipientId != Recipient.UNKNOWN.id) {
|
||||
adapter.submitList(getConfiguration(state).toMappingModelList())
|
||||
}
|
||||
if (!state.channelConsistencyCheckComplete || state.recipientId == Recipient.UNKNOWN.id) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConfiguration(state: SoundsAndNotificationsSettingsState): DSLConfiguration {
|
||||
return configure {
|
||||
val muteSummary = if (state.muteUntil > 0) {
|
||||
state.muteUntil.formatMutedUntil(requireContext())
|
||||
} else {
|
||||
getString(R.string.SoundsAndNotificationsSettingsFragment__not_muted)
|
||||
}
|
||||
|
||||
val muteIcon = if (state.muteUntil > 0) {
|
||||
R.drawable.ic_bell_disabled_24
|
||||
} else {
|
||||
R.drawable.ic_bell_24
|
||||
}
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.SoundsAndNotificationsSettingsFragment__mute_notifications),
|
||||
icon = DSLSettingsIcon.from(muteIcon),
|
||||
summary = DSLSettingsText.from(muteSummary),
|
||||
onClick = {
|
||||
if (state.muteUntil <= 0) {
|
||||
MuteDialog.show(requireContext(), childFragmentManager, viewLifecycleOwner, viewModel::setMuteUntil)
|
||||
} else {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(muteSummary)
|
||||
.setPositiveButton(R.string.ConversationSettingsFragment__unmute) { dialog, _ ->
|
||||
viewModel.unmute()
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.dismiss() }
|
||||
.show()
|
||||
SoundsAndNotificationsSettingsScreen(
|
||||
state = state,
|
||||
formatMuteUntil = { it.formatMutedUntil(requireContext()) },
|
||||
onEvent = { event ->
|
||||
when (event) {
|
||||
is SoundsAndNotificationsEvent.NavigateToCustomNotifications -> {
|
||||
val action = SoundsAndNotificationsSettingsFragmentDirections
|
||||
.actionSoundsAndNotificationsSettingsFragmentToCustomNotificationsSettingsFragment(state.recipientId)
|
||||
Navigation.findNavController(requireView()).safeNavigate(action)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (state.hasMentionsSupport) {
|
||||
val mentionSelection = if (state.mentionSetting == RecipientTable.NotificationSetting.ALWAYS_NOTIFY) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
|
||||
radioListPref(
|
||||
title = DSLSettingsText.from(R.string.SoundsAndNotificationsSettingsFragment__mentions),
|
||||
icon = DSLSettingsIcon.from(R.drawable.ic_at_24),
|
||||
selected = mentionSelection,
|
||||
listItems = mentionLabels,
|
||||
onSelected = {
|
||||
viewModel.setMentionSetting(
|
||||
if (it == 0) {
|
||||
RecipientTable.NotificationSetting.ALWAYS_NOTIFY
|
||||
} else {
|
||||
RecipientTable.NotificationSetting.DO_NOT_NOTIFY
|
||||
}
|
||||
)
|
||||
is SoundsAndNotificationsEvent.NavigateToMutedNotifications -> {
|
||||
val action = SoundsAndNotificationsSettingsFragmentDirections
|
||||
.actionSoundsAndNotificationsSettingsFragmentToMutedNotificationsFragment(state.recipientId)
|
||||
Navigation.findNavController(requireView()).safeNavigate(action)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val customSoundSummary = if (state.hasCustomNotificationSettings) {
|
||||
R.string.preferences_on
|
||||
} else {
|
||||
R.string.preferences_off
|
||||
}
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.SoundsAndNotificationsSettingsFragment__custom_notifications),
|
||||
icon = DSLSettingsIcon.from(R.drawable.ic_speaker_24),
|
||||
summary = DSLSettingsText.from(customSoundSummary),
|
||||
onClick = {
|
||||
val action = SoundsAndNotificationsSettingsFragmentDirections.actionSoundsAndNotificationsSettingsFragmentToCustomNotificationsSettingsFragment(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))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* 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))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components.settings.conversation.sounds
|
||||
|
||||
import android.content.Context
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
class SoundsAndNotificationsSettingsRepository(private val context: Context) {
|
||||
|
||||
fun ensureCustomChannelConsistency(complete: () -> Unit) {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
if (NotificationChannels.supported()) {
|
||||
NotificationChannels.getInstance().ensureCustomChannelConsistency()
|
||||
}
|
||||
complete()
|
||||
}
|
||||
}
|
||||
|
||||
fun setMuteUntil(recipientId: RecipientId, muteUntil: Long) {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
SignalDatabase.recipients.setMuted(recipientId, muteUntil)
|
||||
}
|
||||
}
|
||||
|
||||
fun setMentionSetting(recipientId: RecipientId, mentionSetting: RecipientTable.NotificationSetting) {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
SignalDatabase.recipients.setMentionSetting(recipientId, mentionSetting)
|
||||
}
|
||||
}
|
||||
|
||||
fun hasCustomNotificationSettings(recipientId: RecipientId, consumer: (Boolean) -> Unit) {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
val recipient = Recipient.resolved(recipientId)
|
||||
consumer(
|
||||
if (recipient.notificationChannel != null || !NotificationChannels.supported()) {
|
||||
true
|
||||
} else {
|
||||
NotificationChannels.getInstance().updateWithShortcutBasedChannel(recipient)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
-157
@@ -5,30 +5,18 @@
|
||||
|
||||
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.graphics.vector.ImageVector
|
||||
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 androidx.compose.ui.res.vectorResource
|
||||
import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Dialogs
|
||||
import org.signal.core.ui.compose.Dividers
|
||||
@@ -37,21 +25,19 @@ 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
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
|
||||
@Composable
|
||||
fun SoundsAndNotificationsSettingsScreen(
|
||||
state: SoundsAndNotificationsSettingsState2,
|
||||
state: SoundsAndNotificationsSettingsState,
|
||||
formatMuteUntil: (Long) -> String,
|
||||
onEvent: (SoundsAndNotificationsEvent) -> Unit,
|
||||
onNavigationClick: () -> Unit,
|
||||
onMuteClick: () -> Unit
|
||||
) {
|
||||
val isMuted = state.muteUntil > 0
|
||||
val isMuted = state.isMuted
|
||||
var showUnmuteDialog by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffolds.Settings(
|
||||
@@ -79,6 +65,14 @@ fun SoundsAndNotificationsSettingsScreen(
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Dividers.Default()
|
||||
}
|
||||
|
||||
item {
|
||||
Texts.SectionHeader(text = stringResource(R.string.preferences__notifications))
|
||||
}
|
||||
|
||||
// Mute
|
||||
item {
|
||||
val muteSummary = if (isMuted) {
|
||||
@@ -103,54 +97,18 @@ fun SoundsAndNotificationsSettingsScreen(
|
||||
)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (RemoteConfig.internalUser || 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)) }
|
||||
Rows.TextRow(
|
||||
text = stringResource(R.string.SoundsAndNotificationsSettingsFragment__when_muted),
|
||||
label = getMuteSummary(state),
|
||||
icon = ImageVector.vectorResource(R.drawable.symbol_bell_badge_24),
|
||||
onClick = { onEvent(SoundsAndNotificationsEvent.NavigateToMutedNotifications) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
// TODO(michelle): Unread reminders
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,100 +125,21 @@ fun SoundsAndNotificationsSettingsScreen(
|
||||
}
|
||||
|
||||
@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
|
||||
}
|
||||
)
|
||||
fun getMuteSummary(state: SoundsAndNotificationsSettingsState): String {
|
||||
val body = mutableListOf<String>()
|
||||
if (RemoteConfig.internalUser && state.callNotificationSetting == NotificationSetting.ALWAYS_NOTIFY) {
|
||||
body.add(stringResource(R.string.MutedNotificationsFragment__calls))
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (state.hasMentionsSupport && state.mentionSetting == NotificationSetting.ALWAYS_NOTIFY) {
|
||||
body.add(stringResource(R.string.MutedNotificationsFragment__mentions))
|
||||
}
|
||||
if (RemoteConfig.internalUser && state.replyNotificationSetting == NotificationSetting.ALWAYS_NOTIFY) {
|
||||
body.add(stringResource(R.string.MutedNotificationsFragment__replies))
|
||||
}
|
||||
return if (body.isNotEmpty()) {
|
||||
body.joinToString(", ")
|
||||
} else {
|
||||
stringResource(R.string.preferences__none)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +148,7 @@ private fun NotificationSettingDialog(
|
||||
private fun SoundsAndNotificationsSettingsScreenMutedPreview() {
|
||||
Previews.Preview {
|
||||
SoundsAndNotificationsSettingsScreen(
|
||||
state = SoundsAndNotificationsSettingsState2(
|
||||
state = SoundsAndNotificationsSettingsState(
|
||||
muteUntil = Long.MAX_VALUE,
|
||||
callNotificationSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
mentionSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
@@ -291,7 +170,7 @@ private fun SoundsAndNotificationsSettingsScreenMutedPreview() {
|
||||
private fun SoundsAndNotificationsSettingsScreenUnmutedPreview() {
|
||||
Previews.Preview {
|
||||
SoundsAndNotificationsSettingsScreen(
|
||||
state = SoundsAndNotificationsSettingsState2(
|
||||
state = SoundsAndNotificationsSettingsState(
|
||||
muteUntil = 0L,
|
||||
callNotificationSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
mentionSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
|
||||
+9
-4
@@ -1,14 +1,19 @@
|
||||
package org.thoughtcrime.securesms.components.settings.conversation.sounds
|
||||
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.NotificationSetting
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
data class SoundsAndNotificationsSettingsState(
|
||||
val recipientId: RecipientId = Recipient.UNKNOWN.id,
|
||||
val muteUntil: Long = 0L,
|
||||
val mentionSetting: RecipientTable.NotificationSetting = RecipientTable.NotificationSetting.DO_NOT_NOTIFY,
|
||||
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 channelConsistencyCheckComplete: Boolean = false,
|
||||
val unreadReminders: Boolean = false
|
||||
) {
|
||||
val isMuted = muteUntil > 0
|
||||
}
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
+67
-29
@@ -1,60 +1,98 @@
|
||||
package org.thoughtcrime.securesms.components.settings.conversation.sounds
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
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
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
|
||||
class SoundsAndNotificationsSettingsViewModel(
|
||||
private val recipientId: RecipientId,
|
||||
private val repository: SoundsAndNotificationsSettingsRepository
|
||||
) : ViewModel() {
|
||||
private val recipientId: RecipientId
|
||||
) : ViewModel(), RecipientForeverObserver {
|
||||
|
||||
private val store = Store(SoundsAndNotificationsSettingsState())
|
||||
private val _state = MutableStateFlow(SoundsAndNotificationsSettingsState())
|
||||
val state: StateFlow<SoundsAndNotificationsSettingsState> = _state
|
||||
|
||||
val state: LiveData<SoundsAndNotificationsSettingsState> = store.stateLiveData
|
||||
private val liveRecipient = Recipient.live(recipientId)
|
||||
|
||||
init {
|
||||
store.update(Recipient.live(recipientId).liveData) { recipient, state ->
|
||||
state.copy(
|
||||
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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setMuteUntil(muteUntil: Long) {
|
||||
repository.setMuteUntil(recipientId, muteUntil)
|
||||
override fun onCleared() {
|
||||
liveRecipient.removeForeverObserver(this)
|
||||
}
|
||||
|
||||
fun unmute() {
|
||||
repository.setMuteUntil(recipientId, 0L)
|
||||
}
|
||||
|
||||
fun setMentionSetting(mentionSetting: RecipientTable.NotificationSetting) {
|
||||
repository.setMentionSetting(recipientId, mentionSetting)
|
||||
}
|
||||
|
||||
fun channelConsistencyCheck() {
|
||||
store.update { s -> s.copy(channelConsistencyCheckComplete = false) }
|
||||
repository.ensureCustomChannelConsistency {
|
||||
store.update { s -> s.copy(channelConsistencyCheckComplete = true) }
|
||||
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
|
||||
is SoundsAndNotificationsEvent.NavigateToMutedNotifications -> Unit // Navigation handled by UI
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val recipientId: RecipientId,
|
||||
private val repository: SoundsAndNotificationsSettingsRepository
|
||||
) : ViewModelProvider.Factory {
|
||||
private fun applySetMuteUntil(muteUntil: Long) {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
SignalDatabase.recipients.setMuted(recipientId, muteUntil)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applySetMentionSetting(setting: NotificationSetting) {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
SignalDatabase.recipients.setMentionSetting(recipientId, setting)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applySetCallNotificationSetting(setting: NotificationSetting) {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
SignalDatabase.recipients.setCallNotificationSetting(recipientId, setting)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applySetReplyNotificationSetting(setting: NotificationSetting) {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
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(SoundsAndNotificationsSettingsViewModel(recipientId, repository)))
|
||||
return requireNotNull(modelClass.cast(SoundsAndNotificationsSettingsViewModel(recipientId)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-102
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* 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.Default) {
|
||||
SignalDatabase.recipients.setMuted(recipientId, muteUntil)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applySetMentionSetting(setting: NotificationSetting) {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
SignalDatabase.recipients.setMentionSetting(recipientId, setting)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applySetCallNotificationSetting(setting: NotificationSetting) {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
SignalDatabase.recipients.setCallNotificationSetting(recipientId, setting)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applySetReplyNotificationSetting(setting: NotificationSetting) {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,7 +247,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 ${NotificationSetting.ALWAYS_NOTIFY.id},
|
||||
$MENTION_SETTING INTEGER DEFAULT ${NotificationSetting.SYSTEM_DEFAULT.id},
|
||||
$CAPABILITIES INTEGER DEFAULT 0,
|
||||
$LAST_SESSION_RESET BLOB DEFAULT NULL,
|
||||
$WALLPAPER BLOB DEFAULT NULL,
|
||||
@@ -271,8 +271,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
$NOTE TEXT DEFAULT NULL,
|
||||
$MESSAGE_EXPIRATION_TIME_VERSION INTEGER DEFAULT 1 NOT 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},
|
||||
$CALL_NOTIFICATION_SETTING INTEGER DEFAULT ${NotificationSetting.SYSTEM_DEFAULT.id},
|
||||
$REPLY_NOTIFICATION_SETTING INTEGER DEFAULT ${NotificationSetting.SYSTEM_DEFAULT.id},
|
||||
$BLOCKED_AT INTEGER DEFAULT 0
|
||||
)
|
||||
"""
|
||||
@@ -4579,9 +4579,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
MESSAGE_EXPIRATION_TIME_VERSION to 1,
|
||||
SEALED_SENDER_MODE to 0,
|
||||
STORAGE_SERVICE_PROTO to null,
|
||||
MENTION_SETTING to NotificationSetting.ALWAYS_NOTIFY.id,
|
||||
CALL_NOTIFICATION_SETTING to NotificationSetting.ALWAYS_NOTIFY.id,
|
||||
REPLY_NOTIFICATION_SETTING to NotificationSetting.ALWAYS_NOTIFY.id,
|
||||
MENTION_SETTING to NotificationSetting.SYSTEM_DEFAULT.id,
|
||||
CALL_NOTIFICATION_SETTING to NotificationSetting.SYSTEM_DEFAULT.id,
|
||||
REPLY_NOTIFICATION_SETTING to NotificationSetting.SYSTEM_DEFAULT.id,
|
||||
CAPABILITIES to 0,
|
||||
LAST_SESSION_RESET to null,
|
||||
WALLPAPER to null,
|
||||
@@ -5174,13 +5174,22 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
}
|
||||
|
||||
enum class NotificationSetting(val id: Int) {
|
||||
ALWAYS_NOTIFY(0),
|
||||
DO_NOT_NOTIFY(1);
|
||||
SYSTEM_DEFAULT(0),
|
||||
DO_NOT_NOTIFY(1),
|
||||
ALWAYS_NOTIFY(2);
|
||||
|
||||
companion object {
|
||||
fun fromId(id: Int): NotificationSetting {
|
||||
return entries[id]
|
||||
}
|
||||
|
||||
fun resolve(setting: NotificationSetting, allowedByDefault: Boolean): NotificationSetting {
|
||||
return if (setting == SYSTEM_DEFAULT) {
|
||||
if (allowedByDefault) ALWAYS_NOTIFY else DO_NOT_NOTIFY
|
||||
} else {
|
||||
setting
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.managegroup.dialogs;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.CheckedTextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.NotificationSetting;
|
||||
|
||||
public final class GroupMentionSettingDialog {
|
||||
|
||||
public static void show(@NonNull Context context, @NonNull NotificationSetting mentionSetting, @Nullable Consumer<NotificationSetting> callback) {
|
||||
SelectionCallback selectionCallback = new SelectionCallback(mentionSetting, callback);
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.GroupMentionSettingDialog_notify_me_for_mentions)
|
||||
.setView(getView(context, mentionSetting, selectionCallback))
|
||||
.setPositiveButton(android.R.string.ok, selectionCallback)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
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);
|
||||
|
||||
View.OnClickListener listener = (v) -> {
|
||||
alwaysNotify.setChecked(alwaysNotify == v);
|
||||
dontNotify.setChecked(dontNotify == v);
|
||||
|
||||
if (alwaysNotify.isChecked()) {
|
||||
selectionCallback.selection = NotificationSetting.ALWAYS_NOTIFY;
|
||||
} else if (dontNotify.isChecked()) {
|
||||
selectionCallback.selection = NotificationSetting.DO_NOT_NOTIFY;
|
||||
}
|
||||
};
|
||||
|
||||
alwaysNotify.setOnClickListener(listener);
|
||||
dontNotify.setOnClickListener(listener);
|
||||
|
||||
switch (mentionSetting) {
|
||||
case ALWAYS_NOTIFY:
|
||||
listener.onClick(alwaysNotify);
|
||||
break;
|
||||
case DO_NOT_NOTIFY:
|
||||
listener.onClick(dontNotify);
|
||||
break;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class SelectionCallback implements DialogInterface.OnClickListener {
|
||||
|
||||
@NonNull private final NotificationSetting previousMentionSetting;
|
||||
@NonNull private NotificationSetting selection;
|
||||
@Nullable private final Consumer<NotificationSetting> callback;
|
||||
|
||||
public SelectionCallback(@NonNull NotificationSetting previousMentionSetting, @Nullable Consumer<NotificationSetting> callback) {
|
||||
this.previousMentionSetting = previousMentionSetting;
|
||||
this.selection = previousMentionSetting;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (callback != null && selection != previousMentionSetting) {
|
||||
callback.accept(selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,6 @@ class LabsValues internal constructor(store: KeyValueStore) : SignalStoreValues(
|
||||
|
||||
var stickerReplies by booleanValue(STICKER_REPLIES, false).falseForExternalUsers()
|
||||
|
||||
var muteBreakthroughNotifications by booleanValue(MUTE_BREAKTHROUGH_NOTIFICATIONS, true).falseForExternalUsers()
|
||||
|
||||
var improvedMessageDeletion by booleanValue(IMPROVED_MESSAGE_DELETION, true).falseForExternalUsers()
|
||||
|
||||
private fun SignalStoreValueDelegate<Boolean>.falseForExternalUsers(): SignalStoreValueDelegate<Boolean> {
|
||||
|
||||
@@ -60,6 +60,10 @@ public final class SettingsValues extends SignalStoreValues {
|
||||
public static final String MESSAGE_IN_CHAT_SOUNDS_ENABLED = "settings.message.in.chats.sounds.enabled";
|
||||
public static final String MESSAGE_REPEAT_ALERTS = "settings.message.repeat.alerts";
|
||||
public static final String MESSAGE_NOTIFICATION_PRIVACY = "settings.message.notification.privacy";
|
||||
public static final String MESSAGE_NOTIFICATION_REACTION = "settings.message.notification.reaction";
|
||||
public static final String MESSAGE_NOTIFICATION_MUTED_CALLS = "settings.message.notifications.muted.call";
|
||||
public static final String MESSAGE_NOTIFICATION_MUTED_MENTIONS = "settings.message.notifications.muted.mentions";
|
||||
public static final String MESSAGE_NOTIFICATION_MUTED_REPLIES = "settings.message.notifications.muted.replies";
|
||||
public static final String CALL_NOTIFICATIONS_ENABLED = "settings.call.notifications.enabled";
|
||||
public static final String CALL_RINGTONE = "settings.call.ringtone";
|
||||
public static final String CALL_VIBRATE_ENABLED = "settings.call.vibrate.enabled";
|
||||
@@ -443,6 +447,38 @@ public final class SettingsValues extends SignalStoreValues {
|
||||
putString(MESSAGE_NOTIFICATION_PRIVACY, messageNotificationsPrivacy.toString());
|
||||
}
|
||||
|
||||
public boolean getReactionNotifications() {
|
||||
return getBoolean(MESSAGE_NOTIFICATION_REACTION, true);
|
||||
}
|
||||
|
||||
public void setReactionNotifications(boolean show) {
|
||||
putBoolean(MESSAGE_NOTIFICATION_REACTION, show);
|
||||
}
|
||||
|
||||
public boolean getAllowCallsWhileMuted() {
|
||||
return getBoolean(MESSAGE_NOTIFICATION_MUTED_CALLS, false);
|
||||
}
|
||||
|
||||
public void setAllowCallsWhileMuted(boolean allow) {
|
||||
putBoolean(MESSAGE_NOTIFICATION_MUTED_CALLS, allow);
|
||||
}
|
||||
|
||||
public boolean getAllowMentionsWhileMuted() {
|
||||
return getBoolean(MESSAGE_NOTIFICATION_MUTED_MENTIONS, true);
|
||||
}
|
||||
|
||||
public void setAllowMentionsWhileMuted(boolean allow) {
|
||||
putBoolean(MESSAGE_NOTIFICATION_MUTED_MENTIONS, allow);
|
||||
}
|
||||
|
||||
public boolean getAllowRepliesWhileMuted() {
|
||||
return getBoolean(MESSAGE_NOTIFICATION_MUTED_REPLIES, true);
|
||||
}
|
||||
|
||||
public void setAllowRepliesWhileMuted(boolean allow) {
|
||||
putBoolean(MESSAGE_NOTIFICATION_MUTED_REPLIES, allow);
|
||||
}
|
||||
|
||||
public boolean isCallNotificationsEnabled() {
|
||||
return getBoolean(CALL_NOTIFICATIONS_ENABLED, TextSecurePreferences.isCallNotificationsEnabled(AppDependencies.getApplication()));
|
||||
}
|
||||
|
||||
+7
@@ -172,6 +172,13 @@ class DefaultMessageNotifier(context: Application) : MessageNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
if (state.reactionsDisabledFilteredMessages.isNotEmpty()) {
|
||||
Log.i(TAG, "Marking ${state.reactionsDisabledFilteredMessages.size} reactions as notified to skip notification")
|
||||
state.reactionsDisabledFilteredMessages.forEach { item ->
|
||||
SignalDatabase.messages.markAsNotified(item.id)
|
||||
}
|
||||
}
|
||||
|
||||
if (!SignalStore.settings.isMessageNotificationsEnabled) {
|
||||
Log.i(TAG, "Marking ${state.conversations.size} conversations as notified to skip notification")
|
||||
state.conversations.forEach { conversation ->
|
||||
|
||||
@@ -12,7 +12,12 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
||||
/**
|
||||
* Hold all state for notifications for all conversations.
|
||||
*/
|
||||
data class NotificationState(val conversations: List<NotificationConversation>, val muteFilteredMessages: List<FilteredMessage>, val profileFilteredMessages: List<FilteredMessage>) {
|
||||
data class NotificationState(
|
||||
val conversations: List<NotificationConversation>,
|
||||
val muteFilteredMessages: List<FilteredMessage>,
|
||||
val profileFilteredMessages: List<FilteredMessage>,
|
||||
val reactionsDisabledFilteredMessages: List<FilteredMessage>
|
||||
) {
|
||||
|
||||
val threadCount: Int = conversations.size
|
||||
val isEmpty: Boolean = conversations.isEmpty()
|
||||
@@ -89,6 +94,6 @@ data class NotificationState(val conversations: List<NotificationConversation>,
|
||||
data class FilteredMessage(val id: Long, val isMms: Boolean)
|
||||
|
||||
companion object {
|
||||
val EMPTY = NotificationState(emptyList(), emptyList(), emptyList())
|
||||
val EMPTY = NotificationState(emptyList(), emptyList(), emptyList(), emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
+11
-3
@@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.database.model.MessageId
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.ReactionRecord
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
|
||||
import org.thoughtcrime.securesms.polls.PollVote
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
@@ -94,6 +95,7 @@ object NotificationStateProvider {
|
||||
val conversations: MutableList<NotificationConversation> = mutableListOf()
|
||||
val muteFilteredMessages: MutableList<NotificationState.FilteredMessage> = mutableListOf()
|
||||
val profileFilteredMessages: MutableList<NotificationState.FilteredMessage> = mutableListOf()
|
||||
val reactionsDisabledFilteredMessages: MutableList<NotificationState.FilteredMessage> = mutableListOf()
|
||||
|
||||
messages.groupBy { it.thread }
|
||||
.forEach { (thread, threadMessages) ->
|
||||
@@ -105,6 +107,7 @@ object NotificationStateProvider {
|
||||
MessageInclusion.EXCLUDE -> Unit
|
||||
MessageInclusion.MUTE_FILTERED -> muteFilteredMessages += NotificationState.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
||||
MessageInclusion.PROFILE_FILTERED -> profileFilteredMessages += NotificationState.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
||||
MessageInclusion.REACTIONS_DISABLED_FILTERED -> reactionsDisabledFilteredMessages += NotificationState.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
||||
}
|
||||
|
||||
if (notification.hasUnreadReactions) {
|
||||
@@ -114,6 +117,7 @@ object NotificationStateProvider {
|
||||
MessageInclusion.EXCLUDE -> Unit
|
||||
MessageInclusion.MUTE_FILTERED -> muteFilteredMessages += NotificationState.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
||||
MessageInclusion.PROFILE_FILTERED -> profileFilteredMessages += NotificationState.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
||||
MessageInclusion.REACTIONS_DISABLED_FILTERED -> reactionsDisabledFilteredMessages += NotificationState.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,6 +129,7 @@ object NotificationStateProvider {
|
||||
MessageInclusion.EXCLUDE -> Unit
|
||||
MessageInclusion.MUTE_FILTERED -> muteFilteredMessages += NotificationState.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
||||
MessageInclusion.PROFILE_FILTERED -> profileFilteredMessages += NotificationState.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
||||
MessageInclusion.REACTIONS_DISABLED_FILTERED -> reactionsDisabledFilteredMessages += NotificationState.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +146,7 @@ object NotificationStateProvider {
|
||||
}
|
||||
}
|
||||
|
||||
return NotificationState(conversations, muteFilteredMessages, profileFilteredMessages)
|
||||
return NotificationState(conversations, muteFilteredMessages, profileFilteredMessages, reactionsDisabledFilteredMessages)
|
||||
}
|
||||
|
||||
private data class NotificationMessage(
|
||||
@@ -198,7 +203,9 @@ object NotificationStateProvider {
|
||||
}
|
||||
|
||||
fun includeReaction(reaction: ReactionRecord, notificationProfile: NotificationProfile?): MessageInclusion {
|
||||
return if (threadRecipient.isMuted) {
|
||||
return if (!SignalStore.settings.reactionNotifications) {
|
||||
MessageInclusion.REACTIONS_DISABLED_FILTERED
|
||||
} else if (threadRecipient.isMuted) {
|
||||
MessageInclusion.MUTE_FILTERED
|
||||
} else if (notificationProfile != null && !notificationProfile.isRecipientAllowed(threadRecipient.id)) {
|
||||
MessageInclusion.PROFILE_FILTERED
|
||||
@@ -233,6 +240,7 @@ object NotificationStateProvider {
|
||||
INCLUDE,
|
||||
EXCLUDE,
|
||||
MUTE_FILTERED,
|
||||
PROFILE_FILTERED
|
||||
PROFILE_FILTERED,
|
||||
REACTIONS_DISABLED_FILTERED
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,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.SpanUtil
|
||||
import org.thoughtcrime.securesms.util.UsernameUtil.isValidUsernameForSearch
|
||||
@@ -109,9 +110,9 @@ class Recipient(
|
||||
private val sealedSenderAccessModeValue: SealedSenderAccessMode = SealedSenderAccessMode.UNKNOWN,
|
||||
private val capabilities: RecipientRecord.Capabilities = RecipientRecord.Capabilities.UNKNOWN,
|
||||
val storageId: ByteArray? = null,
|
||||
val mentionSetting: NotificationSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
private val callNotificationSettingValue: NotificationSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
private val replyNotificationSettingValue: NotificationSetting = NotificationSetting.ALWAYS_NOTIFY,
|
||||
private val mentionSettingValue: NotificationSetting = NotificationSetting.SYSTEM_DEFAULT,
|
||||
private val callNotificationSettingValue: NotificationSetting = NotificationSetting.SYSTEM_DEFAULT,
|
||||
private val replyNotificationSettingValue: NotificationSetting = NotificationSetting.SYSTEM_DEFAULT,
|
||||
private val wallpaperValue: ChatWallpaper? = null,
|
||||
private val chatColorsValue: ChatColors? = null,
|
||||
val avatarColor: AvatarColor = AvatarColor.UNKNOWN,
|
||||
@@ -338,13 +339,17 @@ 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 mentions should break through mute for this recipient. */
|
||||
val mentionSetting: NotificationSetting
|
||||
get() = NotificationSetting.resolve(mentionSettingValue, SignalStore.settings.allowMentionsWhileMuted)
|
||||
|
||||
/** Whether calls should break through mute for this recipient. */
|
||||
val callNotificationSetting: NotificationSetting
|
||||
get() = if (SignalStore.labs.muteBreakthroughNotifications) callNotificationSettingValue else NotificationSetting.ALWAYS_NOTIFY
|
||||
get() = if (RemoteConfig.internalUser) NotificationSetting.resolve(callNotificationSettingValue, SignalStore.settings.allowCallsWhileMuted) else NotificationSetting.ALWAYS_NOTIFY
|
||||
|
||||
/** Whether replies should break through mute for this recipient. Only applicable to groups. */
|
||||
/** Whether replies should break through mute for this recipient. */
|
||||
val replyNotificationSetting: NotificationSetting
|
||||
get() = if (groupIdValue == null) NotificationSetting.DO_NOT_NOTIFY else if (SignalStore.labs.muteBreakthroughNotifications) replyNotificationSettingValue else mentionSetting
|
||||
get() = if (groupIdValue == null) NotificationSetting.DO_NOT_NOTIFY else if (RemoteConfig.internalUser) NotificationSetting.resolve(replyNotificationSettingValue, SignalStore.settings.allowRepliesWhileMuted) else mentionSetting
|
||||
|
||||
/** The state around whether we can send sealed sender to this user. */
|
||||
val sealedSenderAccessMode: SealedSenderAccessMode = if (pni.isPresent && pni == serviceId) {
|
||||
@@ -875,7 +880,7 @@ class Recipient(
|
||||
profileAvatar == other.profileAvatar &&
|
||||
notificationChannelValue == other.notificationChannelValue &&
|
||||
sealedSenderAccessModeValue == other.sealedSenderAccessModeValue &&
|
||||
mentionSetting == other.mentionSetting &&
|
||||
mentionSettingValue == other.mentionSettingValue &&
|
||||
callNotificationSettingValue == other.callNotificationSettingValue &&
|
||||
replyNotificationSettingValue == other.replyNotificationSettingValue &&
|
||||
wallpaperValue == other.wallpaperValue &&
|
||||
|
||||
@@ -184,7 +184,7 @@ object RecipientCreator {
|
||||
sealedSenderAccessModeValue = record.sealedSenderAccessMode,
|
||||
capabilities = record.capabilities,
|
||||
storageId = record.storageId,
|
||||
mentionSetting = record.mentionSetting,
|
||||
mentionSettingValue = record.mentionSetting,
|
||||
callNotificationSettingValue = record.callNotificationSetting,
|
||||
replyNotificationSettingValue = record.replyNotificationSetting,
|
||||
wallpaperValue = record.wallpaper?.validate(),
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M4.44,19.15H7.69C8.09,21.18 9.83,22.65 12,22.65C14.17,22.65 15.91,21.18 16.31,19.15H19.56C21.22,19.15 22.25,18 22.25,16.7C22.25,15.87 21.9,15.22 21.22,14.46C20.02,13.12 19.38,11.65 19.2,9.11L19.18,8.86C18.63,9.08 18.03,9.2 17.4,9.2V9.23C17.62,12.19 18.42,14.02 19.88,15.66C20.34,16.18 20.45,16.42 20.45,16.7C20.45,17.06 20.16,17.35 19.56,17.35H4.44C3.84,17.35 3.55,17.06 3.55,16.7C3.55,16.42 3.66,16.18 4.12,15.66C5.58,14.02 6.38,12.19 6.6,9.23C6.88,5.22 9.17,3.15 12,3.15C12.26,3.15 12.51,3.17 12.75,3.2C12.91,2.58 13.19,2.01 13.57,1.52C13.06,1.41 12.54,1.35 12,1.35C8.23,1.35 5.16,4.12 4.8,9.11C4.62,11.65 3.98,13.12 2.78,14.46C2.1,15.22 1.75,15.87 1.75,16.7C1.75,18 2.78,19.15 4.44,19.15ZM17.4,7.5C19.11,7.5 20.5,6.1 20.5,4.4C20.5,2.69 19.11,1.3 17.4,1.3C15.7,1.3 14.3,2.69 14.3,4.4C14.3,6.1 15.7,7.5 17.4,7.5ZM9.55,19.15H14.45C14.1,20.16 13.16,20.85 12,20.85C10.84,20.85 9.9,20.16 9.55,19.15Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
@@ -50,21 +50,36 @@
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/call_recipient_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start|center_vertical"
|
||||
android:maxLines="2"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/Signal.Text.BodyLarge"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/call_info"
|
||||
app:layout_constraintEnd_toStartOf="@id/call_status_barrier"
|
||||
app:layout_constraintEnd_toStartOf="@id/call_recipient_muted"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@+id/call_recipient_avatar"
|
||||
app:layout_constraintTop_toTopOf="@+id/call_recipient_avatar"
|
||||
tools:text="Miles Morales" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/call_recipient_muted"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@drawable/symbol_bell_slash_compact_16"
|
||||
app:layout_constraintBottom_toBottomOf="@id/call_recipient_name"
|
||||
app:layout_constraintEnd_toStartOf="@id/call_status_barrier"
|
||||
app:layout_constraintStart_toEndOf="@id/call_recipient_name"
|
||||
app:layout_constraintTop_toTopOf="@id/call_recipient_name"
|
||||
app:tint="@color/signal_colorOnSurfaceVariant" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/call_info"
|
||||
android:layout_width="0dp"
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:viewBindingIgnore="true"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.AppCompat.Subhead"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:paddingLeft="?attr/dialogPreferredPadding"
|
||||
android:paddingRight="?attr/dialogPreferredPadding"
|
||||
android:text="@string/GroupMentionSettingDialog_receive_notifications_when_youre_mentioned_in_muted_chats" />
|
||||
|
||||
<CheckedTextView
|
||||
android:id="@+id/group_mention_setting_always_notify"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:drawableStart="?android:attr/listChoiceIndicatorSingle"
|
||||
android:drawablePadding="20dp"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?attr/listPreferredItemHeightSmall"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="?attr/dialogPreferredPadding"
|
||||
android:text="@string/GroupMentionSettingDialog_always_notify_me"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?attr/textColorAlertDialogListItem"
|
||||
android:theme="@style/Signal.Widget.CompoundButton.RadioButton" />
|
||||
|
||||
<CheckedTextView
|
||||
android:id="@+id/group_mention_setting_dont_notify"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:drawableStart="?android:attr/listChoiceIndicatorSingle"
|
||||
android:drawablePadding="20dp"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?attr/listPreferredItemHeightSmall"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="?attr/dialogPreferredPadding"
|
||||
android:text="@string/GroupMentionSettingDialog_dont_notify_me"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?attr/textColorAlertDialogListItem"
|
||||
android:theme="@style/Signal.Widget.CompoundButton.RadioButton" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -557,6 +557,14 @@
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_notificationsSettingsFragment_to_mutedNotificationsFragment"
|
||||
app:destination="@id/mutedNotificationsFragment"
|
||||
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>
|
||||
|
||||
<!-- region Privacy -->
|
||||
@@ -1099,6 +1107,10 @@
|
||||
|
||||
<!-- endregion -->
|
||||
|
||||
<fragment
|
||||
android:id="@+id/mutedNotificationsFragment"
|
||||
android:name="org.thoughtcrime.securesms.components.settings.app.notifications.GlobalMutedNotificationsFragment" />
|
||||
|
||||
<!-- Notification Profiles -->
|
||||
<fragment
|
||||
android:id="@+id/notificationProfilesFragment"
|
||||
|
||||
@@ -70,20 +70,6 @@
|
||||
|
||||
</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"
|
||||
@@ -160,8 +146,7 @@
|
||||
|
||||
<fragment
|
||||
android:id="@+id/soundsAndNotificationsSettingsFragment"
|
||||
android:name="org.thoughtcrime.securesms.components.settings.conversation.sounds.SoundsAndNotificationsSettingsFragment"
|
||||
tools:layout="@layout/dsl_settings_fragment">
|
||||
android:name="org.thoughtcrime.securesms.components.settings.conversation.sounds.SoundsAndNotificationsSettingsFragment">
|
||||
|
||||
<argument
|
||||
android:name="recipient_id"
|
||||
@@ -175,19 +160,9 @@
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit" />
|
||||
|
||||
</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"
|
||||
android:id="@+id/action_soundsAndNotificationsSettingsFragment_to_mutedNotificationsFragment"
|
||||
app:destination="@id/mutedNotificationsFragment"
|
||||
app:enterAnim="@anim/fragment_open_enter"
|
||||
app:exitAnim="@anim/fragment_open_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
@@ -225,6 +200,16 @@
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/mutedNotificationsFragment"
|
||||
android:name="org.thoughtcrime.securesms.components.settings.conversation.sounds.MutedNotificationsFragment">
|
||||
|
||||
<argument
|
||||
android:name="recipient_id"
|
||||
app:argType="org.thoughtcrime.securesms.recipients.RecipientId" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/shareableGroupLinkFragment"
|
||||
android:name="org.thoughtcrime.securesms.recipients.ui.sharablegrouplink.ShareableGroupLinkFragment">
|
||||
|
||||
@@ -1579,12 +1579,6 @@
|
||||
<!-- Dialog message -->
|
||||
<string name="RestoreActivity__no_longer_registered_message">This is likely because you registered your Signal account on a different device.</string>
|
||||
|
||||
<!-- GroupMentionSettingDialog -->
|
||||
<string name="GroupMentionSettingDialog_notify_me_for_mentions">Notify me for Mentions</string>
|
||||
<string name="GroupMentionSettingDialog_receive_notifications_when_youre_mentioned_in_muted_chats">Receive notifications when you’re mentioned in muted chats?</string>
|
||||
<string name="GroupMentionSettingDialog_always_notify_me">Always notify me</string>
|
||||
<string name="GroupMentionSettingDialog_dont_notify_me">Don\'t notify me</string>
|
||||
|
||||
<!-- ManageProfileFragment -->
|
||||
<!-- Explanation text about usernames etc displayed underneath buttons to view and edit username etc -->
|
||||
<string name="ManageProfileFragment__your_username">Your username, QR code and link aren\'t visible on your profile. Only share your username with people you trust.</string>
|
||||
@@ -3265,7 +3259,7 @@
|
||||
<!-- Title for a dialog where a user chooses how long they\'d like to mute notifications for -->
|
||||
<string name="MuteDialog_mute_notifications">Mute notifications</string>
|
||||
<!-- Dialog option that, when pressed, will open a time picker to let the user choose how long they\'d like to mute notifications for -->
|
||||
<string name="MuteDialog__mute_until">Mute until…</string>
|
||||
<string name="MuteDialog__mute_until">Until…</string>
|
||||
|
||||
<!-- MuteUntilTimePickerBottomSheet -->
|
||||
<!-- Title for a dialog where a user chooses how long they\'d like to mute notifications for -->
|
||||
@@ -4133,10 +4127,10 @@
|
||||
<string name="arrays__use_default">Use default</string>
|
||||
<string name="arrays__use_custom">Use custom</string>
|
||||
|
||||
<string name="arrays__mute_for_one_hour">Mute for 1 hour</string>
|
||||
<string name="arrays__mute_for_eight_hours">Mute for 8 hours</string>
|
||||
<string name="arrays__mute_for_one_day">Mute for 1 day</string>
|
||||
<string name="arrays__mute_for_seven_days">Mute for 7 days</string>
|
||||
<string name="arrays__mute_for_one_hour">1 Hour</string>
|
||||
<string name="arrays__mute_for_eight_hours">8 Hours</string>
|
||||
<string name="arrays__mute_for_one_day">1 Day</string>
|
||||
<string name="arrays__mute_for_seven_days">1 Week</string>
|
||||
<string name="arrays__always">Always</string>
|
||||
|
||||
<string name="arrays__settings_default">Settings default</string>
|
||||
@@ -4191,6 +4185,10 @@
|
||||
<!-- Legacy preference menu item to choose the interval of inactivity before lockout -->
|
||||
<string name="preferences__inactivity_timeout_interval">Inactivity timeout interval</string>
|
||||
<string name="preferences__notifications">Notifications</string>
|
||||
<!-- Preference item menu to enable notifications -->
|
||||
<string name="preferences__enable_notifications">Enable notifications</string>
|
||||
<!-- Preference header for call notifications -->
|
||||
<string name="preferences__call_notifications">Call notifications</string>
|
||||
<string name="preferences__led_color">LED color</string>
|
||||
<string name="preferences__led_color_unknown">Unknown</string>
|
||||
<string name="preferences__pref_led_blink_title">LED blink pattern</string>
|
||||
@@ -4348,6 +4346,16 @@
|
||||
<string name="preference_data_and_storage__using_less_data_may_improve_calls_on_bad_networks">Using less data may improve calls on bad networks.</string>
|
||||
<string name="preferences_notifications__in_chat_sounds">In-chat sounds</string>
|
||||
<string name="preferences_notifications__show">Show</string>
|
||||
<!-- Preference item to set notification settings when chats are muted -->
|
||||
<string name="preferences_notifications__while_muted">While muted</string>
|
||||
<!-- Option in settings to turn on reaction notifications -->
|
||||
<string name="preferences_notifications__reaction">Reaction notifications</string>
|
||||
<!-- Body option in settings to turn on reaction notifications -->
|
||||
<string name="preferences_notifications__notify_reaction">Show notifications for reactions to messages you send</string>
|
||||
<!-- Option in settings to turn on unread reminders -->
|
||||
<string name="preferences_notifications__unread">Unread reminders</string>
|
||||
<!-- Body option to turn on unread reminders -->
|
||||
<string name="preferences_notifications__notify_unread">Occasionally notify when there are unread messages in muted chats</string>
|
||||
<string name="preferences_notifications__ringtone">Ringtone</string>
|
||||
<string name="preferences_chats__message_text_size">Message font size</string>
|
||||
<!-- Option in settings that will provide a shortcut to notification system settings to control the priority of Signal notifications -->
|
||||
@@ -5922,8 +5930,19 @@
|
||||
<!-- NotificationsSettingsFragment -->
|
||||
<string name="NotificationsSettingsFragment__messages">Messages</string>
|
||||
<string name="NotificationsSettingsFragment__calls">Calls</string>
|
||||
<string name="NotificationsSettingsFragment__notify_when">Notify when…</string>
|
||||
<!-- Notification header for sounds -->
|
||||
<string name="NotificationsSettingsFragment__sounds">Sounds</string>
|
||||
<string name="NotificationsSettingsFragment__contact_joins_signal">Contact joins Signal</string>
|
||||
<!-- Notification body to get notified when someone joins Signal -->
|
||||
<string name="NotificationsSettingsFragment__notify_contact">Show a notification when a phone contact joins Signal</string>
|
||||
<!-- Option to reset their notification settings -->
|
||||
<string name="NotificationsSettingsFragment__reset">Reset notification settings</string>
|
||||
<!-- Option body to reset their notification settings -->
|
||||
<string name="NotificationsSettingsFragment__reset_notifications">Reset notification settings to default, including custom settings for your chats</string>
|
||||
<!-- Dialog to reset their notification settings -->
|
||||
<string name="NotificationsSettingsFragment__reset_body">Reset all notification settings to default for all chats?</string>
|
||||
<!-- Confirmation button to reset dialog -->
|
||||
<string name="NotificationsSettingsFragment__reset_confirm">Reset</string>
|
||||
<!-- Notification preference header -->
|
||||
<string name="NotificationsSettingsFragment__notification_profiles">Notification profiles</string>
|
||||
<!-- Notification preference option header -->
|
||||
@@ -5931,6 +5950,27 @@
|
||||
<!-- Notification preference summary text -->
|
||||
<string name="NotificationsSettingsFragment__create_a_profile_to_receive_notifications_only_from_people_and_groups_you_choose">Create a profile to receive notifications only from people and groups you choose.</string>
|
||||
|
||||
<!-- Header to describe screen that shows while muted settings -->
|
||||
<string name="MutedNotificationsFragment__while">While muted</string>
|
||||
<!-- Title for toggle row to control call settings while muted -->
|
||||
<string name="MutedNotificationsFragment__calls">Calls</string>
|
||||
<!-- Description for toggle row to control call settings while muted -->
|
||||
<string name="MutedNotificationsFragment__calls_body_global">Ring or notify when a call is started in muted chats.</string>
|
||||
<!-- Title for toggle row to control mentions settings while muted -->
|
||||
<string name="MutedNotificationsFragment__mentions">Mentions</string>
|
||||
<!-- Description for toggle row to control mentions settings while muted -->
|
||||
<string name="MutedNotificationsFragment__mentions_body_global">Notify when you are mentioned in muted chats.</string>
|
||||
<!-- Title for toggle row to control replies settings while muted -->
|
||||
<string name="MutedNotificationsFragment__replies">Replies</string>
|
||||
<!-- Description for toggle row to control replies settings while muted -->
|
||||
<string name="MutedNotificationsFragment__replies_body_global">Notify when someone replies to your message in muted chats.</string>
|
||||
<!-- Description for toggle row to control call settings while muted -->
|
||||
<string name="MutedNotificationsFragment__calls_body">Ring or notify when a call is started while this chat is muted.</string>
|
||||
<!-- Description for toggle row to control mentions settings while muted -->
|
||||
<string name="MutedNotificationsFragment__mentions_body">Notify when you are mentioned while this chat is muted.</string>
|
||||
<!-- Description for toggle row to control replies settings while muted -->
|
||||
<string name="MutedNotificationsFragment__replies_body">Notify when someone replies to your message while this chat is muted.</string>
|
||||
|
||||
<!-- NotificationProfilesFragment -->
|
||||
<!-- Title for notification profiles screen that shows all existing profiles; Title with hyphenation. Translation can use soft hyphen - Unicode U+00AD -->
|
||||
<string name="NotificationProfilesFragment__notification_profiles">Notification profiles</string>
|
||||
|
||||
-1
@@ -67,7 +67,6 @@ class GroupSettingsViewModelTest {
|
||||
|
||||
every { repository.isDeprecatedOrUnregistered() } returns false
|
||||
every { repository.isStarredMessagesEnabled() } returns false
|
||||
every { repository.isInternalUser() } returns false
|
||||
every { repository.isInternalRecipientDetailsEnabled() } returns false
|
||||
every { repository.isStoriesFeatureEnabled() } returns false
|
||||
every { repository.isAddToStoryAvailable() } returns true
|
||||
|
||||
+2
-4
@@ -69,7 +69,6 @@ class IndividualSettingsViewModelTest {
|
||||
|
||||
every { repository.isDeprecatedOrUnregistered() } returns false
|
||||
every { repository.isStarredMessagesEnabled() } returns false
|
||||
every { repository.isInternalUser() } returns false
|
||||
every { repository.isInternalRecipientDetailsEnabled() } returns false
|
||||
every { repository.isStoriesFeatureEnabled() } returns false
|
||||
every { repository.isBlockable(any()) } returns true
|
||||
@@ -335,14 +334,13 @@ class IndividualSettingsViewModelTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sounds and notifications click uses the internal screen for internal users`() = runTest(testDispatcher) {
|
||||
every { repository.isInternalUser() } returns true
|
||||
fun `sounds and notifications click navigates`() = runTest(testDispatcher) {
|
||||
val viewModel = createViewModel()
|
||||
val actions = collectActions(viewModel)
|
||||
|
||||
viewModel.onEvent(IndividualSettingsEvent.SoundsAndNotificationsClicked)
|
||||
|
||||
assertEquals(ConversationSettingsAction.NavigateToSoundsAndNotifications(RECIPIENT_ID, useInternalScreen = true), actions.single())
|
||||
assertEquals(ConversationSettingsAction.NavigateToSoundsAndNotifications(RECIPIENT_ID), actions.single())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user