Merge pull request #1205 from bemusementpark/disable-unblock

Disable unblock button
This commit is contained in:
Morgan Pretty 2023-05-30 10:30:51 +10:00 committed by GitHub
commit 22ed2dd8aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 172 additions and 131 deletions

View File

@ -276,7 +276,7 @@ public class RecipientDatabase extends Database {
notifyRecipientListeners(); notifyRecipientListeners();
} }
public void setBlocked(@NonNull List<Recipient> recipients, boolean blocked) { public void setBlocked(@NonNull Iterable<Recipient> recipients, boolean blocked) {
SQLiteDatabase db = getWritableDatabase(); SQLiteDatabase db = getWritableDatabase();
db.beginTransaction(); db.beginTransaction();
try { try {

View File

@ -1010,7 +1010,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseComponent.get(context).reactionDatabase().deleteMessageReactions(MessageId(messageId, mms)) DatabaseComponent.get(context).reactionDatabase().deleteMessageReactions(MessageId(messageId, mms))
} }
override fun unblock(toUnblock: List<Recipient>) { override fun unblock(toUnblock: Iterable<Recipient>) {
val recipientDb = DatabaseComponent.get(context).recipientDatabase() val recipientDb = DatabaseComponent.get(context).recipientDatabase()
recipientDb.setBlocked(toUnblock, false) recipientDb.setBlocked(toUnblock, false)
} }

View File

@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.preferences
import android.app.AlertDialog import android.app.AlertDialog
import android.os.Bundle import android.os.Bundle
import android.view.View
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.view.isVisible import androidx.core.view.isVisible
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -11,58 +10,26 @@ import network.loki.messenger.databinding.ActivityBlockedContactsBinding
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
@AndroidEntryPoint @AndroidEntryPoint
class BlockedContactsActivity: PassphraseRequiredActionBarActivity(), View.OnClickListener { class BlockedContactsActivity: PassphraseRequiredActionBarActivity() {
lateinit var binding: ActivityBlockedContactsBinding lateinit var binding: ActivityBlockedContactsBinding
val viewModel: BlockedContactsViewModel by viewModels() val viewModel: BlockedContactsViewModel by viewModels()
val adapter = BlockedContactsAdapter() val adapter: BlockedContactsAdapter by lazy { BlockedContactsAdapter(viewModel) }
override fun onClick(v: View?) { fun unblock() {
if (v === binding.unblockButton && adapter.getSelectedItems().isNotEmpty()) { // show dialog
val contactsToUnblock = adapter.getSelectedItems() val title = viewModel.getTitle(this)
// show dialog
val title = if (contactsToUnblock.size == 1) {
getString(R.string.Unblock_dialog__title_single, contactsToUnblock.first().name)
} else {
getString(R.string.Unblock_dialog__title_multiple)
}
val message = if (contactsToUnblock.size == 1) { val message = viewModel.getMessage(this)
getString(R.string.Unblock_dialog__message, contactsToUnblock.first().name)
} else {
val stringBuilder = StringBuilder()
val iterator = contactsToUnblock.iterator()
var numberAdded = 0
while (iterator.hasNext() && numberAdded < 3) {
val nextRecipient = iterator.next()
if (numberAdded > 0) stringBuilder.append(", ")
stringBuilder.append(nextRecipient.name)
numberAdded++
}
val overflow = contactsToUnblock.size - numberAdded
if (overflow > 0) {
stringBuilder.append(" ")
val string = resources.getQuantityString(R.plurals.Unblock_dialog__message_multiple_overflow, overflow)
stringBuilder.append(string.format(overflow))
}
getString(R.string.Unblock_dialog__message, stringBuilder.toString())
}
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle(title) .setTitle(title)
.setMessage(message) .setMessage(message)
.setPositiveButton(R.string.continue_2) { d, _ -> .setPositiveButton(R.string.continue_2) { _, _ -> viewModel.unblock() }
viewModel.unblock(contactsToUnblock) .setNegativeButton(R.string.cancel) { _, _ -> }
d.dismiss() .show()
}
.setNegativeButton(R.string.cancel) { d, _ ->
d.dismiss()
}
.show()
}
} }
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
@ -73,15 +40,15 @@ class BlockedContactsActivity: PassphraseRequiredActionBarActivity(), View.OnCli
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
viewModel.subscribe(this) viewModel.subscribe(this)
.observe(this) { newState -> .observe(this) { state ->
adapter.submitList(newState.blockedContacts) adapter.submitList(state.items)
val isEmpty = newState.blockedContacts.isEmpty() binding.emptyStateMessageTextView.isVisible = state.emptyStateMessageTextViewVisible
binding.emptyStateMessageTextView.isVisible = isEmpty binding.nonEmptyStateGroup.isVisible = state.nonEmptyStateGroupVisible
binding.nonEmptyStateGroup.isVisible = !isEmpty binding.unblockButton.isEnabled = state.unblockButtonEnabled
} }
binding.unblockButton.setOnClickListener(this) binding.unblockButton.setOnClickListener { unblock() }
} }
}
}

