Add radioOption DSL

This commit is contained in:
andrew 2023-08-21 19:45:50 +09:30
parent 9123dd90a4
commit 78eef350b5
5 changed files with 130 additions and 106 deletions

View File

@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.preferences.ExpirationRadioOption import org.thoughtcrime.securesms.preferences.ExpirationRadioOption
import org.thoughtcrime.securesms.preferences.RadioOptionAdapter import org.thoughtcrime.securesms.preferences.RadioOptionAdapter
import org.thoughtcrime.securesms.preferences.radioOption
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.max import kotlin.math.max
@ -43,30 +44,27 @@ class ExpirationSettingsActivity: PassphraseRequiredActionBarActivity() {
} }
private val viewModel: ExpirationSettingsViewModel by viewModels { private val viewModel: ExpirationSettingsViewModel by viewModels {
val afterReadOptions = resources.getIntArray(R.array.read_expiration_time_values).map(Int::toString) val afterReadOptions = resources.getIntArray(R.array.read_expiration_time_values)
.zip(resources.getStringArray(R.array.read_expiration_time_names)) { value, name -> .zip(resources.getStringArray(R.array.read_expiration_time_names)) { value, name ->
ExpirationRadioOption(ExpiryMode.AfterRead(value.toLong()), name, getString(R.string.AccessibilityId_time_option)) radioOption(ExpiryMode.AfterRead(value.toLong()), name) { contentDescription(R.string.AccessibilityId_time_option) }
} }
val afterSendOptions = resources.getIntArray(R.array.send_expiration_time_values).map(Int::toString) val afterSendOptions = resources.getIntArray(R.array.send_expiration_time_values)
.zip(resources.getStringArray(R.array.send_expiration_time_names)) { value, name -> .zip(resources.getStringArray(R.array.send_expiration_time_names)) { value, name ->
ExpirationRadioOption(ExpiryMode.AfterSend(value.toLong()), name, getString(R.string.AccessibilityId_time_option)) radioOption(ExpiryMode.AfterSend(value.toLong()), name) { contentDescription(R.string.AccessibilityId_time_option) }
} }
viewModelFactory.create(threadId, mayAddTestExpiryOption(afterReadOptions), mayAddTestExpiryOption(afterSendOptions)) viewModelFactory.create(threadId, mayAddTestExpiryOption(afterReadOptions), mayAddTestExpiryOption(afterSendOptions))
} }
private fun mayAddTestExpiryOption(expiryOptions: List<ExpirationRadioOption>): List<ExpirationRadioOption> { private fun mayAddTestExpiryOption(expiryOptions: List<ExpirationRadioOption>): List<ExpirationRadioOption> =
return if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
val options = expiryOptions.toMutableList() when (expiryOptions.first().value) {
val added = when (options.first().value) {
is ExpiryMode.AfterRead -> ExpiryMode.AfterRead(60) is ExpiryMode.AfterRead -> ExpiryMode.AfterRead(60)
is ExpiryMode.AfterSend -> ExpiryMode.AfterSend(60) is ExpiryMode.AfterSend -> ExpiryMode.AfterSend(60)
is ExpiryMode.Legacy -> ExpiryMode.Legacy(60) is ExpiryMode.Legacy -> ExpiryMode.Legacy(60)
ExpiryMode.NONE -> ExpiryMode.NONE // shouldn't happen ExpiryMode.NONE -> ExpiryMode.NONE // shouldn't happen
} }.let { radioOption(it, "1 Minute (for testing purposes)") }
options.add(1, ExpirationRadioOption(added, "1 Minute (for testing purposes)")) .let { expiryOptions.toMutableList().apply { add(1, it) } }
options
} else expiryOptions } else expiryOptions
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
@ -184,55 +182,41 @@ class ExpirationSettingsActivity: PassphraseRequiredActionBarActivity() {
if (viewModel.recipient.value?.isContactRecipient == true && viewModel.recipient.value?.isLocalNumber == false) { if (viewModel.recipient.value?.isContactRecipient == true && viewModel.recipient.value?.isLocalNumber == false) {
deleteTypeOptions.addAll( deleteTypeOptions.addAll(
listOf( listOf(
ExpirationRadioOption( radioOption(ExpiryMode.NONE, R.string.expiration_off) {
value = ExpiryMode.NONE, contentDescription(R.string.AccessibilityId_disable_disappearing_messages)
title = getString(R.string.expiration_off), },
contentDescription = getString(R.string.AccessibilityId_disable_disappearing_messages) radioOption(ExpiryMode.AfterRead(0), R.string.expiration_type_disappear_after_read) {
), subtitle(R.string.expiration_type_disappear_after_read_description)
ExpirationRadioOption( contentDescription(R.string.AccessibilityId_disappear_after_read_option)
value = ExpiryMode.AfterRead(0), },
title = getString(R.string.expiration_type_disappear_after_read), radioOption(ExpiryMode.AfterSend(0), R.string.expiration_type_disappear_after_send) {
subtitle = getString(R.string.expiration_type_disappear_after_read_description), subtitle(R.string.expiration_type_disappear_after_send_description)
contentDescription = getString(R.string.AccessibilityId_disappear_after_read_option) contentDescription(R.string.AccessibilityId_disappear_after_send_option)
), }
ExpirationRadioOption(
value = ExpiryMode.AfterSend(0),
title = getString(R.string.expiration_type_disappear_after_send),
subtitle = getString(R.string.expiration_type_disappear_after_send_description),
contentDescription = getString(R.string.AccessibilityId_disappear_after_send_option)
)
) )
) )
} else if (viewModel.recipient.value?.isLocalNumber == true) { } else if (viewModel.recipient.value?.isLocalNumber == true) {
deleteTypeOptions.addAll( deleteTypeOptions.addAll(
listOf( listOf(
ExpirationRadioOption( radioOption(ExpiryMode.NONE, R.string.expiration_off) {
value = ExpiryMode.NONE, contentDescription(R.string.AccessibilityId_disable_disappearing_messages)
title = getString(R.string.expiration_off), },
contentDescription = getString(R.string.AccessibilityId_disable_disappearing_messages) radioOption(ExpiryMode.AfterSend(0), R.string.expiration_type_disappear_after_send) {
), subtitle(R.string.expiration_type_disappear_after_send_description)
ExpirationRadioOption( contentDescription(R.string.AccessibilityId_disappear_after_send_option)
value = ExpiryMode.AfterSend(0), }
title = getString(R.string.expiration_type_disappear_after_send),
subtitle = getString(R.string.expiration_type_disappear_after_send_description),
contentDescription = getString(R.string.AccessibilityId_disappear_after_send_option)
)
) )
) )
} else if (viewModel.recipient.value?.isClosedGroupRecipient == true) { } else if (viewModel.recipient.value?.isClosedGroupRecipient == true) {
deleteTypeOptions.addAll( deleteTypeOptions.addAll(
listOf( listOf(
ExpirationRadioOption( radioOption(ExpiryMode.NONE, R.string.expiration_off) {
value = ExpiryMode.NONE, contentDescription(R.string.AccessibilityId_disable_disappearing_messages)
title = getString(R.string.expiration_off), },
contentDescription = getString(R.string.AccessibilityId_disable_disappearing_messages) radioOption(ExpiryMode.AfterSend(0), R.string.expiration_type_disappear_after_send) {
), subtitle(R.string.expiration_type_disappear_after_send_description)
ExpirationRadioOption( contentDescription(R.string.AccessibilityId_disappear_after_send_option)
value = ExpiryMode.AfterSend(0), }
title = getString(R.string.expiration_type_disappear_after_send),
subtitle = getString(R.string.expiration_type_disappear_after_send_description),
contentDescription = getString(R.string.AccessibilityId_disappear_after_send_option)
)
) )
) )
} }
@ -240,48 +224,36 @@ class ExpirationSettingsActivity: PassphraseRequiredActionBarActivity() {
if (viewModel.recipient.value?.isContactRecipient == true && viewModel.recipient.value?.isLocalNumber == false) { if (viewModel.recipient.value?.isContactRecipient == true && viewModel.recipient.value?.isLocalNumber == false) {
deleteTypeOptions.addAll( deleteTypeOptions.addAll(
listOf( listOf(
ExpirationRadioOption( radioOption(ExpiryMode.NONE, R.string.expiration_off) {
value = ExpiryMode.NONE, contentDescription(R.string.AccessibilityId_disable_disappearing_messages)
title = getString(R.string.expiration_off), },
contentDescription = getString(R.string.AccessibilityId_disable_disappearing_messages) radioOption(ExpiryMode.Legacy(0), R.string.expiration_type_disappear_legacy) {
), subtitle(R.string.expiration_type_disappear_legacy_description)
ExpirationRadioOption( },
value = ExpiryMode.Legacy(0), radioOption(ExpiryMode.AfterRead(0), R.string.expiration_type_disappear_after_read) {
title = getString(R.string.expiration_type_disappear_legacy), subtitle(R.string.expiration_type_disappear_after_read_description)
subtitle = getString(R.string.expiration_type_disappear_legacy_description) enabled = false
), contentDescription(R.string.AccessibilityId_disappear_after_read_option)
ExpirationRadioOption( },
value = ExpiryMode.AfterRead(0), radioOption(ExpiryMode.AfterSend(0), R.string.expiration_type_disappear_after_send) {
title = getString(R.string.expiration_type_disappear_after_read), subtitle(R.string.expiration_type_disappear_after_send_description)
subtitle = getString(R.string.expiration_type_disappear_after_read_description), enabled = false
enabled = false, contentDescription(R.string.AccessibilityId_disappear_after_send_option)
contentDescription = getString(R.string.AccessibilityId_disappear_after_read_option) }
),
ExpirationRadioOption(
value = ExpiryMode.AfterSend(0),
title = getString(R.string.expiration_type_disappear_after_send),
subtitle = getString(R.string.expiration_type_disappear_after_send_description),
enabled = false,
contentDescription = getString(R.string.AccessibilityId_disappear_after_send_option)
)
) )
) )
} else { } else {
deleteTypeOptions.addAll( deleteTypeOptions.addAll(
listOf( listOf(
ExpirationRadioOption(value = ExpiryMode.NONE, title = getString(R.string.expiration_off)), radioOption(ExpiryMode.NONE, R.string.expiration_off),
ExpirationRadioOption( radioOption(ExpiryMode.Legacy(0), R.string.expiration_type_disappear_legacy) {
value = ExpiryMode.Legacy(0), subtitle(R.string.expiration_type_disappear_legacy_description)
title = getString(R.string.expiration_type_disappear_legacy), },
subtitle = getString(R.string.expiration_type_disappear_legacy_description) radioOption(ExpiryMode.AfterSend(0), R.string.expiration_type_disappear_after_send) {
), subtitle(R.string.expiration_type_disappear_after_send_description)
ExpirationRadioOption( enabled = false
value = ExpiryMode.AfterSend(0), contentDescription(R.string.AccessibilityId_disappear_after_send_option)
title = getString(R.string.expiration_type_disappear_after_send), }
subtitle = getString(R.string.expiration_type_disappear_after_send_description),
enabled = false,
contentDescription = getString(R.string.AccessibilityId_disappear_after_send_option)
)
) )
) )
} }

View File

@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -27,6 +28,7 @@ import org.thoughtcrime.securesms.preferences.ExpirationRadioOption
import org.thoughtcrime.securesms.preferences.RadioOption import org.thoughtcrime.securesms.preferences.RadioOption
import kotlin.reflect.KClass import kotlin.reflect.KClass
@OptIn(ExperimentalCoroutinesApi::class)
class ExpirationSettingsViewModel( class ExpirationSettingsViewModel(
private val threadId: Long, private val threadId: Long,
private val afterReadOptions: List<ExpirationRadioOption>, private val afterReadOptions: List<ExpirationRadioOption>,

View File

@ -46,8 +46,8 @@ class ClearAllDataDialog : DialogFragment() {
private fun createView(): View { private fun createView(): View {
binding = DialogClearAllDataBinding.inflate(LayoutInflater.from(requireContext())) binding = DialogClearAllDataBinding.inflate(LayoutInflater.from(requireContext()))
val device = StringRadioOption("deviceOnly", requireContext().getString(R.string.dialog_clear_all_data_clear_device_only)) val device = radioOption("deviceOnly", R.string.dialog_clear_all_data_clear_device_only)
val network = StringRadioOption("deviceAndNetwork", requireContext().getString(R.string.dialog_clear_all_data_clear_device_and_network)) val network = radioOption("deviceAndNetwork", R.string.dialog_clear_all_data_clear_device_and_network)
var selectedOption: RadioOption<String> = device var selectedOption: RadioOption<String> = device
val optionAdapter = RadioOptionAdapter { selectedOption = it } val optionAdapter = RadioOptionAdapter { selectedOption = it }
binding.recyclerView.apply { binding.recyclerView.apply {

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.preferences
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.StringRes
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
@ -11,6 +12,7 @@ import network.loki.messenger.R
import network.loki.messenger.databinding.ItemSelectableBinding import network.loki.messenger.databinding.ItemSelectableBinding
import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.ExpiryMode
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.ui.GetString
import java.util.Objects import java.util.Objects
class RadioOptionAdapter<T>( class RadioOptionAdapter<T>(
@ -23,15 +25,15 @@ class RadioOptionAdapter<T>(
override fun areContentsTheSame(oldItem: RadioOption<T>, newItem: RadioOption<T>) = Objects.equals(oldItem.value,newItem.value) override fun areContentsTheSame(oldItem: RadioOption<T>, newItem: RadioOption<T>) = Objects.equals(oldItem.value,newItem.value)
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<T> { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<T> =
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_selectable, parent, false) LayoutInflater.from(parent.context).inflate(R.layout.item_selectable, parent, false)
return ViewHolder<T>(itemView) .let(::ViewHolder)
}
override fun onBindViewHolder(holder: ViewHolder<T>, position: Int) { override fun onBindViewHolder(holder: ViewHolder<T>, position: Int) {
val option = getItem(position) holder.bind(
val isSelected = position == selectedOptionPosition option = getItem(position),
holder.bind(option, isSelected) { isSelected = position == selectedOptionPosition
) {
onClickListener(it) onClickListener(it)
selectedOptionPosition = position selectedOptionPosition = position
notifyItemRangeChanged(0, itemCount) notifyItemRangeChanged(0, itemCount)
@ -44,21 +46,22 @@ class RadioOptionAdapter<T>(
} }
class ViewHolder<T>(itemView: View): RecyclerView.ViewHolder(itemView) { class ViewHolder<T>(itemView: View): RecyclerView.ViewHolder(itemView) {
val glide = GlideApp.with(itemView) val glide = GlideApp.with(itemView)
val binding = ItemSelectableBinding.bind(itemView) val binding = ItemSelectableBinding.bind(itemView)
fun bind(option: RadioOption<T>, isSelected: Boolean, toggleSelection: (RadioOption<T>) -> Unit) { fun bind(option: RadioOption<T>, isSelected: Boolean, toggleSelection: (RadioOption<T>) -> Unit) {
val alpha = if (option.enabled) 1f else 0.5f val alpha = if (option.enabled) 1f else 0.5f
binding.root.isEnabled = option.enabled binding.root.isEnabled = option.enabled
binding.root.contentDescription = option.contentDescription binding.root.contentDescription = option.contentDescription?.string(itemView.context)
binding.titleTextView.alpha = alpha binding.titleTextView.alpha = alpha
binding.subtitleTextView.alpha = alpha binding.subtitleTextView.alpha = alpha
binding.selectButton.alpha = alpha binding.selectButton.alpha = alpha
binding.titleTextView.text = option.title binding.titleTextView.text = option.title.string(itemView.context)
binding.subtitleTextView.text = option.subtitle binding.subtitleTextView.text = option.subtitle?.string(itemView.context).also {
binding.subtitleTextView.isVisible = !option.subtitle.isNullOrEmpty() binding.subtitleTextView.isVisible = !it.isNullOrBlank()
}
binding.selectButton.isSelected = isSelected binding.selectButton.isSelected = isSelected
if (option.enabled) { if (option.enabled) {
binding.root.setOnClickListener { toggleSelection(option) } binding.root.setOnClickListener { toggleSelection(option) }
@ -68,13 +71,55 @@ class RadioOptionAdapter<T>(
} }
data class RadioOption<T>( data class RadioOption<out T>(
val value: T, val value: T,
val title: String, val title: GetString,
val subtitle: String? = null, val subtitle: GetString? = null,
val enabled: Boolean = true, val enabled: Boolean = true,
val contentDescription: String = "" val contentDescription: GetString? = null
) )
fun <T> radioOption(value: T, @StringRes title: Int, configure: RadioOptionBuilder<T>.() -> Unit = {}) =
radioOption(value, GetString(title), configure)
fun <T> radioOption(value: T, title: String, configure: RadioOptionBuilder<T>.() -> Unit = {}) =
radioOption(value, GetString(title), configure)
fun <T> radioOption(value: T, title: GetString, configure: RadioOptionBuilder<T>.() -> Unit = {}) =
RadioOptionBuilder(value, title).also { it.configure() }.build()
class RadioOptionBuilder<out T>(
val value: T,
val title: GetString
) {
var subtitle: GetString? = null
var enabled: Boolean = true
var contentDescription: GetString? = null
fun subtitle(string: String) {
subtitle = GetString(string)
}
fun subtitle(@StringRes stringRes: Int) {
subtitle = GetString(stringRes)
}
fun contentDescription(string: String) {
contentDescription = GetString(string)
}
fun contentDescription(@StringRes stringRes: Int) {
contentDescription = GetString(stringRes)
}
fun build() = RadioOption(
value,
title,
subtitle,
enabled,
contentDescription
)
}
typealias StringRadioOption = RadioOption<String> typealias StringRadioOption = RadioOption<String>
typealias ExpirationRadioOption = RadioOption<ExpiryMode> typealias ExpirationRadioOption = RadioOption<ExpiryMode>

View File

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.ui package org.thoughtcrime.securesms.ui
import android.content.Context
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -10,13 +11,17 @@ import androidx.compose.ui.res.stringResource
sealed class GetString { sealed class GetString {
@Composable @Composable
abstract fun string(): String abstract fun string(): String
abstract fun string(context: Context): String
data class FromString(val string: String): GetString() { data class FromString(val string: String): GetString() {
@Composable @Composable
override fun string(): String = string override fun string(): String = string
override fun string(context: Context): String = string
} }
data class FromResId(@StringRes val resId: Int): GetString() { data class FromResId(@StringRes val resId: Int): GetString() {
@Composable @Composable
override fun string(): String = stringResource(resId) override fun string(): String = stringResource(resId)
override fun string(context: Context): String = context.getString(resId)
} }
} }