Allow users to remove viewers directly from stories.

fork-5.53.8
Alex Hart 2022-07-28 13:44:20 -03:00 zatwierdzone przez Greyson Parrelli
rodzic 2674fd2df4
commit 8f12b2041a
9 zmienionych plików z 133 dodań i 6 usunięć

Wyświetl plik

@ -1,9 +1,13 @@
package org.thoughtcrime.securesms.stories.viewer.views
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import org.signal.core.util.DimensionUnit
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.AvatarImageView
import org.thoughtcrime.securesms.components.menu.ActionItem
import org.thoughtcrime.securesms.components.menu.SignalContextMenu
import org.thoughtcrime.securesms.components.settings.PreferenceModel
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
@ -21,7 +25,10 @@ object StoryViewItem {
}
class Model(
val storyViewItemData: StoryViewItemData
val storyViewItemData: StoryViewItemData,
val canRemoveMember: Boolean,
val goToChat: (Model) -> Unit,
val removeFromStory: (Model) -> Unit
) : PreferenceModel<Model>() {
override fun areItemsTheSame(newItem: Model): Boolean {
return storyViewItemData.recipient == newItem.storyViewItemData.recipient
@ -30,6 +37,7 @@ object StoryViewItem {
override fun areContentsTheSame(newItem: Model): Boolean {
return storyViewItemData == newItem.storyViewItemData &&
storyViewItemData.recipient.hasSameContent(newItem.storyViewItemData.recipient) &&
canRemoveMember == newItem.canRemoveMember &&
super.areContentsTheSame(newItem)
}
}
@ -44,10 +52,47 @@ object StoryViewItem {
avatarView.setAvatar(model.storyViewItemData.recipient)
nameView.text = model.storyViewItemData.recipient.getDisplayName(context)
viewedAtView.text = formatDate(model.storyViewItemData.timeViewedInMillis)
itemView.setOnClickListener {
showContextMenu(model)
}
}
private fun formatDate(dateInMilliseconds: Long): String {
return DateUtils.formatDateWithDayOfWeek(Locale.getDefault(), dateInMilliseconds)
}
private fun showContextMenu(model: Model) {
itemView.isSelected = true
val actions = mutableListOf<ActionItem>()
actions.add(
ActionItem(
iconRes = R.drawable.ic_open_24_tinted,
title = context.getString(R.string.StoriesLandingItem__go_to_chat),
action = {
model.goToChat(model)
}
)
)
if (model.canRemoveMember) {
actions.add(
ActionItem(
iconRes = R.drawable.ic_minus_circle_20,
title = context.getString(R.string.StoryViewItem__remove_viewer),
action = {
model.removeFromStory(model)
}
)
)
}
SignalContextMenu.Builder(itemView, itemView.rootView as ViewGroup)
.offsetY(DimensionUnit.DP.toPixels(16f).toInt())
.onDismiss { itemView.isSelected = false }
.show(actions)
}
}
}

Wyświetl plik

