Add timer selector

This commit is contained in:
charles
2022-10-27 09:07:50 +11:00
parent 63f372b45c
commit 68ca048267
7 changed files with 223 additions and 58 deletions

View File

@@ -3,25 +3,48 @@ package org.thoughtcrime.securesms.conversation.expiration
import android.os.Bundle
import android.os.Parcelable
import android.util.SparseArray
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityExpirationSettingsBinding
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.preferences.RadioOption
import org.thoughtcrime.securesms.preferences.RadioOptionAdapter
import javax.inject.Inject
import kotlin.math.max
@AndroidEntryPoint
class ExpirationSettingsActivity: PassphraseRequiredActionBarActivity() {
private lateinit var binding : ActivityExpirationSettingsBinding
@Inject lateinit var recipientDb: RecipientDatabase
@Inject lateinit var threadDb: ThreadDatabase
@Inject lateinit var viewModelFactory: ExpirationSettingsViewModel.AssistedFactory
private val threadId: Long by lazy {
intent.getLongExtra(THREAD_ID, -1)
}
private val expirationType: ExpirationType? by lazy {
ExpirationType.valueOf(intent.getIntExtra(EXTRA_EXPIRATION_TYPE, -1))
ExpirationType.valueOf(intent.getIntExtra(EXPIRATION_TYPE, -1))
}
private val viewModel: ExpirationSettingsViewModel by viewModels {
val afterReadOptions = resources.getIntArray(R.array.read_expiration_time_values).map(Int::toString)
.zip(resources.getStringArray(R.array.read_expiration_time_names)) { value, name -> RadioOption(value, name)}
val afterSendOptions = resources.getIntArray(R.array.send_expiration_time_values).map(Int::toString)
.zip(resources.getStringArray(R.array.send_expiration_time_names)) { value, name -> RadioOption(value, name)}
viewModelFactory.create(threadId, expirationType, afterReadOptions, afterSendOptions)
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -45,25 +68,26 @@ class ExpirationSettingsActivity: PassphraseRequiredActionBarActivity() {
}
}
val options = if (expirationType == ExpirationType.DELETE_AFTER_SEND) {
val values = resources.getIntArray(R.array.send_expiration_time_values).map(Int::toString)
val names = resources.getStringArray(R.array.send_expiration_time_names)
values.zip(names) { value, name -> RadioOption(value, name)}
} else {
listOf(
RadioOption("off", getString(R.string.expiration_off)),
RadioOption("read", getString(R.string.expiration_type_disappear_after_read)),
RadioOption("send", getString(R.string.expiration_type_disappear_after_send))
val deleteTypeOptions = listOf(
RadioOption("off", getString(R.string.expiration_off)),
RadioOption(
value = ExpirationType.DELETE_AFTER_READ_VALUE.toString(),
title = getString(R.string.expiration_type_disappear_after_read),
subtitle = getString(R.string.expiration_type_disappear_after_read_description)
),
RadioOption(
value = ExpirationType.DELETE_AFTER_SEND_VALUE.toString(),
title = getString(R.string.expiration_type_disappear_after_send),
subtitle = getString(R.string.expiration_type_disappear_after_send_description)
)
}
val optionAdapter = RadioOptionAdapter {
)
val deleteTypeOptionAdapter = RadioOptionAdapter {
viewModel.onExpirationTypeSelected(it)
}
binding.textViewDeleteType.isVisible = expirationType == null
binding.textViewTimer.isVisible = expirationType == null
binding.layoutTimer.isVisible = expirationType == null
binding.recyclerView.apply {
adapter = optionAdapter
binding.layoutDeleteTypes.isVisible = expirationType == null
binding.recyclerViewDeleteTypes.apply {
adapter = deleteTypeOptionAdapter
addItemDecoration(ContextCompat.getDrawable(this@ExpirationSettingsActivity, R.drawable.conversation_menu_divider)!!.let {
DividerItemDecoration(this@ExpirationSettingsActivity, RecyclerView.VERTICAL).apply {
setDrawable(it)
@@ -71,7 +95,34 @@ class ExpirationSettingsActivity: PassphraseRequiredActionBarActivity() {
})
setHasFixedSize(true)
}
optionAdapter.submitList(options)
deleteTypeOptionAdapter.submitList(deleteTypeOptions)
val timerOptionAdapter = RadioOptionAdapter {
viewModel.onExpirationTimerSelected(it)
}
binding.recyclerViewTimerOptions.apply {
adapter = timerOptionAdapter
addItemDecoration(ContextCompat.getDrawable(this@ExpirationSettingsActivity, R.drawable.conversation_menu_divider)!!.let {
DividerItemDecoration(this@ExpirationSettingsActivity, RecyclerView.VERTICAL).apply {
setDrawable(it)
}
})
}
lifecycleScope.launchWhenStarted {
launch {
viewModel.selectedExpirationType.collect { type ->
val position = deleteTypeOptions.indexOfFirst { it.value.toIntOrNull() == type?.number }
deleteTypeOptionAdapter.setSelectedPosition(max(0, position))
}
}
launch {
viewModel.expirationTimerOptions.collect { options ->
binding.textViewTimer.isVisible = options.isNotEmpty() && expirationType == null
binding.layoutTimer.isVisible = options.isNotEmpty()
timerOptionAdapter.submitList(options)
}
}
}
}
@@ -87,10 +138,12 @@ class ExpirationSettingsActivity: PassphraseRequiredActionBarActivity() {
actionBar.setDisplayHomeAsUpEnabled(true)
actionBar.setHomeButtonEnabled(true)
}
companion object {
private const val SCROLL_PARCEL = "scroll_parcel"
const val EXTRA_EXPIRATION_TYPE = "expiration_type"
const val EXTRA_READ_ONLY = "read_only"
const val THREAD_ID = "thread_id"
const val EXPIRATION_TYPE = "expiration_type"
const val READ_ONLY = "read_only"
}
}

View File

@@ -0,0 +1,84 @@
package org.thoughtcrime.securesms.conversation.expiration
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.preferences.RadioOption
class ExpirationSettingsViewModel(
private val threadId: Long,
private val expirationType: ExpirationType?,
private val afterReadOptions: List<RadioOption>,
private val afterSendOptions: List<RadioOption>,
private val threadDb: ThreadDatabase
) : ViewModel() {
val recipient: Recipient?
get() = threadDb.getRecipientForThreadId(threadId)
private val _selectedExpirationType = MutableStateFlow(expirationType)
val selectedExpirationType: StateFlow<ExpirationType?> = _selectedExpirationType
private val _expirationTimerOptions = MutableStateFlow<List<RadioOption>>(emptyList())
val expirationTimerOptions: StateFlow<List<RadioOption>> = _expirationTimerOptions
init {
selectedExpirationType.mapLatest {
when (it) {
ExpirationType.DELETE_AFTER_SEND -> afterSendOptions
ExpirationType.DELETE_AFTER_READ -> afterReadOptions
else -> emptyList()
}
}.onEach {
_expirationTimerOptions.value = it
}.launchIn(viewModelScope)
}
fun onExpirationTypeSelected(option: RadioOption) {
_selectedExpirationType.value = option.value.toIntOrNull()?.let { ExpirationType.valueOf(it) }
}
fun onExpirationTimerSelected(option: RadioOption) {
}
@dagger.assisted.AssistedFactory
interface AssistedFactory {
fun create(
threadId: Long,
expirationType: ExpirationType?,
@Assisted("afterRead") afterReadOptions: List<RadioOption>,
@Assisted("afterSend") afterSendOptions: List<RadioOption>
): Factory
}
@Suppress("UNCHECKED_CAST")
class Factory @AssistedInject constructor(
@Assisted private val threadId: Long,
@Assisted private val expirationType: ExpirationType?,
@Assisted("afterRead") private val afterReadOptions: List<RadioOption>,
@Assisted("afterSend") private val afterSendOptions: List<RadioOption>,
private val threadDb: ThreadDatabase
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ExpirationSettingsViewModel(
threadId,
expirationType,
afterReadOptions,
afterSendOptions,
threadDb
) as T
}
}
}

View File

@@ -990,8 +990,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
if (group?.isActive == false) { return }
}
val expirationIntent = Intent(this, ExpirationSettingsActivity::class.java)
expirationIntent.putExtra(ExpirationSettingsActivity.THREAD_ID, viewModel.threadId)
if (thread.isLocalNumber || thread.isClosedGroupRecipient) {
expirationIntent.putExtra(ExpirationSettingsActivity.EXTRA_EXPIRATION_TYPE, ExpirationType.DELETE_AFTER_SEND_VALUE)
expirationIntent.putExtra(ExpirationSettingsActivity.EXPIRATION_TYPE, ExpirationType.DELETE_AFTER_SEND_VALUE)
}
show(expirationIntent, true)
}

View File

@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.preferences
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
@@ -11,7 +12,7 @@ import network.loki.messenger.databinding.ItemSelectableBinding
import org.thoughtcrime.securesms.mms.GlideApp
class RadioOptionAdapter(
var selectedOptionPosition: Int = 0,
private var selectedOptionPosition: Int = 0,
private val onClickListener: (RadioOption) -> Unit
) : ListAdapter<RadioOption, RadioOptionAdapter.ViewHolder>(RadioOptionDiffer()) {
@@ -35,6 +36,11 @@ class RadioOptionAdapter(
}
}
fun setSelectedPosition(selectedPosition: Int) {
selectedOptionPosition = selectedPosition
notifyDataSetChanged()
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val glide = GlideApp.with(itemView)
@@ -42,6 +48,8 @@ class RadioOptionAdapter(
fun bind(option: RadioOption, isSelected: Boolean, toggleSelection: (RadioOption) -> Unit) {
binding.titleTextView.text = option.title
binding.subtitleTextView.text = option.subtitle
binding.subtitleTextView.isVisible = !option.subtitle.isNullOrEmpty()
binding.root.setOnClickListener { toggleSelection(option) }
binding.selectButton.isSelected = isSelected
}
@@ -51,5 +59,6 @@ class RadioOptionAdapter(
data class RadioOption(
val value: String,
val title: String
val title: String,
val subtitle: String? = null
)