Skip to content

Commit 4005f7c

Browse files
committed
Add DeletedForMeDecorator and related functionality for user-specific message deletion in XML SDK
1 parent 491e2ba commit 4005f7c

File tree

8 files changed

+186
-6
lines changed

8 files changed

+186
-6
lines changed

stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/ChatFragment.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,14 @@ class ChatFragment : Fragment() {
325325
CustomMessageOptions.actionHandler(
326326
onTranslate = { chatViewModel.onAction(ChatViewModel.Action.Translate(it)) },
327327
onClearTranslation = { chatViewModel.onAction(ChatViewModel.Action.ClearTranslation(it)) },
328+
onDeleteForMe = {
329+
ConfirmationDialogFragment.newDeleteMessageForMeInstance(requireContext())
330+
.apply {
331+
confirmClickListener = ConfirmationDialogFragment.ConfirmClickListener {
332+
chatViewModel.onAction(ChatViewModel.Action.DeleteMessageForMe(it))
333+
}
334+
}.show(parentFragmentManager, null)
335+
},
328336
),
329337
)
330338
}

stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/ChatViewModel.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import io.getstream.result.Result
3737
import kotlinx.coroutines.Dispatchers
3838
import kotlinx.coroutines.flow.StateFlow
3939
import kotlinx.coroutines.flow.filterNotNull
40-
import kotlinx.coroutines.flow.firstOrNull
4140
import kotlinx.coroutines.flow.flatMapLatest
4241
import kotlinx.coroutines.launch
4342
import kotlinx.coroutines.withContext
@@ -99,6 +98,10 @@ class ChatViewModel(
9998
is Action.ClearTranslation -> {
10099
clearTranslation(action.message)
101100
}
101+
is Action.DeleteMessageForMe -> {
102+
chatClient.deleteMessageForMe(messageId = action.message.id)
103+
.enqueue()
104+
}
102105
}
103106
}
104107

@@ -163,6 +166,7 @@ class ChatViewModel(
163166
class HeaderClicked(val members: List<Member>) : Action()
164167
class Translate(val message: Message) : Action()
165168
class ClearTranslation(val message: Message) : Action()
169+
class DeleteMessageForMe(val message: Message) : Action()
166170
}
167171

