|
|
|
|
@@ -1,420 +1,615 @@
|
|
|
|
|
package org.thoughtcrime.securesms.components.settings.app.notifications
|
|
|
|
|
|
|
|
|
|
import android.app.Activity
|
|
|
|
|
import android.content.ActivityNotFoundException
|
|
|
|
|
import android.content.Intent
|
|
|
|
|
import android.graphics.ColorFilter
|
|
|
|
|
import android.graphics.PorterDuff
|
|
|
|
|
import android.graphics.PorterDuffColorFilter
|
|
|
|
|
import android.media.Ringtone
|
|
|
|
|
import android.media.RingtoneManager
|
|
|
|
|
import android.net.Uri
|
|
|
|
|
import android.os.Build
|
|
|
|
|
import android.provider.Settings
|
|
|
|
|
import android.text.TextUtils
|
|
|
|
|
import android.os.Bundle
|
|
|
|
|
import android.view.View
|
|
|
|
|
import android.widget.Toast
|
|
|
|
|
import androidx.activity.result.ActivityResultCallback
|
|
|
|
|
import androidx.activity.result.ActivityResultLauncher
|
|
|
|
|
import androidx.activity.result.contract.ActivityResultContract
|
|
|
|
|
import androidx.annotation.RequiresApi
|
|
|
|
|
import androidx.core.content.ContextCompat
|
|
|
|
|
import androidx.lifecycle.ViewModelProvider
|
|
|
|
|
import androidx.compose.foundation.background
|
|
|
|
|
import androidx.compose.foundation.layout.Box
|
|
|
|
|
import androidx.compose.foundation.layout.Spacer
|
|
|
|
|
import androidx.compose.foundation.layout.padding
|
|
|
|
|
import androidx.compose.foundation.layout.size
|
|
|
|
|
import androidx.compose.foundation.lazy.LazyColumn
|
|
|
|
|
import androidx.compose.foundation.shape.CircleShape
|
|
|
|
|
import androidx.compose.material3.Text
|
|
|
|
|
import androidx.compose.runtime.Composable
|
|
|
|
|
import androidx.compose.runtime.getValue
|
|
|
|
|
import androidx.compose.runtime.remember
|
|
|
|
|
import androidx.compose.ui.Modifier
|
|
|
|
|
import androidx.compose.ui.draw.clip
|
|
|
|
|
import androidx.compose.ui.graphics.Color
|
|
|
|
|
import androidx.compose.ui.graphics.vector.ImageVector
|
|
|
|
|
import androidx.compose.ui.res.colorResource
|
|
|
|
|
import androidx.compose.ui.res.stringArrayResource
|
|
|
|
|
import androidx.compose.ui.res.stringResource
|
|
|
|
|
import androidx.compose.ui.res.vectorResource
|
|
|
|
|
import androidx.compose.ui.unit.dp
|
|
|
|
|
import androidx.fragment.app.Fragment
|
|
|
|
|
import androidx.fragment.app.FragmentActivity
|
|
|
|
|
import androidx.lifecycle.Lifecycle
|
|
|
|
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
|
|
|
import androidx.lifecycle.lifecycleScope
|
|
|
|
|
import androidx.lifecycle.repeatOnLifecycle
|
|
|
|
|
import androidx.navigation.fragment.findNavController
|
|
|
|
|
import androidx.preference.PreferenceManager
|
|
|
|
|
import org.signal.core.util.getParcelableExtraCompat
|
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
|
import org.signal.core.ui.compose.Dividers
|
|
|
|
|
import org.signal.core.ui.compose.Previews
|
|
|
|
|
import org.signal.core.ui.compose.Rows
|
|
|
|
|
import org.signal.core.ui.compose.Scaffolds
|
|
|
|
|
import org.signal.core.ui.compose.SignalPreview
|
|
|
|
|
import org.signal.core.ui.compose.Texts
|
|
|
|
|
import org.signal.core.util.logging.Log
|
|
|
|
|
import org.thoughtcrime.securesms.R
|
|
|
|
|
import org.thoughtcrime.securesms.components.PromptBatterySaverDialogFragment
|
|
|
|
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
|
|
|
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
|
|
|
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
|
|
|
|
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
|
|
|
|
import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder
|
|
|
|
|
import org.thoughtcrime.securesms.components.settings.RadioListPreference
|
|
|
|
|
import org.thoughtcrime.securesms.components.settings.RadioListPreferenceViewHolder
|
|
|
|
|
import org.thoughtcrime.securesms.components.settings.configure
|
|
|
|
|
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.compose.ComposeFragment
|
|
|
|
|
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
|
|
|
|
import org.thoughtcrime.securesms.notifications.TurnOnNotificationsBottomSheet
|
|
|
|
|
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
|
|
|
|
import org.thoughtcrime.securesms.util.RingtoneUtil
|
|
|
|
|
import org.thoughtcrime.securesms.util.ViewUtil
|
|
|
|
|
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
|
|
|
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
|
|
|
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
|
|
|
|
import org.thoughtcrime.securesms.util.viewModel
|
|
|
|
|
|
|
|
|
|
private const val MESSAGE_SOUND_SELECT: Int = 1
|
|
|
|
|
private const val CALL_RINGTONE_SELECT: Int = 2
|
|
|
|
|
private val TAG = Log.tag(NotificationsSettingsFragment::class.java)
|
|
|
|
|
class NotificationsSettingsFragment : ComposeFragment() {
|
|
|
|
|
|
|
|
|
|
class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__notifications) {
|
|
|
|
|
private val viewModel: NotificationsSettingsViewModel by viewModel {
|
|
|
|
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
|
|
|
|
|
|
|
|
|
private val repeatAlertsValues by lazy { resources.getStringArray(R.array.pref_repeat_alerts_values) }
|
|
|
|
|
private val repeatAlertsLabels by lazy { resources.getStringArray(R.array.pref_repeat_alerts_entries) }
|
|
|
|
|
NotificationsSettingsViewModel.Factory(sharedPreferences).create(NotificationsSettingsViewModel::class.java)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val notificationPrivacyValues by lazy { resources.getStringArray(R.array.pref_notification_privacy_values) }
|
|
|
|
|
private val notificationPrivacyLabels by lazy { resources.getStringArray(R.array.pref_notification_privacy_entries) }
|
|
|
|
|
private val appSettingsRouter: AppSettingsRouter by viewModel {
|
|
|
|
|
AppSettingsRouter()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val notificationPriorityValues by lazy { resources.getStringArray(R.array.pref_notification_priority_values) }
|
|
|
|
|
private val notificationPriorityLabels by lazy { resources.getStringArray(R.array.pref_notification_priority_entries) }
|
|
|
|
|
private lateinit var callbacks: DefaultNotificationsSettingsCallbacks
|
|
|
|
|
|
|
|
|
|
private val ledColorValues by lazy { resources.getStringArray(R.array.pref_led_color_values) }
|
|
|
|
|
private val ledColorLabels by lazy { resources.getStringArray(R.array.pref_led_color_entries) }
|
|
|
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
|
|
|
super.onViewCreated(view, savedInstanceState)
|
|
|
|
|
|
|
|
|
|
private val ledBlinkValues by lazy { resources.getStringArray(R.array.pref_led_blink_pattern_values) }
|
|
|
|
|
private val ledBlinkLabels by lazy { resources.getStringArray(R.array.pref_led_blink_pattern_entries) }
|
|
|
|
|
callbacks = DefaultNotificationsSettingsCallbacks(requireActivity(), viewModel, appSettingsRouter, DefaultNotificationsSettingsCallbacks.ActivityResultRegisterer.ForFragment(this))
|
|
|
|
|
|
|
|
|
|
private lateinit var viewModel: NotificationsSettingsViewModel
|
|
|
|
|
viewLifecycleOwner.lifecycleScope.launch {
|
|
|
|
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
|
|
|
|
appSettingsRouter.currentRoute.collect {
|
|
|
|
|
when (it) {
|
|
|
|
|
AppSettingsRoute.NotificationsRoute.NotificationProfiles -> {
|
|
|
|
|
findNavController().safeNavigate(R.id.action_notificationsSettingsFragment_to_notificationProfilesFragment)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else -> error("Unexpected route: ${it.javaClass.name}")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onResume() {
|
|
|
|
|
super.onResume()
|
|
|
|
|
viewModel.refresh()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
|
|
|
if (requestCode == MESSAGE_SOUND_SELECT && resultCode == Activity.RESULT_OK && data != null) {
|
|
|
|
|
val uri: Uri? = data.getParcelableExtraCompat(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java)
|
|
|
|
|
viewModel.setMessageNotificationsSound(uri)
|
|
|
|
|
} else if (requestCode == CALL_RINGTONE_SELECT && resultCode == Activity.RESULT_OK && data != null) {
|
|
|
|
|
val uri: Uri? = data.getParcelableExtraCompat(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java)
|
|
|
|
|
viewModel.setCallRingtone(uri)
|
|
|
|
|
@Composable
|
|
|
|
|
override fun FragmentContent() {
|
|
|
|
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
|
|
|
|
|
|
|
|
|
NotificationsSettingsScreen(state = state, callbacks = callbacks)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Default callbacks that package up launcher handling and other logic that was in the original fragment.
|
|
|
|
|
* This must be called during the creation cycle of the component it is attached to.
|
|
|
|
|
*/
|
|
|
|
|
open class DefaultNotificationsSettingsCallbacks(
|
|
|
|
|
val activity: FragmentActivity,
|
|
|
|
|
val viewModel: NotificationsSettingsViewModel,
|
|
|
|
|
val appSettingsRouter: AppSettingsRouter,
|
|
|
|
|
activityResultRegisterer: ActivityResultRegisterer = ActivityResultRegisterer.ForActivity(activity)
|
|
|
|
|
) : NotificationsSettingsCallbacks {
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
private val TAG = Log.tag(DefaultNotificationsSettingsCallbacks::class)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ActivityResultRegisterer {
|
|
|
|
|
fun <I, O> registerForActivityResult(
|
|
|
|
|
contract: ActivityResultContract<I, O>,
|
|
|
|
|
callback: ActivityResultCallback<O>
|
|
|
|
|
): ActivityResultLauncher<I>
|
|
|
|
|
|
|
|
|
|
class ForActivity(val activity: FragmentActivity) : ActivityResultRegisterer {
|
|
|
|
|
override fun <I, O> registerForActivityResult(
|
|
|
|
|
contract: ActivityResultContract<I, O>,
|
|
|
|
|
callback: ActivityResultCallback<O>
|
|
|
|
|
): ActivityResultLauncher<I> {
|
|
|
|
|
return activity.registerForActivityResult(contract, callback)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ForFragment(val fragment: Fragment) : ActivityResultRegisterer {
|
|
|
|
|
override fun <I, O> registerForActivityResult(
|
|
|
|
|
contract: ActivityResultContract<I, O>,
|
|
|
|
|
callback: ActivityResultCallback<O>
|
|
|
|
|
): ActivityResultLauncher<I> {
|
|
|
|
|
return fragment.registerForActivityResult(contract, callback)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun bindAdapter(adapter: MappingAdapter) {
|
|
|
|
|
adapter.registerFactory(
|
|
|
|
|
LedColorPreference::class.java,
|
|
|
|
|
LayoutFactory(::LedColorPreferenceViewHolder, R.layout.dsl_preference_item)
|
|
|
|
|
)
|
|
|
|
|
private val messageSoundSelectionLauncher: ActivityResultLauncher<Unit> = activityResultRegisterer.registerForActivityResult(
|
|
|
|
|
NotificationSoundSelectionContract(NotificationSoundSelectionContract.Target.MESSAGE),
|
|
|
|
|
viewModel::setMessageNotificationsSound
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Banner.register(adapter)
|
|
|
|
|
private val callsSoundSelectionLauncher: ActivityResultLauncher<Unit> = activityResultRegisterer.registerForActivityResult(
|
|
|
|
|
NotificationSoundSelectionContract(NotificationSoundSelectionContract.Target.CALL),
|
|
|
|
|
viewModel::setCallRingtone
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
|
|
|
|
val factory = NotificationsSettingsViewModel.Factory(sharedPreferences)
|
|
|
|
|
private val notificationPrioritySelectionLauncher: ActivityResultLauncher<Unit> = activityResultRegisterer.registerForActivityResult(
|
|
|
|
|
contract = NotificationPrioritySelectionContract(),
|
|
|
|
|
callback = {}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
viewModel = ViewModelProvider(this, factory)[NotificationsSettingsViewModel::class.java]
|
|
|
|
|
override fun onTurnOnNotificationsActionClick() {
|
|
|
|
|
TurnOnNotificationsBottomSheet.turnOnSystemNotificationsFragment(activity).show(activity.supportFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
viewModel.state.observe(viewLifecycleOwner) {
|
|
|
|
|
adapter.submitList(getConfiguration(it).toMappingModelList())
|
|
|
|
|
override fun onNavigationClick() {
|
|
|
|
|
activity.onBackPressedDispatcher.onBackPressed()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun setMessageNotificationsEnabled(enabled: Boolean) {
|
|
|
|
|
viewModel.setMessageNotificationsEnabled(enabled)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onCustomizeClick() {
|
|
|
|
|
activity.let {
|
|
|
|
|
NotificationChannels.getInstance().openChannelSettings(it, NotificationChannels.getInstance().messagesChannel, null)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun getConfiguration(state: NotificationsSettingsState): DSLConfiguration {
|
|
|
|
|
return configure {
|
|
|
|
|
if (!state.messageNotificationsState.canEnableNotifications) {
|
|
|
|
|
customPref(
|
|
|
|
|
Banner.Model(
|
|
|
|
|
textId = R.string.NotificationSettingsFragment__to_enable_notifications,
|
|
|
|
|
actionId = R.string.NotificationSettingsFragment__turn_on,
|
|
|
|
|
onClick = {
|
|
|
|
|
TurnOnNotificationsBottomSheet.turnOnSystemNotificationsFragment(requireContext()).show(childFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sectionHeaderPref(R.string.NotificationsSettingsFragment__messages)
|
|
|
|
|
|
|
|
|
|
switchPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.preferences__notifications),
|
|
|
|
|
isEnabled = state.messageNotificationsState.canEnableNotifications,
|
|
|
|
|
isChecked = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onClick = {
|
|
|
|
|
viewModel.setMessageNotificationsEnabled(!state.messageNotificationsState.notificationsEnabled)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= 30) {
|
|
|
|
|
clickPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.preferences__customize),
|
|
|
|
|
summary = DSLSettingsText.from(R.string.preferences__change_sound_and_vibration),
|
|
|
|
|
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onClick = {
|
|
|
|
|
NotificationChannels.getInstance().openChannelSettings(requireActivity(), NotificationChannels.getInstance().messagesChannel, null)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
clickPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.preferences__sound),
|
|
|
|
|
summary = DSLSettingsText.from(getRingtoneSummary(state.messageNotificationsState.sound)),
|
|
|
|
|
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onClick = {
|
|
|
|
|
launchMessageSoundSelectionIntent()
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
switchPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.preferences__vibrate),
|
|
|
|
|
isChecked = state.messageNotificationsState.vibrateEnabled,
|
|
|
|
|
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onClick = {
|
|
|
|
|
viewModel.setMessageNotificationVibration(!state.messageNotificationsState.vibrateEnabled)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
customPref(
|
|
|
|
|
LedColorPreference(
|
|
|
|
|
colorValues = ledColorValues,
|
|
|
|
|
radioListPreference = RadioListPreference(
|
|
|
|
|
title = DSLSettingsText.from(R.string.preferences__led_color),
|
|
|
|
|
listItems = ledColorLabels,
|
|
|
|
|
selected = ledColorValues.indexOf(state.messageNotificationsState.ledColor),
|
|
|
|
|
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onSelected = {
|
|
|
|
|
viewModel.setMessageNotificationLedColor(ledColorValues[it])
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (!NotificationChannels.supported()) {
|
|
|
|
|
radioListPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.preferences__pref_led_blink_title),
|
|
|
|
|
listItems = ledBlinkLabels,
|
|
|
|
|
selected = ledBlinkValues.indexOf(state.messageNotificationsState.ledBlink),
|
|
|
|
|
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onSelected = {
|
|
|
|
|
viewModel.setMessageNotificationLedBlink(ledBlinkValues[it])
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switchPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.preferences_notifications__in_chat_sounds),
|
|
|
|
|
isChecked = state.messageNotificationsState.inChatSoundsEnabled,
|
|
|
|
|
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onClick = {
|
|
|
|
|
viewModel.setMessageNotificationInChatSoundsEnabled(!state.messageNotificationsState.inChatSoundsEnabled)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
radioListPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.preferences__repeat_alerts),
|
|
|
|
|
listItems = repeatAlertsLabels,
|
|
|
|
|
selected = repeatAlertsValues.indexOf(state.messageNotificationsState.repeatAlerts.toString()),
|
|
|
|
|
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onSelected = {
|
|
|
|
|
viewModel.setMessageRepeatAlerts(repeatAlertsValues[it].toInt())
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
radioListPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.preferences_notifications__show),
|
|
|
|
|
listItems = notificationPrivacyLabels,
|
|
|
|
|
selected = notificationPrivacyValues.indexOf(state.messageNotificationsState.messagePrivacy),
|
|
|
|
|
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onSelected = {
|
|
|
|
|
viewModel.setMessageNotificationPrivacy(notificationPrivacyValues[it])
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= 23 && state.messageNotificationsState.troubleshootNotifications) {
|
|
|
|
|
clickPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.preferences_notifications__troubleshoot),
|
|
|
|
|
isEnabled = true,
|
|
|
|
|
onClick = {
|
|
|
|
|
PromptBatterySaverDialogFragment.show(childFragmentManager)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT < 30) {
|
|
|
|
|
if (NotificationChannels.supported()) {
|
|
|
|
|
clickPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.preferences_notifications__priority),
|
|
|
|
|
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onClick = {
|
|
|
|
|
launchNotificationPriorityIntent()
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
radioListPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.preferences_notifications__priority),
|
|
|
|
|
listItems = notificationPriorityLabels,
|
|
|
|
|
selected = notificationPriorityValues.indexOf(state.messageNotificationsState.priority.toString()),
|
|
|
|
|
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onSelected = {
|
|
|
|
|
viewModel.setMessageNotificationPriority(notificationPriorityValues[it].toInt())
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dividerPref()
|
|
|
|
|
|
|
|
|
|
sectionHeaderPref(R.string.NotificationsSettingsFragment__calls)
|
|
|
|
|
|
|
|
|
|
switchPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.preferences__notifications),
|
|
|
|
|
isEnabled = state.callNotificationsState.canEnableNotifications,
|
|
|
|
|
isChecked = state.callNotificationsState.notificationsEnabled,
|
|
|
|
|
onClick = {
|
|
|
|
|
viewModel.setCallNotificationsEnabled(!state.callNotificationsState.notificationsEnabled)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
clickPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.preferences_notifications__ringtone),
|
|
|
|
|
summary = DSLSettingsText.from(getRingtoneSummary(state.callNotificationsState.ringtone)),
|
|
|
|
|
isEnabled = state.callNotificationsState.notificationsEnabled,
|
|
|
|
|
onClick = {
|
|
|
|
|
launchCallRingtoneSelectionIntent()
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
switchPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.preferences__vibrate),
|
|
|
|
|
isChecked = state.callNotificationsState.vibrateEnabled,
|
|
|
|
|
isEnabled = state.callNotificationsState.notificationsEnabled,
|
|
|
|
|
onClick = {
|
|
|
|
|
viewModel.setCallVibrateEnabled(!state.callNotificationsState.vibrateEnabled)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
dividerPref()
|
|
|
|
|
|
|
|
|
|
sectionHeaderPref(R.string.NotificationsSettingsFragment__notification_profiles)
|
|
|
|
|
|
|
|
|
|
clickPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.NotificationsSettingsFragment__profiles),
|
|
|
|
|
summary = DSLSettingsText.from(R.string.NotificationsSettingsFragment__create_a_profile_to_receive_notifications_only_from_people_and_groups_you_choose),
|
|
|
|
|
onClick = {
|
|
|
|
|
findNavController().safeNavigate(R.id.action_notificationsSettingsFragment_to_notificationProfilesFragment)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
dividerPref()
|
|
|
|
|
|
|
|
|
|
sectionHeaderPref(R.string.NotificationsSettingsFragment__notify_when)
|
|
|
|
|
|
|
|
|
|
switchPref(
|
|
|
|
|
title = DSLSettingsText.from(R.string.NotificationsSettingsFragment__contact_joins_signal),
|
|
|
|
|
isChecked = state.notifyWhenContactJoinsSignal,
|
|
|
|
|
onClick = {
|
|
|
|
|
viewModel.setNotifyWhenContactJoinsSignal(!state.notifyWhenContactJoinsSignal)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun getRingtoneSummary(uri: Uri): String {
|
|
|
|
|
return if (TextUtils.isEmpty(uri.toString())) {
|
|
|
|
|
getString(R.string.preferences__silent)
|
|
|
|
|
override fun getRingtoneSummary(uri: Uri): String {
|
|
|
|
|
return if (uri.toString().isBlank()) {
|
|
|
|
|
activity.getString(R.string.preferences__silent)
|
|
|
|
|
} else {
|
|
|
|
|
val tone: Ringtone? = RingtoneUtil.getRingtone(requireContext(), uri)
|
|
|
|
|
val tone: Ringtone? = RingtoneUtil.getRingtone(activity, uri)
|
|
|
|
|
if (tone != null) {
|
|
|
|
|
try {
|
|
|
|
|
tone.getTitle(requireContext()) ?: getString(R.string.NotificationsSettingsFragment__unknown_ringtone)
|
|
|
|
|
tone.getTitle(activity) ?: activity.getString(R.string.NotificationsSettingsFragment__unknown_ringtone)
|
|
|
|
|
} catch (e: SecurityException) {
|
|
|
|
|
Log.w(TAG, "Unable to get title for ringtone", e)
|
|
|
|
|
return getString(R.string.NotificationsSettingsFragment__unknown_ringtone)
|
|
|
|
|
return activity.getString(R.string.NotificationsSettingsFragment__unknown_ringtone)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
getString(R.string.preferences__default)
|
|
|
|
|
activity.getString(R.string.preferences__default)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun launchMessageSoundSelectionIntent() {
|
|
|
|
|
val current = SignalStore.settings.messageNotificationSound
|
|
|
|
|
|
|
|
|
|
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
|
|
|
|
|
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
|
|
|
|
|
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true)
|
|
|
|
|
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
|
|
|
|
|
intent.putExtra(
|
|
|
|
|
RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
|
|
|
|
|
Settings.System.DEFAULT_NOTIFICATION_URI
|
|
|
|
|
)
|
|
|
|
|
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, current)
|
|
|
|
|
|
|
|
|
|
openRingtonePicker(intent, MESSAGE_SOUND_SELECT)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@RequiresApi(26)
|
|
|
|
|
private fun launchNotificationPriorityIntent() {
|
|
|
|
|
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
|
|
|
|
intent.putExtra(
|
|
|
|
|
Settings.EXTRA_CHANNEL_ID,
|
|
|
|
|
NotificationChannels.getInstance().messagesChannel
|
|
|
|
|
)
|
|
|
|
|
intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
|
|
|
|
|
startActivity(intent)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun launchCallRingtoneSelectionIntent() {
|
|
|
|
|
val current = SignalStore.settings.callRingtone
|
|
|
|
|
|
|
|
|
|
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
|
|
|
|
|
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
|
|
|
|
|
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true)
|
|
|
|
|
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE)
|
|
|
|
|
intent.putExtra(
|
|
|
|
|
RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
|
|
|
|
|
Settings.System.DEFAULT_RINGTONE_URI
|
|
|
|
|
)
|
|
|
|
|
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, current)
|
|
|
|
|
|
|
|
|
|
openRingtonePicker(intent, CALL_RINGTONE_SELECT)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Suppress("DEPRECATION")
|
|
|
|
|
private fun openRingtonePicker(intent: Intent, requestCode: Int) {
|
|
|
|
|
override fun launchMessageSoundSelectionIntent() {
|
|
|
|
|
try {
|
|
|
|
|
startActivityForResult(intent, requestCode)
|
|
|
|
|
messageSoundSelectionLauncher.launch(Unit)
|
|
|
|
|
} catch (e: ActivityNotFoundException) {
|
|
|
|
|
Toast.makeText(requireContext(), R.string.NotificationSettingsFragment__failed_to_open_picker, Toast.LENGTH_LONG).show()
|
|
|
|
|
Toast.makeText(activity, R.string.NotificationSettingsFragment__failed_to_open_picker, Toast.LENGTH_LONG).show()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class LedColorPreference(
|
|
|
|
|
val colorValues: Array<String>,
|
|
|
|
|
val radioListPreference: RadioListPreference
|
|
|
|
|
) : PreferenceModel<LedColorPreference>(
|
|
|
|
|
title = radioListPreference.title,
|
|
|
|
|
icon = radioListPreference.icon,
|
|
|
|
|
summary = radioListPreference.summary
|
|
|
|
|
override fun launchCallsSoundSelectionIntent() {
|
|
|
|
|
try {
|
|
|
|
|
callsSoundSelectionLauncher.launch(Unit)
|
|
|
|
|
} catch (e: ActivityNotFoundException) {
|
|
|
|
|
Toast.makeText(activity, R.string.NotificationSettingsFragment__failed_to_open_picker, Toast.LENGTH_LONG).show()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun setMessageNotificationVibration(enabled: Boolean) {
|
|
|
|
|
viewModel.setMessageNotificationsEnabled(enabled)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun setMessasgeNotificationLedColor(selection: String) {
|
|
|
|
|
viewModel.setMessageNotificationLedColor(selection)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun setMessasgeNotificationLedBlink(selection: String) {
|
|
|
|
|
viewModel.setMessageNotificationLedBlink(selection)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun setMessageNotificationInChatSoundsEnabled(enabled: Boolean) {
|
|
|
|
|
viewModel.setMessageNotificationInChatSoundsEnabled(enabled)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun setMessageRepeatAlerts(selection: String) {
|
|
|
|
|
viewModel.setMessageRepeatAlerts(selection.toInt())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun setMessageNotificationPrivacy(selection: String) {
|
|
|
|
|
viewModel.setMessageNotificationPrivacy(selection)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@RequiresApi(23)
|
|
|
|
|
override fun onTroubleshootNotificationsClick() {
|
|
|
|
|
PromptBatterySaverDialogFragment.show(activity.supportFragmentManager)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun launchNotificationPriorityIntent() {
|
|
|
|
|
notificationPrioritySelectionLauncher.launch(Unit)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun setMessageNotificationPriority(selection: String) {
|
|
|
|
|
viewModel.setMessageNotificationPriority(selection.toInt())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun setCallNotificationsEnabled(enabled: Boolean) {
|
|
|
|
|
viewModel.setCallNotificationsEnabled(enabled)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun setCallVibrateEnabled(enabled: Boolean) {
|
|
|
|
|
viewModel.setCallVibrateEnabled(enabled)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onNavigationProfilesClick() {
|
|
|
|
|
appSettingsRouter.navigateTo(AppSettingsRoute.NotificationsRoute.NotificationProfiles)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun setNotifyWhenContactJoinsSignal(enabled: Boolean) {
|
|
|
|
|
viewModel.setNotifyWhenContactJoinsSignal(enabled)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface NotificationsSettingsCallbacks {
|
|
|
|
|
fun onTurnOnNotificationsActionClick() = Unit
|
|
|
|
|
fun onNavigationClick() = Unit
|
|
|
|
|
fun setMessageNotificationsEnabled(enabled: Boolean) = Unit
|
|
|
|
|
fun onCustomizeClick() = Unit
|
|
|
|
|
fun getRingtoneSummary(uri: Uri): String = "Test Sound"
|
|
|
|
|
fun launchMessageSoundSelectionIntent(): Unit = Unit
|
|
|
|
|
fun launchCallsSoundSelectionIntent(): Unit = Unit
|
|
|
|
|
fun setMessageNotificationVibration(enabled: Boolean) = Unit
|
|
|
|
|
fun setMessasgeNotificationLedColor(selection: String) = Unit
|
|
|
|
|
fun setMessasgeNotificationLedBlink(selection: String) = Unit
|
|
|
|
|
fun setMessageNotificationInChatSoundsEnabled(enabled: Boolean) = Unit
|
|
|
|
|
fun setMessageRepeatAlerts(selection: String) = Unit
|
|
|
|
|
fun setMessageNotificationPrivacy(selection: String) = Unit
|
|
|
|
|
fun onTroubleshootNotificationsClick() = Unit
|
|
|
|
|
fun launchNotificationPriorityIntent() = Unit
|
|
|
|
|
fun setMessageNotificationPriority(selection: String) = Unit
|
|
|
|
|
fun setCallNotificationsEnabled(enabled: Boolean) = Unit
|
|
|
|
|
fun setCallVibrateEnabled(enabled: Boolean) = Unit
|
|
|
|
|
fun onNavigationProfilesClick() = Unit
|
|
|
|
|
fun setNotifyWhenContactJoinsSignal(enabled: Boolean) = Unit
|
|
|
|
|
|
|
|
|
|
object Empty : NotificationsSettingsCallbacks
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
fun NotificationsSettingsScreen(
|
|
|
|
|
state: NotificationsSettingsState,
|
|
|
|
|
callbacks: NotificationsSettingsCallbacks,
|
|
|
|
|
deviceState: DeviceState = remember { DeviceState() }
|
|
|
|
|
) {
|
|
|
|
|
Scaffolds.Settings(
|
|
|
|
|
title = stringResource(R.string.preferences__notifications),
|
|
|
|
|
onNavigationClick = callbacks::onNavigationClick,
|
|
|
|
|
navigationIcon = ImageVector.vectorResource(R.drawable.symbol_arrow_start_24)
|
|
|
|
|
) {
|
|
|
|
|
override fun areContentsTheSame(newItem: LedColorPreference): Boolean {
|
|
|
|
|
return super.areContentsTheSame(newItem) && radioListPreference.areContentsTheSame(newItem.radioListPreference)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
LazyColumn(
|
|
|
|
|
modifier = Modifier.padding(it)
|
|
|
|
|
) {
|
|
|
|
|
if (!state.messageNotificationsState.canEnableNotifications) {
|
|
|
|
|
item {
|
|
|
|
|
Banner(
|
|
|
|
|
text = stringResource(R.string.NotificationSettingsFragment__to_enable_notifications),
|
|
|
|
|
action = stringResource(R.string.NotificationSettingsFragment__turn_on),
|
|
|
|
|
onActionClick = callbacks::onTurnOnNotificationsActionClick
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class LedColorPreferenceViewHolder(itemView: View) :
|
|
|
|
|
PreferenceViewHolder<LedColorPreference>(itemView) {
|
|
|
|
|
item {
|
|
|
|
|
Texts.SectionHeader(stringResource(R.string.NotificationsSettingsFragment__messages))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val radioListPreferenceViewHolder = RadioListPreferenceViewHolder(itemView)
|
|
|
|
|
item {
|
|
|
|
|
Rows.ToggleRow(
|
|
|
|
|
text = stringResource(R.string.preferences__notifications),
|
|
|
|
|
enabled = state.messageNotificationsState.canEnableNotifications,
|
|
|
|
|
checked = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onCheckChanged = callbacks::setMessageNotificationsEnabled
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun bind(model: LedColorPreference) {
|
|
|
|
|
super.bind(model)
|
|
|
|
|
radioListPreferenceViewHolder.bind(model.radioListPreference)
|
|
|
|
|
|
|
|
|
|
summaryView.visibility = View.GONE
|
|
|
|
|
|
|
|
|
|
val circleDrawable = requireNotNull(ContextCompat.getDrawable(context, R.drawable.circle_tintable))
|
|
|
|
|
circleDrawable.setBounds(0, 0, ViewUtil.dpToPx(20), ViewUtil.dpToPx(20))
|
|
|
|
|
circleDrawable.colorFilter = model.colorValues[model.radioListPreference.selected].toColorFilter()
|
|
|
|
|
|
|
|
|
|
if (ViewUtil.isLtr(itemView)) {
|
|
|
|
|
titleView.setCompoundDrawables(null, null, circleDrawable, null)
|
|
|
|
|
if (deviceState.apiLevel >= 30) {
|
|
|
|
|
item {
|
|
|
|
|
Rows.TextRow(
|
|
|
|
|
text = stringResource(R.string.preferences__customize),
|
|
|
|
|
label = stringResource(R.string.preferences__change_sound_and_vibration),
|
|
|
|
|
enabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onClick = callbacks::onCustomizeClick
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
titleView.setCompoundDrawables(circleDrawable, null, null, null)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
item {
|
|
|
|
|
Rows.TextRow(
|
|
|
|
|
text = stringResource(R.string.preferences__sound),
|
|
|
|
|
label = remember(state.messageNotificationsState.sound) {
|
|
|
|
|
callbacks.getRingtoneSummary(state.messageNotificationsState.sound)
|
|
|
|
|
},
|
|
|
|
|
enabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onClick = callbacks::launchMessageSoundSelectionIntent
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun String.toColorFilter(): ColorFilter {
|
|
|
|
|
val color = when (this) {
|
|
|
|
|
"green" -> ContextCompat.getColor(context, R.color.green_500)
|
|
|
|
|
"red" -> ContextCompat.getColor(context, R.color.red_500)
|
|
|
|
|
"blue" -> ContextCompat.getColor(context, R.color.blue_500)
|
|
|
|
|
"yellow" -> ContextCompat.getColor(context, R.color.yellow_500)
|
|
|
|
|
"cyan" -> ContextCompat.getColor(context, R.color.cyan_500)
|
|
|
|
|
"magenta" -> ContextCompat.getColor(context, R.color.pink_500)
|
|
|
|
|
"white" -> ContextCompat.getColor(context, R.color.white)
|
|
|
|
|
else -> ContextCompat.getColor(context, R.color.transparent)
|
|
|
|
|
item {
|
|
|
|
|
Rows.ToggleRow(
|
|
|
|
|
text = stringResource(R.string.preferences__vibrate),
|
|
|
|
|
checked = state.messageNotificationsState.vibrateEnabled,
|
|
|
|
|
enabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onCheckChanged = callbacks::setMessageNotificationsEnabled
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
item {
|
|
|
|
|
Rows.RadioListRow(
|
|
|
|
|
text = {
|
|
|
|
|
Box(
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
.clip(CircleShape)
|
|
|
|
|
.size(24.dp)
|
|
|
|
|
.background(color = getLedColor(state.messageNotificationsState.ledColor))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.size(10.dp))
|
|
|
|
|
|
|
|
|
|
Text(text = stringResource(R.string.preferences__led_color))
|
|
|
|
|
},
|
|
|
|
|
dialogTitle = stringResource(R.string.preferences__led_color),
|
|
|
|
|
labels = stringArrayResource(R.array.pref_led_color_entries),
|
|
|
|
|
values = stringArrayResource(R.array.pref_led_color_values),
|
|
|
|
|
selectedValue = state.messageNotificationsState.ledColor,
|
|
|
|
|
enabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onSelected = callbacks::setMessasgeNotificationLedColor
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!deviceState.supportsNotificationChannels) {
|
|
|
|
|
item {
|
|
|
|
|
Rows.RadioListRow(
|
|
|
|
|
text = stringResource(R.string.preferences__pref_led_blink_title),
|
|
|
|
|
labels = stringArrayResource(R.array.pref_led_blink_pattern_entries),
|
|
|
|
|
values = stringArrayResource(R.array.pref_led_blink_pattern_values),
|
|
|
|
|
selectedValue = state.messageNotificationsState.ledBlink,
|
|
|
|
|
enabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onSelected = callbacks::setMessasgeNotificationLedBlink
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
|
|
|
|
|
item {
|
|
|
|
|
Rows.ToggleRow(
|
|
|
|
|
text = stringResource(R.string.preferences_notifications__in_chat_sounds),
|
|
|
|
|
checked = state.messageNotificationsState.inChatSoundsEnabled,
|
|
|
|
|
enabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onCheckChanged = callbacks::setMessageNotificationInChatSoundsEnabled
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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(
|
|
|
|
|
text = stringResource(R.string.preferences_notifications__troubleshoot),
|
|
|
|
|
onClick = callbacks::onTroubleshootNotificationsClick
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (deviceState.apiLevel < 30) {
|
|
|
|
|
if (deviceState.supportsNotificationChannels) {
|
|
|
|
|
item {
|
|
|
|
|
Rows.TextRow(
|
|
|
|
|
text = stringResource(R.string.preferences_notifications__priority),
|
|
|
|
|
enabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onClick = callbacks::launchNotificationPriorityIntent
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
item {
|
|
|
|
|
Rows.RadioListRow(
|
|
|
|
|
text = stringResource(R.string.preferences_notifications__priority),
|
|
|
|
|
labels = stringArrayResource(R.array.pref_notification_priority_entries),
|
|
|
|
|
values = stringArrayResource(R.array.pref_notification_priority_values),
|
|
|
|
|
selectedValue = state.messageNotificationsState.priority.toString(),
|
|
|
|
|
enabled = state.messageNotificationsState.notificationsEnabled,
|
|
|
|
|
onSelected = callbacks::setMessageNotificationPriority
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
item {
|
|
|
|
|
Dividers.Default()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
item {
|
|
|
|
|
Texts.SectionHeader(stringResource(R.string.NotificationsSettingsFragment__calls))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
item {
|
|
|
|
|
Rows.ToggleRow(
|
|
|
|
|
text = stringResource(R.string.preferences__notifications),
|
|
|
|
|
enabled = state.callNotificationsState.canEnableNotifications,
|
|
|
|
|
checked = state.callNotificationsState.notificationsEnabled,
|
|
|
|
|
onCheckChanged = callbacks::setCallNotificationsEnabled
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
item {
|
|
|
|
|
val ringtoneSummary = remember(state.callNotificationsState.ringtone) {
|
|
|
|
|
callbacks.getRingtoneSummary(state.callNotificationsState.ringtone)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Rows.TextRow(
|
|
|
|
|
text = stringResource(R.string.preferences_notifications__ringtone),
|
|
|
|
|
label = ringtoneSummary,
|
|
|
|
|
enabled = state.callNotificationsState.notificationsEnabled,
|
|
|
|
|
onClick = callbacks::launchCallsSoundSelectionIntent
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
item {
|
|
|
|
|
Rows.ToggleRow(
|
|
|
|
|
text = stringResource(R.string.preferences__vibrate),
|
|
|
|
|
checked = state.callNotificationsState.vibrateEnabled,
|
|
|
|
|
enabled = state.callNotificationsState.notificationsEnabled,
|
|
|
|
|
onCheckChanged = callbacks::setCallVibrateEnabled
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
item {
|
|
|
|
|
Dividers.Default()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
item {
|
|
|
|
|
Texts.SectionHeader(stringResource(R.string.NotificationsSettingsFragment__notification_profiles))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
item {
|
|
|
|
|
Rows.TextRow(
|
|
|
|
|
text = stringResource(R.string.NotificationsSettingsFragment__profiles),
|
|
|
|
|
label = stringResource(R.string.NotificationsSettingsFragment__create_a_profile_to_receive_notifications_only_from_people_and_groups_you_choose),
|
|
|
|
|
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 getLedColor(ledColorString: String): Color {
|
|
|
|
|
return when (ledColorString) {
|
|
|
|
|
"green" -> colorResource(R.color.green_500)
|
|
|
|
|
"red" -> colorResource(R.color.red_500)
|
|
|
|
|
"blue" -> colorResource(R.color.blue_500)
|
|
|
|
|
"yellow" -> colorResource(R.color.yellow_500)
|
|
|
|
|
"cyan" -> colorResource(R.color.cyan_500)
|
|
|
|
|
"magenta" -> colorResource(R.color.pink_500)
|
|
|
|
|
"white" -> colorResource(R.color.white)
|
|
|
|
|
else -> colorResource(R.color.transparent)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@SignalPreview
|
|
|
|
|
@Composable
|
|
|
|
|
private fun NotificationsSettingsScreenPreview() {
|
|
|
|
|
Previews.Preview {
|
|
|
|
|
NotificationsSettingsScreen(
|
|
|
|
|
deviceState = rememberTestDeviceState(),
|
|
|
|
|
state = rememberTestState(),
|
|
|
|
|
callbacks = NotificationsSettingsCallbacks.Empty
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@SignalPreview
|
|
|
|
|
@Composable
|
|
|
|
|
private fun NotificationsSettingsScreenAPI21Preview() {
|
|
|
|
|
Previews.Preview {
|
|
|
|
|
NotificationsSettingsScreen(
|
|
|
|
|
deviceState = rememberTestDeviceState(apiLevel = 21, supportsNotificationChannels = false),
|
|
|
|
|
state = rememberTestState(),
|
|
|
|
|
callbacks = NotificationsSettingsCallbacks.Empty
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
private fun rememberTestDeviceState(
|
|
|
|
|
apiLevel: Int = 35,
|
|
|
|
|
supportsNotificationChannels: Boolean = true
|
|
|
|
|
): DeviceState = remember {
|
|
|
|
|
DeviceState(
|
|
|
|
|
apiLevel = apiLevel,
|
|
|
|
|
supportsNotificationChannels = supportsNotificationChannels
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
private fun rememberTestState(): NotificationsSettingsState = remember {
|
|
|
|
|
NotificationsSettingsState(
|
|
|
|
|
messageNotificationsState = MessageNotificationsState(
|
|
|
|
|
notificationsEnabled = true,
|
|
|
|
|
canEnableNotifications = true,
|
|
|
|
|
sound = Uri.EMPTY,
|
|
|
|
|
vibrateEnabled = true,
|
|
|
|
|
ledColor = "blue",
|
|
|
|
|
ledBlink = "",
|
|
|
|
|
inChatSoundsEnabled = true,
|
|
|
|
|
repeatAlerts = 1,
|
|
|
|
|
messagePrivacy = "",
|
|
|
|
|
priority = 1,
|
|
|
|
|
troubleshootNotifications = true
|
|
|
|
|
),
|
|
|
|
|
callNotificationsState = CallNotificationsState(
|
|
|
|
|
notificationsEnabled = true,
|
|
|
|
|
canEnableNotifications = true,
|
|
|
|
|
ringtone = Uri.EMPTY,
|
|
|
|
|
vibrateEnabled = true
|
|
|
|
|
),
|
|
|
|
|
notifyWhenContactJoinsSignal = true
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data class DeviceState(
|
|
|
|
|
val apiLevel: Int = Build.VERSION.SDK_INT,
|
|
|
|
|
val supportsNotificationChannels: Boolean = NotificationChannels.supported()
|
|
|
|
|
)
|
|
|
|
|
|