View File

@ -10,38 +10,30 @@ import network.loki.messenger.R
import network.loki.messenger.databinding.BlockedContactLayoutBinding import network.loki.messenger.databinding.BlockedContactLayoutBinding
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.util.adapter.SelectableItem
class BlockedContactsAdapter: ListAdapter<Recipient,BlockedContactsAdapter.ViewHolder>(RecipientDiffer()) { typealias SelectableRecipient = SelectableItem<Recipient>
class RecipientDiffer: DiffUtil.ItemCallback<Recipient>() { class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdapter<SelectableRecipient,BlockedContactsAdapter.ViewHolder>(RecipientDiffer()) {
override fun areItemsTheSame(oldItem: Recipient, newItem: Recipient) = oldItem === newItem
override fun areContentsTheSame(oldItem: Recipient, newItem: Recipient) = oldItem == newItem class RecipientDiffer: DiffUtil.ItemCallback<SelectableRecipient>() {
override fun areItemsTheSame(old: SelectableRecipient, new: SelectableRecipient) = old.item.address == new.item.address
override fun areContentsTheSame(old: SelectableRecipient, new: SelectableRecipient) = old.isSelected == new.isSelected
override fun getChangePayload(old: SelectableRecipient, new: SelectableRecipient) = new.isSelected
} }
private val selectedItems = mutableListOf<Recipient>() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
LayoutInflater.from(parent.context)
fun getSelectedItems() = selectedItems .inflate(R.layout.blocked_contact_layout, parent, false)
.let(::ViewHolder)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.blocked_contact_layout, parent, false)
return ViewHolder(itemView)
}
private fun toggleSelection(recipient: Recipient, isSelected: Boolean, position: Int) {
if (isSelected) {
selectedItems -= recipient
} else {
selectedItems += recipient
}
notifyItemChanged(position)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val recipient = getItem(position) holder.bind(getItem(position), viewModel::toggle)
val isSelected = recipient in selectedItems }
holder.bind(recipient, isSelected) {
toggleSelection(recipient, isSelected, position) override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
} if (payloads.isEmpty()) holder.bind(getItem(position), viewModel::toggle)
else holder.select(getItem(position).isSelected)
} }
override fun onViewRecycled(holder: ViewHolder) { override fun onViewRecycled(holder: ViewHolder) {
@ -54,15 +46,18 @@ class BlockedContactsAdapter: ListAdapter<Recipient,BlockedContactsAdapter.ViewH
val glide = GlideApp.with(itemView) val glide = GlideApp.with(itemView)
val binding = BlockedContactLayoutBinding.bind(itemView) val binding = BlockedContactLayoutBinding.bind(itemView)
fun bind(recipient: Recipient, isSelected: Boolean, toggleSelection: () -> Unit) { fun bind(selectable: SelectableRecipient, toggle: (SelectableRecipient) -> Unit) {
binding.recipientName.text = recipient.name binding.recipientName.text = selectable.item.name
with (binding.profilePictureView.root) { with (binding.profilePictureView.root) {
glide = this@ViewHolder.glide glide = this@ViewHolder.glide
update(recipient) update(selectable.item)
} }
binding.root.setOnClickListener { toggleSelection() } binding.root.setOnClickListener { toggle(selectable) }
binding.selectButton.isSelected = selectable.isSelected
}
fun select(isSelected: Boolean) {
binding.selectButton.isSelected = isSelected binding.selectButton.isSelected = isSelected
} }
} }
}
}

View File