168172
sealed class NavigationEvent {

stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/messagelist/decorator/CustomDecoratorProvider.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,6 @@ class CustomDecoratorProvider(
5555
getLanguageDisplayName: (code: String) -> String,
5656
) : DecoratorProvider {
5757
override val decorators by lazy {
58-
listOf(ForwardedDecorator())
58+
listOf(ForwardedDecorator(), DeletedForMeDecorator())
5959
}
6060
}

stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/messagelist/decorator/CustomDecoratorType.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.dec
2020

2121
enum class CustomDecoratorType : Decorator.Type {
2222
FORWARDED,
23+
DELETED_FOR_ME,
2324
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright (c) 2014-2024 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.chat.ui.sample.feature.chat.messagelist.decorator
18+
19+
import android.view.Gravity
20+
import android.view.View
21+
import android.view.ViewGroup
22+
import android.view.ViewGroup.MarginLayoutParams.WRAP_CONTENT
23+
import android.widget.TextView
24+
import androidx.appcompat.widget.LinearLayoutCompat
25+
import androidx.core.content.ContextCompat
26+
import androidx.core.widget.TextViewCompat
27+
import io.getstream.chat.android.ui.common.utils.Utils
28+
import io.getstream.chat.android.ui.feature.messages.list.adapter.MessageListItem
29+
import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.decorator.BaseDecorator
30+
import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.decorator.Decorator
31+
import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.impl.CustomAttachmentsViewHolder
32+
import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.impl.FileAttachmentsViewHolder
33+
import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.impl.GiphyAttachmentViewHolder
34+
import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.impl.LinkAttachmentsViewHolder
35+
import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.impl.MediaAttachmentsViewHolder
36+
import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.impl.MessageDeletedViewHolder
37+
import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.impl.MessagePlainTextViewHolder
38+
import io.getstream.chat.ui.sample.R
39+
40+
class DeletedForMeDecorator : BaseDecorator() {
41+
42+
override val type: Decorator.Type = CustomDecoratorType.DELETED_FOR_ME
43+
44+
private val deletedForMeViewId = View.generateViewId()
45+
46+
override fun decorateDeletedMessage(viewHolder: MessageDeletedViewHolder, data: MessageListItem.MessageItem) {
47+
val container = (viewHolder.binding.footnote as View)
48+
.findViewById<ViewGroup>(R.id.messageFooterContainerInner)
49+
setupDeletedForMeView(container, data)
50+
}
51+
52+
override fun decorateCustomAttachmentsMessage(
53+
viewHolder: CustomAttachmentsViewHolder,
54+
data: MessageListItem.MessageItem,
55+
) {
56+
/* no-op */
57+
}
58+
59+
override fun decorateGiphyAttachmentMessage(
60+
viewHolder: GiphyAttachmentViewHolder,
61+
data: MessageListItem.MessageItem,
62+
) {
63+
/* no-op */
64+
}
65+
66+
override fun decorateFileAttachmentsMessage(
67+
viewHolder: FileAttachmentsViewHolder,
68+
data: MessageListItem.MessageItem,
69+
) {
70+
/* no-op */
71+
}
72+
73+
override fun decorateMediaAttachmentsMessage(
74+
viewHolder: MediaAttachmentsViewHolder,
75+
data: MessageListItem.MessageItem,
76+
) {
77+
/* no-op */
78+
}
79+
80+
override fun decoratePlainTextMessage(viewHolder: MessagePlainTextViewHolder, data: MessageListItem.MessageItem) {
81+
/* no-op */
82+
}
83+
84+
override fun decorateLinkAttachmentsMessage(
85+
viewHolder: LinkAttachmentsViewHolder,
86+
data: MessageListItem.MessageItem,
87+
) {
88+
/* no-op */
89+
}
90+
91+
private fun setupDeletedForMeView(
92+
container: ViewGroup,
93+
data: MessageListItem.MessageItem,
94+
) {
95+
var textView = container.findViewById<TextView>(deletedForMeViewId)
96+
if (textView == null && data.message.deletedForMe) {
97+
textView = createTextView(container)
98+
container.addView(textView, 0)
99+
}
100+
}
101+
102+
private fun createTextView(container: ViewGroup) = TextView(container.context).apply {
103+
id = deletedForMeViewId
104+
layoutParams = LinearLayoutCompat.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
105+
gravity = Gravity.BOTTOM
106+
marginStart = Utils.dpToPx(MARGIN_START_DP)
107+
marginEnd = Utils.dpToPx(MARGIN_END_DP)
108+
}
109+
TextViewCompat.setTextAppearance(this, R.style.StreamUiTextAppearance_Footnote)
110+
setTextColor(ContextCompat.getColor(container.context, R.color.stream_ui_text_color_primary))
111+
text = "Deleted only for me"
112+
}
113+
}
114+
115+
private const val MARGIN_START_DP = 8
116+
private const val MARGIN_END_DP = 8

stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/messagelist/options/CustomMessageOptions.kt

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import io.getstream.chat.android.client.utils.message.isSystem
2323
import io.getstream.chat.android.models.Message
2424
import io.getstream.chat.android.models.User
2525
import io.getstream.chat.android.ui.common.state.messages.CustomAction
26+
import io.getstream.chat.android.ui.common.utils.canDeleteMessage
2627
import io.getstream.chat.android.ui.feature.messages.list.MessageListView
2728
import io.getstream.chat.android.ui.feature.messages.list.MessageListViewStyle
2829
import io.getstream.chat.android.ui.feature.messages.list.options.message.MessageOptionItem
@@ -46,9 +47,12 @@ internal object CustomMessageOptions {
4647
fun actionHandler(
4748
onTranslate: (Message) -> Unit,
4849
onClearTranslation: (Message) -> Unit,
49-
): MessageListView.CustomActionHandler {
50-
return CustomMessageOptionHandler(onTranslate, onClearTranslation)
51-
}
50+
onDeleteForMe: (Message) -> Unit,
51+
): MessageListView.CustomActionHandler = CustomMessageOptionHandler(
52+
onTranslate,
53+
onClearTranslation,
54+
onDeleteForMe,
55+
)
5256
}
5357

