mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-20 11:08:31 +00:00
Convert NotificationsSettingsFragment to compose.
This commit is contained in:
committed by
Greyson Parrelli
parent
7af811eb3f
commit
2eb4f650d8
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.components.settings.app.notifications
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
|
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity result contract for launching the system notification channel settings screen
|
||||||
|
* for the messages notification channel.
|
||||||
|
*
|
||||||
|
* This contract allows users to configure notification priority, sound, vibration, and other
|
||||||
|
* channel-specific settings through the system's native notification settings UI.
|
||||||
|
*/
|
||||||
|
class NotificationPrioritySelectionContract : ActivityResultContract<Unit, Unit>() {
|
||||||
|
override fun createIntent(context: Context, input: Unit): Intent {
|
||||||
|
return Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
||||||
|
.putExtra(
|
||||||
|
Settings.EXTRA_CHANNEL_ID,
|
||||||
|
NotificationChannels.getInstance().messagesChannel
|
||||||
|
)
|
||||||
|
.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun parseResult(resultCode: Int, intent: Intent?) = Unit
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.components.settings.app.notifications
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.media.RingtoneManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
|
import org.signal.core.util.getParcelableExtraCompat
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity result contract for launching the system ringtone picker to select notification sounds.
|
||||||
|
*
|
||||||
|
* Supports selecting sounds for both message notifications and call ringtones through the
|
||||||
|
* Android system's ringtone picker interface.
|
||||||
|
*
|
||||||
|
* @param target Specifies whether to configure sounds for messages or calls
|
||||||
|
*/
|
||||||
|
class NotificationSoundSelectionContract(
|
||||||
|
private val target: Target
|
||||||
|
) : ActivityResultContract<Unit, Uri?>() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the type of notification sound to configure.
|
||||||
|
*/
|
||||||
|
enum class Target {
|
||||||
|
/** Message notification sounds */
|
||||||
|
MESSAGE,
|
||||||
|
|
||||||
|
/** Call ringtones */
|
||||||
|
CALL
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createIntent(context: Context, input: Unit): Intent {
|
||||||
|
return when (target) {
|
||||||
|
Target.MESSAGE -> createIntentForMessageSoundSelection()
|
||||||
|
Target.CALL -> createIntentForCallSoundSelection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createIntentForMessageSoundSelection(): Intent {
|
||||||
|
val current = SignalStore.settings.messageNotificationSound
|
||||||
|
|
||||||
|
return Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
|
||||||
|
.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
|
||||||
|
.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true)
|
||||||
|
.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
|
||||||
|
.putExtra(
|
||||||
|
RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
|
||||||
|
Settings.System.DEFAULT_NOTIFICATION_URI
|
||||||
|
)
|
||||||
|
.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, current)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createIntentForCallSoundSelection(): Intent {
|
||||||
|
val current = SignalStore.settings.callRingtone
|
||||||
|
|
||||||
|
return Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
|
||||||
|
.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
|
||||||
|
.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true)
|
||||||
|
.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE)
|
||||||
|
.putExtra(
|
||||||
|
RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
|
||||||
|
Settings.System.DEFAULT_RINGTONE_URI
|
||||||
|
)
|
||||||
|
.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, current)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
|
||||||
|
return intent?.getParcelableExtraCompat(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,420 +1,615 @@
|
|||||||
package org.thoughtcrime.securesms.components.settings.app.notifications
|
package org.thoughtcrime.securesms.components.settings.app.notifications
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.ActivityNotFoundException
|
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.Ringtone
|
||||||
import android.media.RingtoneManager
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.Settings
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
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.annotation.RequiresApi
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.compose.foundation.background
|
||||||
import androidx.lifecycle.ViewModelProvider
|
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.navigation.fragment.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
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.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.PromptBatterySaverDialogFragment
|
import org.thoughtcrime.securesms.components.PromptBatterySaverDialogFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.app.routes.AppSettingsRoute
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.app.routes.AppSettingsRouter
|
||||||
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.models.Banner
|
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.NotificationChannels
|
||||||
import org.thoughtcrime.securesms.notifications.TurnOnNotificationsBottomSheet
|
import org.thoughtcrime.securesms.notifications.TurnOnNotificationsBottomSheet
|
||||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||||
import org.thoughtcrime.securesms.util.RingtoneUtil
|
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.navigation.safeNavigate
|
||||||
|
import org.thoughtcrime.securesms.util.viewModel
|
||||||
|
|
||||||
private const val MESSAGE_SOUND_SELECT: Int = 1
|
class NotificationsSettingsFragment : ComposeFragment() {
|
||||||
private const val CALL_RINGTONE_SELECT: Int = 2
|
|
||||||
private val TAG = Log.tag(NotificationsSettingsFragment::class.java)
|
|
||||||
|
|
||||||
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) }
|
NotificationsSettingsViewModel.Factory(sharedPreferences).create(NotificationsSettingsViewModel::class.java)
|
||||||
private val repeatAlertsLabels by lazy { resources.getStringArray(R.array.pref_repeat_alerts_entries) }
|
}
|
||||||
|
|
||||||
private val notificationPrivacyValues by lazy { resources.getStringArray(R.array.pref_notification_privacy_values) }
|
private val appSettingsRouter: AppSettingsRouter by viewModel {
|
||||||
private val notificationPrivacyLabels by lazy { resources.getStringArray(R.array.pref_notification_privacy_entries) }
|
AppSettingsRouter()
|
||||||
|
}
|
||||||
|
|
||||||
private val notificationPriorityValues by lazy { resources.getStringArray(R.array.pref_notification_priority_values) }
|
private lateinit var callbacks: DefaultNotificationsSettingsCallbacks
|
||||||
private val notificationPriorityLabels by lazy { resources.getStringArray(R.array.pref_notification_priority_entries) }
|
|
||||||
|
|
||||||
private val ledColorValues by lazy { resources.getStringArray(R.array.pref_led_color_values) }
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
private val ledColorLabels by lazy { resources.getStringArray(R.array.pref_led_color_entries) }
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
private val ledBlinkValues by lazy { resources.getStringArray(R.array.pref_led_blink_pattern_values) }
|
callbacks = DefaultNotificationsSettingsCallbacks(requireActivity(), viewModel, appSettingsRouter, DefaultNotificationsSettingsCallbacks.ActivityResultRegisterer.ForFragment(this))
|
||||||
private val ledBlinkLabels by lazy { resources.getStringArray(R.array.pref_led_blink_pattern_entries) }
|
|
||||||
|
|
||||||
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() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
viewModel.refresh()
|
viewModel.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
@Composable
|
||||||
if (requestCode == MESSAGE_SOUND_SELECT && resultCode == Activity.RESULT_OK && data != null) {
|
override fun FragmentContent() {
|
||||||
val uri: Uri? = data.getParcelableExtraCompat(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java)
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
viewModel.setMessageNotificationsSound(uri)
|
|
||||||
} else if (requestCode == CALL_RINGTONE_SELECT && resultCode == Activity.RESULT_OK && data != null) {
|
NotificationsSettingsScreen(state = state, callbacks = callbacks)
|
||||||
val uri: Uri? = data.getParcelableExtraCompat(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java)
|
}
|
||||||
viewModel.setCallRingtone(uri)
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
private val messageSoundSelectionLauncher: ActivityResultLauncher<Unit> = activityResultRegisterer.registerForActivityResult(
|
||||||
adapter.registerFactory(
|
NotificationSoundSelectionContract(NotificationSoundSelectionContract.Target.MESSAGE),
|
||||||
LedColorPreference::class.java,
|
viewModel::setMessageNotificationsSound
|
||||||
LayoutFactory(::LedColorPreferenceViewHolder, R.layout.dsl_preference_item)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
Banner.register(adapter)
|
private val callsSoundSelectionLauncher: ActivityResultLauncher<Unit> = activityResultRegisterer.registerForActivityResult(
|
||||||
|
NotificationSoundSelectionContract(NotificationSoundSelectionContract.Target.CALL),
|
||||||
|
viewModel::setCallRingtone
|
||||||
|
)
|
||||||
|
|
||||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
private val notificationPrioritySelectionLauncher: ActivityResultLauncher<Unit> = activityResultRegisterer.registerForActivityResult(
|
||||||
val factory = NotificationsSettingsViewModel.Factory(sharedPreferences)
|
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) {
|
override fun onNavigationClick() {
|
||||||
adapter.submitList(getConfiguration(it).toMappingModelList())
|
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 {
|
override fun getRingtoneSummary(uri: Uri): String {
|
||||||
return configure {
|
return if (uri.toString().isBlank()) {
|
||||||
if (!state.messageNotificationsState.canEnableNotifications) {
|
activity.getString(R.string.preferences__silent)
|
||||||
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)
|
|
||||||
} else {
|
} else {
|
||||||
val tone: Ringtone? = RingtoneUtil.getRingtone(requireContext(), uri)
|
val tone: Ringtone? = RingtoneUtil.getRingtone(activity, uri)
|
||||||
if (tone != null) {
|
if (tone != null) {
|
||||||
try {
|
try {
|
||||||
tone.getTitle(requireContext()) ?: getString(R.string.NotificationsSettingsFragment__unknown_ringtone)
|
tone.getTitle(activity) ?: activity.getString(R.string.NotificationsSettingsFragment__unknown_ringtone)
|
||||||
} catch (e: SecurityException) {
|
} catch (e: SecurityException) {
|
||||||
Log.w(TAG, "Unable to get title for ringtone", e)
|
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 {
|
} else {
|
||||||
getString(R.string.preferences__default)
|
activity.getString(R.string.preferences__default)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchMessageSoundSelectionIntent() {
|
override 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) {
|
|
||||||
try {
|
try {
|
||||||
startActivityForResult(intent, requestCode)
|
messageSoundSelectionLauncher.launch(Unit)
|
||||||
} catch (e: ActivityNotFoundException) {
|
} 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(
|
override fun launchCallsSoundSelectionIntent() {
|
||||||
val colorValues: Array<String>,
|
try {
|
||||||
val radioListPreference: RadioListPreference
|
callsSoundSelectionLauncher.launch(Unit)
|
||||||
) : PreferenceModel<LedColorPreference>(
|
} catch (e: ActivityNotFoundException) {
|
||||||
title = radioListPreference.title,
|
Toast.makeText(activity, R.string.NotificationSettingsFragment__failed_to_open_picker, Toast.LENGTH_LONG).show()
|
||||||
icon = radioListPreference.icon,
|
}
|
||||||
summary = radioListPreference.summary
|
}
|
||||||
|
|
||||||
|
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 {
|
LazyColumn(
|
||||||
return super.areContentsTheSame(newItem) && radioListPreference.areContentsTheSame(newItem.radioListPreference)
|
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) :
|
item {
|
||||||
PreferenceViewHolder<LedColorPreference>(itemView) {
|
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) {
|
if (deviceState.apiLevel >= 30) {
|
||||||
super.bind(model)
|
item {
|
||||||
radioListPreferenceViewHolder.bind(model.radioListPreference)
|
Rows.TextRow(
|
||||||
|
text = stringResource(R.string.preferences__customize),
|
||||||
summaryView.visibility = View.GONE
|
label = stringResource(R.string.preferences__change_sound_and_vibration),
|
||||||
|
enabled = state.messageNotificationsState.notificationsEnabled,
|
||||||
val circleDrawable = requireNotNull(ContextCompat.getDrawable(context, R.drawable.circle_tintable))
|
onClick = callbacks::onCustomizeClick
|
||||||
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)
|
|
||||||
} else {
|
} 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 {
|
item {
|
||||||
val color = when (this) {
|
Rows.ToggleRow(
|
||||||
"green" -> ContextCompat.getColor(context, R.color.green_500)
|
text = stringResource(R.string.preferences__vibrate),
|
||||||
"red" -> ContextCompat.getColor(context, R.color.red_500)
|
checked = state.messageNotificationsState.vibrateEnabled,
|
||||||
"blue" -> ContextCompat.getColor(context, R.color.blue_500)
|
enabled = state.messageNotificationsState.notificationsEnabled,
|
||||||
"yellow" -> ContextCompat.getColor(context, R.color.yellow_500)
|
onCheckChanged = callbacks::setMessageNotificationsEnabled
|
||||||
"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.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()
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package org.thoughtcrime.securesms.components.settings.app.notifications
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.notifications.DeviceSpecificNotificationConfig
|
import org.thoughtcrime.securesms.notifications.DeviceSpecificNotificationConfig
|
||||||
@@ -13,13 +15,12 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels
|
|||||||
import org.thoughtcrime.securesms.notifications.SlowNotificationHeuristics
|
import org.thoughtcrime.securesms.notifications.SlowNotificationHeuristics
|
||||||
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference
|
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.thoughtcrime.securesms.util.livedata.Store
|
|
||||||
|
|
||||||
class NotificationsSettingsViewModel(private val sharedPreferences: SharedPreferences) : ViewModel() {
|
class NotificationsSettingsViewModel(private val sharedPreferences: SharedPreferences) : ViewModel() {
|
||||||
|
|
||||||
private val store = Store(getState())
|
private val store = MutableStateFlow(getState())
|
||||||
|
|
||||||
val state: LiveData<NotificationsSettingsState> = store.stateLiveData
|
val state: StateFlow<NotificationsSettingsState> = store
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (NotificationChannels.supported()) {
|
if (NotificationChannels.supported()) {
|
||||||
|
|||||||
@@ -6,6 +6,24 @@
|
|||||||
package org.thoughtcrime.securesms.components.settings.models
|
package org.thoughtcrime.securesms.components.settings.models
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedCard
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.colorResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.signal.core.ui.compose.Previews
|
||||||
|
import org.signal.core.ui.compose.SignalPreview
|
||||||
|
import org.signal.core.ui.compose.horizontalGutters
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.databinding.DslBannerBinding
|
import org.thoughtcrime.securesms.databinding.DslBannerBinding
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.BindingFactory
|
import org.thoughtcrime.securesms.util.adapter.mapping.BindingFactory
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.BindingViewHolder
|
import org.thoughtcrime.securesms.util.adapter.mapping.BindingViewHolder
|
||||||
@@ -42,3 +60,53 @@ object Banner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replicates the Banner DSL preference for use in compose components.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun Banner(
|
||||||
|
text: String,
|
||||||
|
action: String,
|
||||||
|
onActionClick: () -> Unit
|
||||||
|
) {
|
||||||
|
OutlinedCard(
|
||||||
|
shape = RoundedCornerShape(18.dp),
|
||||||
|
border = BorderStroke(width = 1.dp, color = colorResource(R.color.signal_colorOutline_38)),
|
||||||
|
modifier = Modifier
|
||||||
|
.horizontalGutters()
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.57.dp)
|
||||||
|
.padding(top = 16.dp, bottom = 10.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
onClick = onActionClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.End)
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
) {
|
||||||
|
Text(text = action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SignalPreview
|
||||||
|
@Composable
|
||||||
|
private fun BannerPreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
Banner(
|
||||||
|
text = "Banner text will go here and probably be about something important",
|
||||||
|
action = "Action",
|
||||||
|
onActionClick = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:layout_marginStart="24dp"
|
android:layout_marginStart="24dp"
|
||||||
android:layout_marginEnd="24dp"
|
android:layout_marginEnd="24dp"
|
||||||
|
app:cardBackgroundColor="@color/signal_colorBackground"
|
||||||
app:cardCornerRadius="18dp"
|
app:cardCornerRadius="18dp"
|
||||||
app:cardElevation="0dp"
|
app:cardElevation="0dp"
|
||||||
app:strokeColor="@color/signal_colorOutline_38"
|
app:strokeColor="@color/signal_colorOutline_38"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
@@ -44,6 +45,8 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalWindowInfo
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -372,7 +375,7 @@ object Dialogs {
|
|||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(vertical = 100.dp)
|
.heightIn(min = 0.dp, max = getScreenHeight() - 200.dp)
|
||||||
.background(
|
.background(
|
||||||
color = SignalTheme.colors.colorSurface2,
|
color = SignalTheme.colors.colorSurface2,
|
||||||
shape = AlertDialogDefaults.shape
|
shape = AlertDialogDefaults.shape
|
||||||
@@ -456,7 +459,7 @@ object Dialogs {
|
|||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(vertical = 100.dp)
|
.heightIn(min = 0.dp, max = getScreenHeight() - 200.dp)
|
||||||
.background(
|
.background(
|
||||||
color = SignalTheme.colors.colorSurface2,
|
color = SignalTheme.colors.colorSurface2,
|
||||||
shape = AlertDialogDefaults.shape
|
shape = AlertDialogDefaults.shape
|
||||||
@@ -587,6 +590,13 @@ object Dialogs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getScreenHeight(): Dp {
|
||||||
|
return with(LocalDensity.current) {
|
||||||
|
LocalWindowInfo.current.containerSize.height.toDp()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SignalPreview
|
@SignalPreview
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import androidx.compose.ui.text.TextStyle
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import org.signal.core.ui.R
|
import org.signal.core.ui.R
|
||||||
import org.signal.core.ui.compose.Rows.TextAndLabel
|
import org.signal.core.ui.compose.Rows.TextAndLabel
|
||||||
|
import org.signal.core.ui.compose.Rows.TextRow
|
||||||
|
|
||||||
object Rows {
|
object Rows {
|
||||||
|
|
||||||
@@ -150,20 +151,47 @@ object Rows {
|
|||||||
labels: Array<String>,
|
labels: Array<String>,
|
||||||
values: Array<String>,
|
values: Array<String>,
|
||||||
selectedValue: String,
|
selectedValue: String,
|
||||||
onSelected: (String) -> Unit
|
onSelected: (String) -> Unit,
|
||||||
|
enabled: Boolean = true
|
||||||
|
) {
|
||||||
|
RadioListRow(
|
||||||
|
text = { selectedIndex ->
|
||||||
|
val selectedLabel = if (selectedIndex in labels.indices) {
|
||||||
|
labels[selectedIndex]
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
TextAndLabel(
|
||||||
|
text = text,
|
||||||
|
label = selectedLabel
|
||||||
|
)
|
||||||
|
},
|
||||||
|
dialogTitle = text,
|
||||||
|
labels = labels,
|
||||||
|
values = values,
|
||||||
|
selectedValue = selectedValue,
|
||||||
|
onSelected = onSelected,
|
||||||
|
enabled = enabled
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RadioListRow(
|
||||||
|
text: @Composable RowScope.(Int) -> Unit,
|
||||||
|
dialogTitle: String,
|
||||||
|
labels: Array<String>,
|
||||||
|
values: Array<String>,
|
||||||
|
selectedValue: String,
|
||||||
|
onSelected: (String) -> Unit,
|
||||||
|
enabled: Boolean = true
|
||||||
) {
|
) {
|
||||||
val selectedIndex = values.indexOf(selectedValue)
|
val selectedIndex = values.indexOf(selectedValue)
|
||||||
val selectedLabel = if (selectedIndex in labels.indices) {
|
|
||||||
labels[selectedIndex]
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
var displayDialog by remember { mutableStateOf(false) }
|
var displayDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
TextRow(
|
TextRow(
|
||||||
text = text,
|
text = { text(selectedIndex) },
|
||||||
label = selectedLabel,
|
enabled = enabled,
|
||||||
onClick = {
|
onClick = {
|
||||||
displayDialog = true
|
displayDialog = true
|
||||||
}
|
}
|
||||||
@@ -175,7 +203,7 @@ object Rows {
|
|||||||
labels = labels,
|
labels = labels,
|
||||||
values = values,
|
values = values,
|
||||||
selectedIndex = selectedIndex,
|
selectedIndex = selectedIndex,
|
||||||
title = text,
|
title = dialogTitle,
|
||||||
onSelected = {
|
onSelected = {
|
||||||
onSelected(values[it])
|
onSelected(values[it])
|
||||||
}
|
}
|
||||||
@@ -183,18 +211,6 @@ object Rows {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
multiSelectPref(
|
|
||||||
text = stringResource(R.string.preferences_chats__when_using_mobile_data),
|
|
||||||
listItems = autoDownloadLabels,
|
|
||||||
selected = autoDownloadValues.map { state.mobileAutoDownloadValues.contains(it) }.toBooleanArray(),
|
|
||||||
onSelected = {
|
|
||||||
val resultSet = it.mapIndexed { index, selected -> if (selected) autoDownloadValues[index] else null }.filterNotNull().toSet()
|
|
||||||
viewModel.setMobileAutoDownloadValues(resultSet)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MultiSelectRow(
|
fun MultiSelectRow(
|
||||||
text: String,
|
text: String,
|
||||||
|
|||||||
Reference in New Issue
Block a user