mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 13:08:46 +00:00
Convert AdvancedPrivacySettingsFragment to compose.
This commit is contained in:
committed by
Jeffrey Starke
parent
7d35cf1374
commit
d92286297f
@@ -4,59 +4,65 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.net.ConnectivityManager
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.foundation.text.appendInlineContent
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import androidx.compose.ui.text.Placeholder
|
||||
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withLink
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.preference.PreferenceManager
|
||||
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.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.SignalProgressDialog
|
||||
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.configure
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
|
||||
class AdvancedPrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__advanced) {
|
||||
/**
|
||||
* Displays advanced privacy controls such as call relaying and
|
||||
* censorship circumvention to the user.
|
||||
*/
|
||||
class AdvancedPrivacySettingsFragment : ComposeFragment() {
|
||||
|
||||
private lateinit var viewModel: AdvancedPrivacySettingsViewModel
|
||||
private val viewModel: AdvancedPrivacySettingsViewModel by viewModel {
|
||||
val repository = AdvancedPrivacySettingsRepository(requireContext())
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
AdvancedPrivacySettingsViewModel(
|
||||
preferences,
|
||||
repository
|
||||
)
|
||||
}
|
||||
|
||||
private var networkReceiver: NetworkReceiver? = null
|
||||
|
||||
private val sealedSenderSummary: CharSequence by lazy {
|
||||
SpanUtil.learnMore(
|
||||
requireContext(),
|
||||
ContextCompat.getColor(requireContext(), R.color.signal_text_primary)
|
||||
) {
|
||||
CommunicationActions.openBrowserLink(
|
||||
requireContext(),
|
||||
getString(R.string.AdvancedPrivacySettingsFragment__sealed_sender_link)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var progressDialog: SignalProgressDialog? = null
|
||||
|
||||
val statusIcon: CharSequence by lazy {
|
||||
val unidentifiedDeliveryIcon = requireNotNull(
|
||||
ContextCompat.getDrawable(
|
||||
requireContext(),
|
||||
R.drawable.ic_unidentified_delivery
|
||||
)
|
||||
)
|
||||
unidentifiedDeliveryIcon.setBounds(0, 0, ViewUtil.dpToPx(20), ViewUtil.dpToPx(15))
|
||||
val iconTint = ContextCompat.getColor(requireContext(), R.color.signal_text_primary_dialog)
|
||||
unidentifiedDeliveryIcon.colorFilter = PorterDuffColorFilter(iconTint, PorterDuff.Mode.SRC_IN)
|
||||
|
||||
SpanUtil.buildImageSpan(unidentifiedDeliveryIcon)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.refresh()
|
||||
@@ -68,96 +74,30 @@ class AdvancedPrivacySettingsFragment : DSLSettingsFragment(R.string.preferences
|
||||
unregisterNetworkReceiver()
|
||||
}
|
||||
|
||||
override fun bindAdapter(adapter: MappingAdapter) {
|
||||
val repository = AdvancedPrivacySettingsRepository(requireContext())
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val factory = AdvancedPrivacySettingsViewModel.Factory(preferences, repository)
|
||||
|
||||
viewModel = ViewModelProvider(this, factory)[AdvancedPrivacySettingsViewModel::class.java]
|
||||
|
||||
viewModel.state.observe(viewLifecycleOwner) {
|
||||
if (it.showProgressSpinner) {
|
||||
if (progressDialog?.isShowing == false) {
|
||||
progressDialog = SignalProgressDialog.show(requireContext(), null, null, true)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
viewModel.events.collect {
|
||||
if (it == AdvancedPrivacySettingsViewModel.Event.DISABLE_PUSH_FAILED) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.ApplicationPreferencesActivity_error_connecting_to_server,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
progressDialog?.hide()
|
||||
}
|
||||
|
||||
adapter.submitList(getConfiguration(it).toMappingModelList())
|
||||
}
|
||||
|
||||
viewModel.events.observe(viewLifecycleOwner) {
|
||||
if (it == AdvancedPrivacySettingsViewModel.Event.DISABLE_PUSH_FAILED) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.ApplicationPreferencesActivity_error_connecting_to_server,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConfiguration(state: AdvancedPrivacySettingsState): DSLConfiguration {
|
||||
return configure {
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences_advanced__always_relay_calls),
|
||||
summary = DSLSettingsText.from(R.string.preferences_advanced__relay_all_calls_through_the_signal_server_to_avoid_revealing_your_ip_address),
|
||||
isChecked = state.alwaysRelayCalls
|
||||
) {
|
||||
viewModel.setAlwaysRelayCalls(!state.alwaysRelayCalls)
|
||||
}
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(R.string.preferences_communication__category_censorship_circumvention)
|
||||
|
||||
val censorshipSummaryResId: Int = when (state.censorshipCircumventionState) {
|
||||
CensorshipCircumventionState.AVAILABLE -> R.string.preferences_communication__censorship_circumvention_if_enabled_signal_will_attempt_to_circumvent_censorship
|
||||
CensorshipCircumventionState.AVAILABLE_MANUALLY_DISABLED -> R.string.preferences_communication__censorship_circumvention_you_have_manually_disabled
|
||||
CensorshipCircumventionState.AVAILABLE_AUTOMATICALLY_ENABLED -> R.string.preferences_communication__censorship_circumvention_has_been_activated_based_on_your_accounts_phone_number
|
||||
CensorshipCircumventionState.UNAVAILABLE_CONNECTED -> R.string.preferences_communication__censorship_circumvention_is_not_necessary_you_are_already_connected
|
||||
CensorshipCircumventionState.UNAVAILABLE_NO_INTERNET -> R.string.preferences_communication__censorship_circumvention_can_only_be_activated_when_connected_to_the_internet
|
||||
}
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences_communication__censorship_circumvention),
|
||||
summary = DSLSettingsText.from(censorshipSummaryResId),
|
||||
isChecked = state.censorshipCircumventionEnabled,
|
||||
isEnabled = state.censorshipCircumventionState.available,
|
||||
onClick = {
|
||||
viewModel.setCensorshipCircumventionEnabled(!state.censorshipCircumventionEnabled)
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(R.string.preferences_communication__category_sealed_sender)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(
|
||||
SpannableStringBuilder(getString(R.string.AdvancedPrivacySettingsFragment__show_status_icon))
|
||||
.append(" ")
|
||||
.append(statusIcon)
|
||||
),
|
||||
summary = DSLSettingsText.from(R.string.AdvancedPrivacySettingsFragment__show_an_icon),
|
||||
isChecked = state.showSealedSenderStatusIcon
|
||||
) {
|
||||
viewModel.setShowStatusIconForSealedSender(!state.showSealedSenderStatusIcon)
|
||||
}
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences_communication__sealed_sender_allow_from_anyone),
|
||||
summary = DSLSettingsText.from(R.string.preferences_communication__sealed_sender_allow_from_anyone_description),
|
||||
isChecked = state.allowSealedSenderFromAnyone
|
||||
) {
|
||||
viewModel.setAllowSealedSenderFromAnyone(!state.allowSealedSenderFromAnyone)
|
||||
}
|
||||
|
||||
textPref(
|
||||
summary = DSLSettingsText.from(sealedSenderSummary)
|
||||
)
|
||||
}
|
||||
AdvancedPrivacySettingsScreen(
|
||||
state = state,
|
||||
callbacks = remember { Callbacks() }
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@@ -182,4 +122,183 @@ class AdvancedPrivacySettingsFragment : DSLSettingsFragment(R.string.preferences
|
||||
viewModel.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
private inner class Callbacks : AdvancedPrivacySettingsCallbacks {
|
||||
override fun onNavigationClick() {
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
override fun onAlwaysRelayCallsChanged(enabled: Boolean) {
|
||||
viewModel.setAlwaysRelayCalls(enabled)
|
||||
}
|
||||
|
||||
override fun onCensorshipCircumventionChanged(enabled: Boolean) {
|
||||
viewModel.setCensorshipCircumventionEnabled(enabled)
|
||||
}
|
||||
|
||||
override fun onShowStatusIconForSealedSenderChanged(enabled: Boolean) {
|
||||
viewModel.setShowStatusIconForSealedSender(enabled)
|
||||
}
|
||||
|
||||
override fun onAllowSealedSenderFromAnyoneChanged(enabled: Boolean) {
|
||||
viewModel.setAllowSealedSenderFromAnyone(enabled)
|
||||
}
|
||||
|
||||
override fun onSealedSenderLearnMoreClick() {
|
||||
CommunicationActions.openBrowserLink(
|
||||
requireContext(),
|
||||
getString(R.string.AdvancedPrivacySettingsFragment__sealed_sender_link)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private interface AdvancedPrivacySettingsCallbacks {
|
||||
|
||||
fun onNavigationClick() = Unit
|
||||
fun onAlwaysRelayCallsChanged(enabled: Boolean) = Unit
|
||||
fun onCensorshipCircumventionChanged(enabled: Boolean) = Unit
|
||||
fun onShowStatusIconForSealedSenderChanged(enabled: Boolean) = Unit
|
||||
fun onAllowSealedSenderFromAnyoneChanged(enabled: Boolean) = Unit
|
||||
fun onSealedSenderLearnMoreClick() = Unit
|
||||
|
||||
object Empty : AdvancedPrivacySettingsCallbacks
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AdvancedPrivacySettingsScreen(
|
||||
state: AdvancedPrivacySettingsState,
|
||||
callbacks: AdvancedPrivacySettingsCallbacks
|
||||
) {
|
||||
Scaffolds.Settings(
|
||||
title = stringResource(R.string.preferences__advanced),
|
||||
onNavigationClick = callbacks::onNavigationClick,
|
||||
navigationIcon = ImageVector.vectorResource(R.drawable.symbol_arrow_start_24)
|
||||
) { paddingValues ->
|
||||
LazyColumn(
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
) {
|
||||
item {
|
||||
Rows.ToggleRow(
|
||||
checked = state.alwaysRelayCalls,
|
||||
text = stringResource(R.string.preferences_advanced__always_relay_calls),
|
||||
label = stringResource(R.string.preferences_advanced__relay_all_calls_through_the_signal_server_to_avoid_revealing_your_ip_address),
|
||||
onCheckChanged = callbacks::onAlwaysRelayCallsChanged
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Dividers.Default()
|
||||
}
|
||||
|
||||
item {
|
||||
Texts.SectionHeader(text = stringResource(R.string.preferences_communication__category_censorship_circumvention))
|
||||
}
|
||||
|
||||
item {
|
||||
val censorshipSummaryResId: Int = when (state.censorshipCircumventionState) {
|
||||
CensorshipCircumventionState.AVAILABLE -> R.string.preferences_communication__censorship_circumvention_if_enabled_signal_will_attempt_to_circumvent_censorship
|
||||
CensorshipCircumventionState.AVAILABLE_MANUALLY_DISABLED -> R.string.preferences_communication__censorship_circumvention_you_have_manually_disabled
|
||||
CensorshipCircumventionState.AVAILABLE_AUTOMATICALLY_ENABLED -> R.string.preferences_communication__censorship_circumvention_has_been_activated_based_on_your_accounts_phone_number
|
||||
CensorshipCircumventionState.UNAVAILABLE_CONNECTED -> R.string.preferences_communication__censorship_circumvention_is_not_necessary_you_are_already_connected
|
||||
CensorshipCircumventionState.UNAVAILABLE_NO_INTERNET -> R.string.preferences_communication__censorship_circumvention_can_only_be_activated_when_connected_to_the_internet
|
||||
}
|
||||
|
||||
Rows.ToggleRow(
|
||||
text = stringResource(R.string.preferences_communication__censorship_circumvention),
|
||||
label = stringResource(censorshipSummaryResId),
|
||||
checked = state.censorshipCircumventionEnabled,
|
||||
enabled = state.censorshipCircumventionState.available,
|
||||
onCheckChanged = callbacks::onCensorshipCircumventionChanged
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Dividers.Default()
|
||||
}
|
||||
|
||||
item {
|
||||
Texts.SectionHeader(
|
||||
text = stringResource(R.string.preferences_communication__category_sealed_sender)
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
val imageId = "sealed-sender-image"
|
||||
val text = buildAnnotatedString {
|
||||
append(stringResource(R.string.AdvancedPrivacySettingsFragment__show_status_icon))
|
||||
append(" ")
|
||||
appendInlineContent(imageId, "[image]")
|
||||
}
|
||||
|
||||
val inlineContentMap = mapOf(
|
||||
imageId to InlineTextContent(
|
||||
placeholder = Placeholder(
|
||||
width = 20.sp,
|
||||
height = 15.sp,
|
||||
placeholderVerticalAlign = PlaceholderVerticalAlign.Center
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_unidentified_delivery),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Rows.ToggleRow(
|
||||
text = text,
|
||||
inlineContent = inlineContentMap,
|
||||
label = AnnotatedString(stringResource(R.string.AdvancedPrivacySettingsFragment__show_an_icon)),
|
||||
checked = state.showSealedSenderStatusIcon,
|
||||
onCheckChanged = callbacks::onShowStatusIconForSealedSenderChanged
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.ToggleRow(
|
||||
checked = state.allowSealedSenderFromAnyone,
|
||||
text = stringResource(R.string.preferences_communication__sealed_sender_allow_from_anyone),
|
||||
label = stringResource(R.string.preferences_communication__sealed_sender_allow_from_anyone_description),
|
||||
onCheckChanged = callbacks::onAllowSealedSenderFromAnyoneChanged
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
val sealedSenderSummary = buildAnnotatedString {
|
||||
withLink(
|
||||
LinkAnnotation.Clickable("learn-more", linkInteractionListener = {
|
||||
callbacks.onSealedSenderLearnMoreClick()
|
||||
})
|
||||
) {
|
||||
append(stringResource(R.string.LearnMoreTextView_learn_more))
|
||||
}
|
||||
}
|
||||
|
||||
Rows.TextRow(
|
||||
text = sealedSenderSummary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun AdvancedPrivacySettingsScreenPreview() {
|
||||
Previews.Preview {
|
||||
AdvancedPrivacySettingsScreen(
|
||||
state = AdvancedPrivacySettingsState(
|
||||
isPushEnabled = true,
|
||||
alwaysRelayCalls = false,
|
||||
censorshipCircumventionState = CensorshipCircumventionState.UNAVAILABLE_CONNECTED,
|
||||
censorshipCircumventionEnabled = false,
|
||||
showSealedSenderStatusIcon = false,
|
||||
allowSealedSenderFromAnyone = false,
|
||||
showProgressSpinner = false
|
||||
),
|
||||
callbacks = AdvancedPrivacySettingsCallbacks.Empty
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.privacy.advanced
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
|
||||
@@ -13,9 +16,7 @@ import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SettingsValues
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.SignalE164Util
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
|
||||
|
||||
class AdvancedPrivacySettingsViewModel(
|
||||
@@ -23,11 +24,11 @@ class AdvancedPrivacySettingsViewModel(
|
||||
private val repository: AdvancedPrivacySettingsRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val store = Store(getState())
|
||||
private val singleEvents = SingleLiveEvent<Event>()
|
||||
private val store = MutableStateFlow(getState())
|
||||
private val singleEvents = MutableSharedFlow<Event>()
|
||||
|
||||
val state: LiveData<AdvancedPrivacySettingsState> = store.stateLiveData
|
||||
val events: LiveData<Event> = singleEvents
|
||||
val state: StateFlow<AdvancedPrivacySettingsState> = store
|
||||
val events: SharedFlow<Event> = singleEvents
|
||||
val disposables: CompositeDisposable = CompositeDisposable()
|
||||
|
||||
init {
|
||||
@@ -136,20 +137,4 @@ class AdvancedPrivacySettingsViewModel(
|
||||
enum class Event {
|
||||
DISABLE_PUSH_FAILED
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
private val repository: AdvancedPrivacySettingsRepository
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return requireNotNull(
|
||||
modelClass.cast(
|
||||
AdvancedPrivacySettingsViewModel(
|
||||
sharedPreferences,
|
||||
repository
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -161,12 +162,44 @@ object Rows {
|
||||
enabled: Boolean = true,
|
||||
isLoading: Boolean = false
|
||||
) {
|
||||
val enabled = enabled && !isLoading
|
||||
ToggleRow(
|
||||
checked = checked,
|
||||
text = AnnotatedString(text),
|
||||
onCheckChanged = onCheckChanged,
|
||||
modifier = modifier,
|
||||
label = label?.let { AnnotatedString(it) },
|
||||
icon = icon,
|
||||
textColor = textColor,
|
||||
enabled = enabled,
|
||||
isLoading = isLoading
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Row that positions [text] and optional [label] in a [TextAndLabel] to the side of a [Switch].
|
||||
*
|
||||
* Can display a circular loading indicator by setting isLoaded to true. Setting isLoading to true
|
||||
* will disable the control by default.
|
||||
*/
|
||||
@Composable
|
||||
fun ToggleRow(
|
||||
checked: Boolean,
|
||||
text: AnnotatedString,
|
||||
onCheckChanged: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
label: AnnotatedString? = null,
|
||||
icon: ImageVector? = null,
|
||||
textColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
enabled: Boolean = true,
|
||||
isLoading: Boolean = false,
|
||||
inlineContent: Map<String, InlineTextContent> = mapOf()
|
||||
) {
|
||||
val isEnabled = enabled && !isLoading
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(enabled = enabled) { onCheckChanged(!checked) }
|
||||
.clickable(enabled = isEnabled) { onCheckChanged(!checked) }
|
||||
.padding(defaultPadding()),
|
||||
verticalAlignment = CenterVertically
|
||||
) {
|
||||
@@ -183,13 +216,14 @@ object Rows {
|
||||
text = text,
|
||||
label = label,
|
||||
textColor = textColor,
|
||||
enabled = enabled,
|
||||
modifier = Modifier.padding(end = 16.dp)
|
||||
enabled = isEnabled,
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
inlineContent = inlineContent
|
||||
)
|
||||
|
||||
val loadingContent by rememberDelayedState(isLoading)
|
||||
val toggleState = remember(checked, loadingContent, enabled, onCheckChanged) {
|
||||
ToggleState(checked, loadingContent, enabled, onCheckChanged)
|
||||
val toggleState = remember(checked, loadingContent, isEnabled, onCheckChanged) {
|
||||
ToggleState(checked, loadingContent, isEnabled, onCheckChanged)
|
||||
}
|
||||
|
||||
AnimatedContent(
|
||||
@@ -415,7 +449,8 @@ object Rows {
|
||||
label: AnnotatedString? = null,
|
||||
enabled: Boolean = true,
|
||||
textColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
textStyle: TextStyle = MaterialTheme.typography.bodyLarge
|
||||
textStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||
inlineContent: Map<String, InlineTextContent> = mapOf()
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
@@ -426,7 +461,8 @@ object Rows {
|
||||
Text(
|
||||
text = text,
|
||||
style = textStyle,
|
||||
color = textColor
|
||||
color = textColor,
|
||||
inlineContent = inlineContent
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user