mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-26 03:40:56 +01:00
Replace with new custom notifications page.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.components.settings.conversation.sounds
|
||||
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.Navigation
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.thoughtcrime.securesms.MuteDialog
|
||||
import org.thoughtcrime.securesms.R
|
||||
@@ -13,7 +14,6 @@ import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.Utils.formatMutedUntil
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.ui.notifications.CustomNotificationsDialogFragment
|
||||
|
||||
class SoundsAndNotificationsSettingsFragment : DSLSettingsFragment(
|
||||
titleId = R.string.ConversationSettingsFragment__sounds_and_notifications
|
||||
@@ -110,7 +110,8 @@ class SoundsAndNotificationsSettingsFragment : DSLSettingsFragment(
|
||||
icon = DSLSettingsIcon.from(R.drawable.ic_speaker_24),
|
||||
summary = DSLSettingsText.from(customSoundSummary),
|
||||
onClick = {
|
||||
CustomNotificationsDialogFragment.create(state.recipientId).show(parentFragmentManager, null)
|
||||
val action = SoundsAndNotificationsSettingsFragmentDirections.actionSoundsAndNotificationsSettingsFragmentToCustomNotificationsSettingsFragment(state.recipientId)
|
||||
Navigation.findNavController(requireView()).navigate(action)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
package org.thoughtcrime.securesms.components.settings.conversation.sounds.custom
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.viewModels
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.util.RingtoneUtil
|
||||
|
||||
class CustomNotificationsSettingsFragment : DSLSettingsFragment(R.string.CustomNotificationsDialogFragment__custom_notifications) {
|
||||
|
||||
private val vibrateLabels: Array<String> by lazy {
|
||||
resources.getStringArray(R.array.recipient_vibrate_entries)
|
||||
}
|
||||
|
||||
private val viewModel: CustomNotificationsSettingsViewModel by viewModels(factoryProducer = this::createFactory)
|
||||
|
||||
private lateinit var callSoundResultLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var messageSoundResultLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
private fun createFactory(): CustomNotificationsSettingsViewModel.Factory {
|
||||
val recipientId = CustomNotificationsSettingsFragmentArgs.fromBundle(requireArguments()).recipientId
|
||||
val repository = CustomNotificationsSettingsRepository(requireContext())
|
||||
|
||||
return CustomNotificationsSettingsViewModel.Factory(recipientId, repository)
|
||||
}
|
||||
|
||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
||||
messageSoundResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
handleResult(result, viewModel::setMessageSound)
|
||||
}
|
||||
|
||||
callSoundResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
handleResult(result, viewModel::setCallSound)
|
||||
}
|
||||
|
||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||
adapter.submitList(getConfiguration(state).toMappingModelList())
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleResult(result: ActivityResult, resultHandler: (Uri?) -> Unit) {
|
||||
val resultCode = result.resultCode
|
||||
val data = result.data
|
||||
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
val uri: Uri? = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
|
||||
resultHandler(uri)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConfiguration(state: CustomNotificationsSettingsState): DSLConfiguration {
|
||||
return configure {
|
||||
|
||||
val controlsEnabled = state.hasCustomNotifications && state.isInitialLoadComplete
|
||||
|
||||
sectionHeaderPref(R.string.CustomNotificationsDialogFragment__messages)
|
||||
|
||||
if (NotificationChannels.supported()) {
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.CustomNotificationsDialogFragment__use_custom_notifications),
|
||||
isEnabled = state.isInitialLoadComplete,
|
||||
isChecked = state.hasCustomNotifications,
|
||||
onClick = { viewModel.setHasCustomNotifications(!state.hasCustomNotifications) }
|
||||
)
|
||||
}
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.CustomNotificationsDialogFragment__notification_sound),
|
||||
summary = DSLSettingsText.from(getRingtoneSummary(requireContext(), state.messageSound, Settings.System.DEFAULT_NOTIFICATION_URI)),
|
||||
isEnabled = controlsEnabled,
|
||||
onClick = { requestSound(state.messageSound, false) }
|
||||
)
|
||||
|
||||
if (NotificationChannels.supported()) {
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.CustomNotificationsDialogFragment__vibrate),
|
||||
isEnabled = controlsEnabled,
|
||||
isChecked = state.messageVibrateEnabled,
|
||||
onClick = { viewModel.setMessageVibrate(RecipientDatabase.VibrateState.fromBoolean(!state.messageVibrateEnabled)) }
|
||||
)
|
||||
} else {
|
||||
radioListPref(
|
||||
title = DSLSettingsText.from(R.string.CustomNotificationsDialogFragment__vibrate),
|
||||
isEnabled = controlsEnabled,
|
||||
listItems = vibrateLabels,
|
||||
selected = state.messageVibrateState.id,
|
||||
onSelected = {
|
||||
viewModel.setMessageVibrate(RecipientDatabase.VibrateState.fromId(it))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (state.showCallingOptions) {
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(R.string.CustomNotificationsDialogFragment__call_settings)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.CustomNotificationsDialogFragment__ringtone),
|
||||
summary = DSLSettingsText.from(getRingtoneSummary(requireContext(), state.callSound, Settings.System.DEFAULT_RINGTONE_URI)),
|
||||
isEnabled = controlsEnabled,
|
||||
onClick = { requestSound(state.callSound, true) }
|
||||
)
|
||||
|
||||
radioListPref(
|
||||
title = DSLSettingsText.from(R.string.CustomNotificationsDialogFragment__vibrate),
|
||||
isEnabled = controlsEnabled,
|
||||
listItems = vibrateLabels,
|
||||
selected = state.callVibrateState.id,
|
||||
onSelected = {
|
||||
viewModel.setCallVibrate(RecipientDatabase.VibrateState.fromId(it))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRingtoneSummary(context: Context, ringtone: Uri?, defaultNotificationUri: Uri?): String {
|
||||
if (ringtone == null || ringtone == defaultNotificationUri) {
|
||||
return context.getString(R.string.CustomNotificationsDialogFragment__default)
|
||||
} else if (ringtone.toString().isEmpty()) {
|
||||
return context.getString(R.string.preferences__silent)
|
||||
} else {
|
||||
val tone = RingtoneUtil.getRingtone(requireContext(), ringtone)
|
||||
if (tone != null) {
|
||||
return tone.getTitle(context)
|
||||
}
|
||||
}
|
||||
return context.getString(R.string.CustomNotificationsDialogFragment__default)
|
||||
}
|
||||
|
||||
private fun requestSound(current: Uri?, forCalls: Boolean) {
|
||||
val existing: Uri? = when {
|
||||
current == null -> getDefaultSound(forCalls)
|
||||
current.toString().isEmpty() -> null
|
||||
else -> current
|
||||
}
|
||||
|
||||
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
|
||||
putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true)
|
||||
putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
|
||||
putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, if (forCalls) RingtoneManager.TYPE_RINGTONE else RingtoneManager.TYPE_NOTIFICATION)
|
||||
putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, existing)
|
||||
}
|
||||
|
||||
if (forCalls) {
|
||||
callSoundResultLauncher.launch(intent)
|
||||
} else {
|
||||
messageSoundResultLauncher.launch(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDefaultSound(forCalls: Boolean) = if (forCalls) Settings.System.DEFAULT_RINGTONE_URI else Settings.System.DEFAULT_NOTIFICATION_URI
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.thoughtcrime.securesms.components.settings.conversation.sounds.custom
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.concurrent.SerialExecutor
|
||||
|
||||
class CustomNotificationsSettingsRepository(context: Context) {
|
||||
|
||||
private val context = context.applicationContext
|
||||
private val executor = SerialExecutor(SignalExecutors.BOUNDED)
|
||||
|
||||
fun initialize(recipientId: RecipientId, onInitializationComplete: () -> Unit) {
|
||||
executor.execute {
|
||||
val recipient = Recipient.resolved(recipientId)
|
||||
val database = DatabaseFactory.getRecipientDatabase(context)
|
||||
|
||||
if (NotificationChannels.supported() && recipient.notificationChannel != null) {
|
||||
database.setMessageRingtone(recipient.id, NotificationChannels.getMessageRingtone(context, recipient))
|
||||
database.setMessageVibrate(recipient.id, RecipientDatabase.VibrateState.fromBoolean(NotificationChannels.getMessageVibrate(context, recipient)))
|
||||
|
||||
NotificationChannels.ensureCustomChannelConsistency(context)
|
||||
}
|
||||
|
||||
onInitializationComplete()
|
||||
}
|
||||
}
|
||||
|
||||
fun setHasCustomNotifications(recipientId: RecipientId, hasCustomNotifications: Boolean) {
|
||||
executor.execute {
|
||||
if (hasCustomNotifications) {
|
||||
createCustomNotificationChannel(recipientId)
|
||||
} else {
|
||||
deleteCustomNotificationChannel(recipientId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setMessageVibrate(recipientId: RecipientId, vibrateState: RecipientDatabase.VibrateState) {
|
||||
executor.execute {
|
||||
val recipient: Recipient = Recipient.resolved(recipientId)
|
||||
|
||||
DatabaseFactory.getRecipientDatabase(context).setMessageVibrate(recipient.id, vibrateState)
|
||||
NotificationChannels.updateMessageVibrate(context, recipient, vibrateState)
|
||||
}
|
||||
}
|
||||
|
||||
fun setCallingVibrate(recipientId: RecipientId, vibrateState: RecipientDatabase.VibrateState) {
|
||||
executor.execute {
|
||||
DatabaseFactory.getRecipientDatabase(context).setCallVibrate(recipientId, vibrateState)
|
||||
}
|
||||
}
|
||||
|
||||
fun setMessageSound(recipientId: RecipientId, sound: Uri?) {
|
||||
executor.execute {
|
||||
val recipient: Recipient = Recipient.resolved(recipientId)
|
||||
val defaultValue = SignalStore.settings().messageNotificationSound
|
||||
val newValue: Uri? = if (defaultValue == sound) null else sound ?: Uri.EMPTY
|
||||
|
||||
DatabaseFactory.getRecipientDatabase(context).setMessageRingtone(recipient.id, newValue)
|
||||
NotificationChannels.updateMessageRingtone(context, recipient, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
fun setCallSound(recipientId: RecipientId, sound: Uri?) {
|
||||
executor.execute {
|
||||
val defaultValue = SignalStore.settings().callRingtone
|
||||
val newValue: Uri? = if (defaultValue == sound) null else sound ?: Uri.EMPTY
|
||||
|
||||
DatabaseFactory.getRecipientDatabase(context).setCallRingtone(recipientId, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun createCustomNotificationChannel(recipientId: RecipientId) {
|
||||
val recipient: Recipient = Recipient.resolved(recipientId)
|
||||
val channelId = NotificationChannels.createChannelFor(context, recipient)
|
||||
DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient.id, channelId)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun deleteCustomNotificationChannel(recipientId: RecipientId) {
|
||||
val recipient: Recipient = Recipient.resolved(recipientId)
|
||||
DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient.id, null)
|
||||
NotificationChannels.deleteChannelFor(context, recipient)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.thoughtcrime.securesms.components.settings.conversation.sounds.custom
|
||||
|
||||
import android.net.Uri
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase
|
||||
|
||||
data class CustomNotificationsSettingsState(
|
||||
val isInitialLoadComplete: Boolean = false,
|
||||
val hasCustomNotifications: Boolean = false,
|
||||
val messageVibrateState: RecipientDatabase.VibrateState = RecipientDatabase.VibrateState.DEFAULT,
|
||||
val messageVibrateEnabled: Boolean = false,
|
||||
val messageSound: Uri? = null,
|
||||
val callVibrateState: RecipientDatabase.VibrateState = RecipientDatabase.VibrateState.DEFAULT,
|
||||
val callSound: Uri? = null,
|
||||
val showCallingOptions: Boolean = false,
|
||||
)
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.thoughtcrime.securesms.components.settings.conversation.sounds.custom
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
|
||||
class CustomNotificationsSettingsViewModel(
|
||||
private val recipientId: RecipientId,
|
||||
private val repository: CustomNotificationsSettingsRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val store = Store(CustomNotificationsSettingsState())
|
||||
|
||||
val state: LiveData<CustomNotificationsSettingsState> = store.stateLiveData
|
||||
|
||||
init {
|
||||
repository.initialize(recipientId) {
|
||||
store.update { it.copy(isInitialLoadComplete = true) }
|
||||
}
|
||||
|
||||
store.update(Recipient.live(recipientId).liveData) { recipient, state ->
|
||||
state.copy(
|
||||
hasCustomNotifications = NotificationChannels.supported() && recipient.notificationChannel != null,
|
||||
messageSound = recipient.messageRingtone,
|
||||
messageVibrateState = recipient.messageVibrate,
|
||||
messageVibrateEnabled = when (recipient.messageVibrate) {
|
||||
RecipientDatabase.VibrateState.DEFAULT -> SignalStore.settings().isMessageVibrateEnabled
|
||||
RecipientDatabase.VibrateState.ENABLED -> true
|
||||
RecipientDatabase.VibrateState.DISABLED -> false
|
||||
},
|
||||
showCallingOptions = !recipient.isGroup && recipient.isRegistered,
|
||||
callSound = recipient.callRingtone,
|
||||
callVibrateState = recipient.callVibrate
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setHasCustomNotifications(hasCustomNotifications: Boolean) {
|
||||
repository.setHasCustomNotifications(recipientId, hasCustomNotifications)
|
||||
}
|
||||
|
||||
fun setMessageVibrate(messageVibrateState: RecipientDatabase.VibrateState) {
|
||||
repository.setMessageVibrate(recipientId, messageVibrateState)
|
||||
}
|
||||
|
||||
fun setMessageSound(uri: Uri?) {
|
||||
repository.setMessageSound(recipientId, uri)
|
||||
}
|
||||
|
||||
fun setCallVibrate(callVibrateState: RecipientDatabase.VibrateState) {
|
||||
repository.setCallingVibrate(recipientId, callVibrateState)
|
||||
}
|
||||
|
||||
fun setCallSound(uri: Uri?) {
|
||||
repository.setCallSound(recipientId, uri)
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val recipientId: RecipientId,
|
||||
private val repository: CustomNotificationsSettingsRepository
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return requireNotNull(modelClass.cast(CustomNotificationsSettingsViewModel(recipientId, repository)))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user