@ -17,9 +17,11 @@ import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import network.loki.messenger.R
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.DatabaseContentProviders
import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.util.adapter.SelectableItem
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -29,7 +31,9 @@ class BlockedContactsViewModel @Inject constructor(private val storage: Storage)
private val listUpdateChannel = Channel<Unit>(capacity = Channel.CONFLATED) private val listUpdateChannel = Channel<Unit>(capacity = Channel.CONFLATED)
private val _contacts = MutableLiveData(BlockedContactsViewState(emptyList())) private val _state = MutableLiveData(BlockedContactsViewState())
val state get() = _state.value!!
fun subscribe(context: Context): LiveData<BlockedContactsViewState> { fun subscribe(context: Context): LiveData<BlockedContactsViewState> {
executor.launch(IO) { executor.launch(IO) {
@ -45,21 +49,74 @@ class BlockedContactsViewModel @Inject constructor(private val storage: Storage)
} }
executor.launch(IO) { executor.launch(IO) {
for (update in listUpdateChannel) { for (update in listUpdateChannel) {
val blockedContactState = BlockedContactsViewState(storage.blockedContacts().sortedBy { it.name }) val blockedContactState = state.copy(
blockedContacts = storage.blockedContacts().sortedBy { it.name }
)
withContext(Main) { withContext(Main) {
_contacts.value = blockedContactState _state.value = blockedContactState
} }
} }
} }
return _contacts return _state
} }
fun unblock(toUnblock: List<Recipient>) { fun unblock() {
storage.unblock(toUnblock) storage.unblock(state.selectedItems)
_state.value = state.copy(selectedItems = emptySet())
}
fun select(selectedItem: Recipient, isSelected: Boolean) {
_state.value = state.run {
if (isSelected) copy(selectedItems = selectedItems + selectedItem)
else copy(selectedItems = selectedItems - selectedItem)
}
}
fun getTitle(context: Context): String =
if (state.selectedItems.size == 1) {
context.getString(R.string.Unblock_dialog__title_single, state.selectedItems.first().name)
} else {
context.getString(R.string.Unblock_dialog__title_multiple)
}
fun getMessage(context: Context): String {
if (state.selectedItems.size == 1) {
return context.getString(R.string.Unblock_dialog__message, state.selectedItems.first().name)
}
val stringBuilder = StringBuilder()
val iterator = state.selectedItems.iterator()
var numberAdded = 0
while (iterator.hasNext() && numberAdded < 3) {
val nextRecipient = iterator.next()
if (numberAdded > 0) stringBuilder.append(", ")
stringBuilder.append(nextRecipient.name)
numberAdded++
}
val overflow = state.selectedItems.size - numberAdded
if (overflow > 0) {
stringBuilder.append(" ")
val string = context.resources.getQuantityString(R.plurals.Unblock_dialog__message_multiple_overflow, overflow)
stringBuilder.append(string.format(overflow))
}
return context.getString(R.string.Unblock_dialog__message, stringBuilder.toString())
}
fun toggle(selectable: SelectableItem<Recipient>) {
_state.value = state.run {
if (selectable.item in selectedItems) copy(selectedItems = selectedItems - selectable.item)
else copy(selectedItems = selectedItems + selectable.item)
}
} }
data class BlockedContactsViewState( data class BlockedContactsViewState(
val blockedContacts: List<Recipient> val blockedContacts: List<Recipient> = emptyList(),
) val selectedItems: Set<Recipient> = emptySet()
) {
val items = blockedContacts.map { SelectableItem(it, it in selectedItems) }
} val unblockButtonEnabled get() = selectedItems.isNotEmpty()
val emptyStateMessageTextViewVisible get() = blockedContacts.isEmpty()
val nonEmptyStateGroupVisible get() = blockedContacts.isNotEmpty()
}
}

View File

@ -0,0 +1,3 @@
package org.thoughtcrime.securesms.util.adapter
data class SelectableItem<T>(val item: T, val isSelected: Boolean)

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="?android:textColorTertiary"/>
<item android:color="@color/destructive"/>
</selector>

View File

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape <ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/button_destructive">
android:shape="rectangle"> <item>
<shape android:shape="rectangle">
<solid android:color="@color/transparent" /> <solid android:color="?colorPrimary"/>
<corners android:radius="@dimen/medium_button_corner_radius" />
<corners android:radius="@dimen/medium_button_corner_radius" /> <stroke
android:color="@color/button_destructive"
<stroke android:width="@dimen/border_thickness" android:color="@color/destructive" /> android:width="@dimen/border_thickness" />
</shape> </shape>
</item>
</ripple>

View File

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape <ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" android:color="?prominentButtonColor">
android:shape="rectangle"> <item>
<shape android:shape="rectangle">
<solid android:color="@color/transparent" /> <solid android:color="?colorPrimary"/>
<corners android:radius="@dimen/medium_button_corner_radius" />
<corners android:radius="@dimen/medium_button_corner_radius" /> <stroke
android:color="?prominentButtonColor"
<stroke android:width="@dimen/border_thickness" android:color="?prominentButtonColor" /> android:width="@dimen/border_thickness" />
</shape> </shape>
</item>
</ripple>