5458
/**
@@ -57,13 +61,16 @@ internal object CustomMessageOptions {
5761
private class CustomMessageOptionHandler(
5862
private val onTranslate: (Message) -> Unit,
5963
private val onClearTranslation: (Message) -> Unit,
64+
private val onDeleteForMe: (Message) -> Unit,
6065
) : MessageListView.CustomActionHandler {
6166

6267
override fun onCustomAction(message: Message, extraProperties: Map<String, Any>) {
6368
if (TranslationOption.isTranslateAction(extraProperties)) {
6469
onTranslate(message)
6570
} else if (TranslationOption.isClearTranslationAction(extraProperties)) {
6671
onClearTranslation(message)
72+
} else if (DeleteForMeOption.isDeleteForMeAction(extraProperties)) {
73+
onDeleteForMe(message)
6774
}
6875
}
6976
}
@@ -110,6 +117,15 @@ private class CustomMessageOptionItemsFactory(
110117
),
111118
)
112119
}
120+
val canDeleteMessage = canDeleteMessage(
121+
deleteMessageEnabled = style.deleteMessageEnabled,
122+
currentUser = currentUser,
123+
message = selectedMessage,
124+
ownCapabilities = ownCapabilities,
125+
)
126+
if (canDeleteMessage) {
127+
messageOptions.add(DeleteForMeOption(context, style, selectedMessage))
128+
}
113129
return messageOptions
114130
}
115131
}
@@ -161,3 +177,29 @@ private class TranslationOption private constructor() {
161177
}
162178
}
163179
}
180+
181+
private object DeleteForMeOption {
182+
183+
private const val ACTION = "action"
184+
private const val DELETE_FOR_ME = "delete_for_me"
185+
186+
operator fun invoke(
187+
context: Context,
188+
style: MessageListViewStyle,
189+
message: Message,
190+
): MessageOptionItem {
191+
val icon = ContextCompat.getDrawable(context, style.deleteIcon) ?: error("Drawable not found")
192+
return MessageOptionItem(
193+
optionText = "Delete Message for Me",
194+
optionIcon = icon,
195+
messageAction = CustomAction(
196+
message = message,
197+
extraProperties = mapOf(ACTION to DELETE_FOR_ME),
198+
),
199+
isWarningItem = true,
200+
)
201+
}
202+
203+
fun isDeleteForMeAction(extra: Map<String, Any>): Boolean =
204+
extra[ACTION] == DELETE_FOR_ME
205+
}

stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/common/ConfirmationDialogFragment.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,15 @@ internal class ConfirmationDialogFragment : BottomSheetDialogFragment() {
216216
hasConfirmButton = false,
217217
)
218218

219+
fun newDeleteMessageForMeInstance(context: Context): ConfirmationDialogFragment = newInstance(
220+
iconResId = R.drawable.ic_delete,
221+
iconTintResId = R.color.red,
222+
title = "Delete for Me",
223+
description = "Are you sure you want to delete this message for you?",
224+
confirmText = context.getString(R.string.stream_ui_message_list_delete_confirmation_positive_button),
225+
cancelText = context.getString(R.string.cancel),
226+
)
227+
219228
fun newInstance(
220229
@DrawableRes iconResId: Int,
221230
@ColorRes iconTintResId: Int,

stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/decorator/internal/FootnoteDecorator.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ internal class FootnoteDecorator(
314314
}
315315

316316
data.isBottomPosition() &&
317-
data.message.isDeleted() &&
317+
data.message.isDeleted() && !data.message.deletedForMe &&
318318
deletedMessageVisibilityHandler() == DeletedMessageVisibility.VISIBLE_FOR_CURRENT_USER -> {
319319
showOnlyVisibleToYou(textView, style)
320320
}

0 commit comments

Comments
 (0)