@ -4,12 +4,15 @@ import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.stories.viewer.reply.StoryViewsAndRepliesPagerChild
import org.thoughtcrime.securesms.stories.viewer.reply.StoryViewsAndRepliesPagerParent
import org.thoughtcrime.securesms.util.fragments.findListener
@ -66,12 +69,37 @@ class StoryViewsFragment :
private fun getConfiguration(state: StoryViewsState): DSLConfiguration {
return configure {
state.views.forEach {
customPref(StoryViewItem.Model(it))
state.views.forEach { storyViewItemData ->
customPref(
StoryViewItem.Model(
storyViewItemData = storyViewItemData,
canRemoveMember = state.storyRecipient?.isDistributionList ?: false,
goToChat = {
val chatIntent = ConversationIntents.createBuilder(requireContext(), it.storyViewItemData.recipient.id, -1L).build()
startActivity(chatIntent)
},
removeFromStory = {
if (state.storyRecipient?.isDistributionList == true) {
confirmRemoveFromStory(it.storyViewItemData.recipient, state.storyRecipient)
}
}
)
)
}
}
}
private fun confirmRemoveFromStory(user: Recipient, story: Recipient) {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.StoryViewsFragment__remove_viewer)
.setMessage(getString(R.string.StoryViewsFragment__s_will_still_be_able, user.getShortDisplayName(requireContext()), story.getDisplayName(requireContext())))
.setPositiveButton(R.string.StoryViewsFragment__remove) { _, _ ->
viewModel.removeUserFromStory(user, story)
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
}
companion object {
private const val ARG_STORY_ID = "arg.story.id"

Wyświetl plik

@ -1,7 +1,10 @@
package org.thoughtcrime.securesms.stories.viewer.views
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.database.DatabaseObserver
import org.thoughtcrime.securesms.database.GroupReceiptDatabase
import org.thoughtcrime.securesms.database.SignalDatabase
@ -11,8 +14,20 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
class StoryViewsRepository {
companion object {
private val TAG = Log.tag(StoryViewsRepository::class.java)
}
fun isReadReceiptsEnabled(): Boolean = TextSecurePreferences.isReadReceiptsEnabled(ApplicationDependencies.getApplication())
fun getStoryRecipient(storyId: Long): Single<Recipient> {
return Single.fromCallable {
val record = SignalDatabase.mms.getMessageRecord(storyId)
record.recipient
}.subscribeOn(Schedulers.io())
}
fun getViews(storyId: Long): Observable<List<StoryViewItemData>> {
return Observable.create<List<StoryViewItemData>> { emitter ->
fun refresh() {
@ -38,4 +53,15 @@ class StoryViewsRepository {
refresh()
}.subscribeOn(Schedulers.io())
}
fun removeUserFromStory(user: Recipient, story: Recipient): Completable {
return Completable.fromAction {
val distributionListRecord = SignalDatabase.distributionLists.getList(story.requireDistributionListId())!!
if (user.id in distributionListRecord.members) {
SignalDatabase.distributionLists.excludeFromStory(user.id, distributionListRecord)
} else {
Log.w(TAG, "User is no longer in the distribution list.")
}
}
}
}

Wyświetl plik

@ -1,7 +1,10 @@
package org.thoughtcrime.securesms.stories.viewer.views
import org.thoughtcrime.securesms.recipients.Recipient
data class StoryViewsState(
val loadState: LoadState = LoadState.INIT,
val storyRecipient: Recipient? = null,
val views: List<StoryViewItemData> = emptyList()
) {
enum class LoadState {

Wyświetl plik

@ -5,17 +5,24 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.livedata.Store
class StoryViewsViewModel(private val storyId: Long, private val repository: StoryViewsRepository) : ViewModel() {
private val store = Store(StoryViewsState(StoryViewsState.LoadState.INIT))
private val store = Store(StoryViewsState())
private val disposables = CompositeDisposable()
val state: LiveData<StoryViewsState> = store.stateLiveData
fun refresh() {
if (repository.isReadReceiptsEnabled()) {
disposables += repository.getStoryRecipient(storyId).subscribe { storyRecipient ->
store.update {
it.copy(storyRecipient = storyRecipient)
}
}
disposables += repository.getViews(storyId).subscribe { data ->
store.update {
it.copy(
@ -37,6 +44,10 @@ class StoryViewsViewModel(private val storyId: Long, private val repository: Sto
disposables.clear()
}
fun removeUserFromStory(user: Recipient, story: Recipient) {
repository.removeUserFromStory(user, story).subscribe()
}
class Factory(
private val storyId: Long,
private val repository: StoryViewsRepository

Wyświetl plik

@ -4,8 +4,10 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dsl_settings_gutter"
android:minHeight="64dp">
android:layout_marginHorizontal="@dimen/selectable_list_item_margin"
android:background="@drawable/selectable_list_item_background"
android:minHeight="64dp"
android:paddingHorizontal="@dimen/selectable_list_item_padding">
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/avatar"

Wyświetl plik

@ -20,6 +20,8 @@
<dimen name="media_overview_toggle_gutter">10dp</dimen>
<dimen name="wallpaper_selection_gutter">16dp</dimen>
<dimen name="safety_number_recipient_row_item_gutter">12dp</dimen>
<dimen name="selectable_list_item_margin">16dp</dimen>
<dimen name="selectable_list_item_padding">8dp</dimen>
<dimen name="chat_colors_preview_bubble_max_width">260dp</dimen>

Wyświetl plik

@ -200,6 +200,8 @@
<dimen name="media_overview_toggle_gutter">2dp</dimen>
<dimen name="wallpaper_selection_gutter">8dp</dimen>
<dimen name="safety_number_recipient_row_item_gutter">4dp</dimen>
<dimen name="selectable_list_item_margin">8dp</dimen>
<dimen name="selectable_list_item_padding">8dp</dimen>
<dimen name="chat_colors_preview_bubble_max_width">240dp</dimen>

Wyświetl plik

@ -4628,6 +4628,14 @@
<string name="StoryViewsFragment__enable_read_receipts_to_see_whos_viewed_your_story">Enable read receipts to see who\'s viewed your stories.</string>
<!-- Button label displayed when user has disabled receipts -->
<string name="StoryViewsFragment__go_to_settings">Go to settings</string>
<!-- Dialog action to remove viewer from a story -->
<string name="StoryViewsFragment__remove">Remove</string>
<!-- Dialog title when removing a viewer from a story -->
<string name="StoryViewsFragment__remove_viewer">Remove viewer?</string>
<!-- Dialog message when removing a viewer from a story -->
<string name="StoryViewsFragment__s_will_still_be_able">%1$s will still be able to view this post, but will not be able to view any future posts you share to %2$s.</string>
<!-- Story View context menu action to remove them from a story -->
<string name="StoryViewItem__remove_viewer">Remove viewer</string>
<!-- Displayed when a story has no replies yet -->
<string name="StoryGroupReplyFragment__no_replies_yet">No replies yet</string>
<!-- Displayed for each user that reacted to a story when viewing replies -->