View File

@ -4,28 +4,37 @@
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.cardview.widget.CardView
<androidx.recyclerview.widget.RecyclerView android:id="@+id/cardView"
android:id="@+id/recyclerView"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/unblockButton" app:layout_constraintBottom_toTopOf="@+id/unblockButton"
android:layout_width="match_parent" app:cardCornerRadius="?preferenceCornerRadius"
android:layout_height="0dp" app:cardElevation="0dp"
android:background="@drawable/preference_single_no_padding" app:cardBackgroundColor="?colorSettingsBackground"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_marginHorizontal="@dimen/medium_spacing" android:layout_marginHorizontal="@dimen/medium_spacing"
android:layout_marginVertical="@dimen/large_spacing" android:layout_marginVertical="@dimen/large_spacing"
/> android:layout_width="match_parent"
android:layout_height="0dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
/>
</androidx.cardview.widget.CardView>
<TextView <TextView
android:id="@+id/emptyStateMessageTextView" android:id="@+id/emptyStateMessageTextView"
android:visibility="gone" android:visibility="gone"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="@+id/recyclerView" app:layout_constraintTop_toTopOf="@+id/cardView"
android:layout_marginTop="@dimen/medium_spacing" android:layout_marginTop="@dimen/medium_spacing"
app:layout_constraintStart_toStartOf="@+id/recyclerView" app:layout_constraintStart_toStartOf="@+id/cardView"
app:layout_constraintEnd_toEndOf="@+id/recyclerView" app:layout_constraintEnd_toEndOf="@+id/cardView"
android:text="@string/blocked_contacts_empty_state" android:text="@string/blocked_contacts_empty_state"
/> />
@ -38,7 +47,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/recyclerView" app:layout_constraintTop_toBottomOf="@+id/cardView"
android:id="@+id/unblockButton" android:id="@+id/unblockButton"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginVertical="@dimen/large_spacing" android:layout_marginVertical="@dimen/large_spacing"
@ -49,6 +58,6 @@
android:id="@+id/nonEmptyStateGroup" android:id="@+id/nonEmptyStateGroup"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:constraint_referenced_ids="unblockButton,recyclerView"/> app:constraint_referenced_ids="unblockButton,cardView"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -7,6 +7,7 @@
android:paddingHorizontal="@dimen/medium_spacing" android:paddingHorizontal="@dimen/medium_spacing"
android:paddingVertical="@dimen/small_spacing" android:paddingVertical="@dimen/small_spacing"
android:gravity="center_vertical" android:gravity="center_vertical"
android:background="?selectableItemBackground"
android:id="@+id/backgroundContainer"> android:id="@+id/backgroundContainer">
<include layout="@layout/view_profile_picture" <include layout="@layout/view_profile_picture"
android:id="@+id/profilePictureView" android:id="@+id/profilePictureView"

View File

@ -112,7 +112,7 @@
<style name="Widget.Session.Button.Common.DestructiveOutline"> <style name="Widget.Session.Button.Common.DestructiveOutline">
<item name="android:background">@drawable/destructive_outline_button_medium_background</item> <item name="android:background">@drawable/destructive_outline_button_medium_background</item>
<item name="android:textColor">@color/destructive</item> <item name="android:textColor">@color/button_destructive</item>
<item name="android:drawableTint">?android:textColorPrimary</item> <item name="android:drawableTint">?android:textColorPrimary</item>
</style> </style>

View File

@ -202,6 +202,6 @@ interface StorageProtocol {
fun removeReaction(emoji: String, messageTimestamp: Long, author: String, notifyUnread: Boolean) fun removeReaction(emoji: String, messageTimestamp: Long, author: String, notifyUnread: Boolean)
fun updateReactionIfNeeded(message: Message, sender: String, openGroupSentTimestamp: Long) fun updateReactionIfNeeded(message: Message, sender: String, openGroupSentTimestamp: Long)
fun deleteReactions(messageId: Long, mms: Boolean) fun deleteReactions(messageId: Long, mms: Boolean)
fun unblock(toUnblock: List<Recipient>) fun unblock(toUnblock: Iterable<Recipient>)
fun blockedContacts(): List<Recipient> fun blockedContacts(): List<Recipient>
} }