Skip to content

Commit

Permalink
Merge pull request #5204 from vector-im/feature/fga/reactions_ui_impr…
Browse files Browse the repository at this point in the history
…ovements

Feature/fga/reactions UI improvements
  • Loading branch information
ganfra authored Feb 11, 2022
2 parents 5af56f6 + 0a52651 commit f1376ea
Show file tree
Hide file tree
Showing 16 changed files with 205 additions and 47 deletions.
1 change: 1 addition & 0 deletions changelog.d/5204.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve UI of reactions in timeline, including quick add reaction.
11 changes: 11 additions & 0 deletions library/ui-styles/src/main/res/values/styles_timeline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,15 @@
<item name="android:backgroundTint">?vctr_content_quinary</item>
</style>

<style name="TimelineReactionView">
<item name="android:paddingStart">6dp</item>
<item name="android:paddingEnd">6dp</item>
<item name="android:paddingTop">1dp</item>
<item name="android:paddingBottom">1dp</item>
<item name="android:minHeight">28dp</item>
<item name="android:minWidth">40dp</item>
<item name="android:gravity">center</item>
</style>


</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ internal class UIEchoManager(private val listener: Listener) {
return existingState != sendState
}

fun onLocalEchoCreated(timelineEvent: TimelineEvent): Boolean {
fun onLocalEchoCreated(timelineEvent: TimelineEvent): Boolean {
when (timelineEvent.root.getClearType()) {
EventType.REDACTION -> {
}
EventType.REACTION -> {
EventType.REACTION -> {
val content: ReactionContent? = timelineEvent.root.content?.toModel<ReactionContent>()
if (RelationType.ANNOTATION == content?.relatesTo?.type) {
val reaction = content.relatesTo.key
Expand Down Expand Up @@ -104,8 +104,8 @@ internal class UIEchoManager(private val listener: Listener) {
val updateReactions = existingAnnotationSummary.reactionsSummary.toMutableList()

contents.forEach { uiEchoReaction ->
val existing = updateReactions.firstOrNull { it.key == uiEchoReaction.reaction }
if (existing == null) {
val indexOfExistingReaction = updateReactions.indexOfFirst { it.key == uiEchoReaction.reaction }
if (indexOfExistingReaction == -1) {
// just add the new key
ReactionAggregatedSummary(
key = uiEchoReaction.reaction,
Expand All @@ -117,6 +117,7 @@ internal class UIEchoManager(private val listener: Listener) {
).let { updateReactions.add(it) }
} else {
// update Existing Key
val existing = updateReactions[indexOfExistingReaction]
if (!existing.localEchoEvents.contains(uiEchoReaction.localEchoId)) {
updateReactions.remove(existing)
// only update if echo is not yet there
Expand All @@ -128,7 +129,7 @@ internal class UIEchoManager(private val listener: Listener) {
sourceEvents = existing.sourceEvents,
localEchoEvents = existing.localEchoEvents + uiEchoReaction.localEchoId

).let { updateReactions.add(it) }
).let { updateReactions.add(indexOfExistingReaction, it) }
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions vector/src/main/java/im/vector/app/core/extensions/Context.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ package im.vector.app.core.extensions

import android.content.Context
import android.graphics.drawable.Drawable
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ImageSpan
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
Expand All @@ -34,6 +37,16 @@ fun Context.singletonEntryPoint(): SingletonEntryPoint {
return EntryPoints.get(applicationContext, SingletonEntryPoint::class.java)
}

fun Context.getDrawableAsSpannable(@DrawableRes drawableRes: Int, alignment: Int = ImageSpan.ALIGN_BOTTOM): Spannable {
return SpannableString(" ").apply {
val span = ContextCompat.getDrawable(this@getDrawableAsSpannable, drawableRes)?.let {
it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
ImageSpan(it, alignment)
}
setSpan(span, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
}
}

fun Context.getResTintedDrawable(@DrawableRes drawableRes: Int, @ColorRes tint: Int, @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1f): Drawable? {
return getTintedDrawable(drawableRes, ContextCompat.getColor(this, tint), alpha)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1915,6 +1915,10 @@ class TimelineFragment @Inject constructor(
timelineViewModel.handle(RoomDetailAction.LoadMoreTimelineEvents(direction))
}

override fun onAddMoreReaction(event: TimelineEvent) {
openEmojiReactionPicker(event.eventId)
}

override fun onEventCellClicked(informationData: MessageInformationData, messageContent: Any?, view: View, isRootThreadEvent: Boolean) {
when (messageContent) {
is MessageVerificationRequestContent -> {
Expand Down Expand Up @@ -2119,7 +2123,7 @@ class TimelineFragment @Inject constructor(
openRoomMemberProfile(action.userId)
}
is EventSharedAction.AddReaction -> {
emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), action.eventId))
openEmojiReactionPicker(action.eventId)
}
is EventSharedAction.ViewReactions -> {
ViewReactionsBottomSheet.newInstance(timelineArgs.roomId, action.messageInformationData)
Expand Down Expand Up @@ -2241,6 +2245,10 @@ class TimelineFragment @Inject constructor(
}
}

private fun openEmojiReactionPicker(eventId: String) {
emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), eventId))
}

private fun askConfirmationToEndPoll(eventId: String) {
MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Vector_MaterialAlertDialog)
.setTitle(R.string.end_poll_confirmation_title)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFact
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.ReactionsSummaryFactory
import im.vector.app.features.home.room.detail.timeline.helper.TimelineControllerInterceptorHelper
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
Expand Down Expand Up @@ -86,7 +87,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
@TimelineEventControllerHandler
private val backgroundHandler: Handler,
private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper,
private val readReceiptsItemFactory: ReadReceiptsItemFactory
private val readReceiptsItemFactory: ReadReceiptsItemFactory,
private val reactionListFactory: ReactionsSummaryFactory
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor {

/**
Expand Down Expand Up @@ -138,6 +140,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
fun getPreviewUrlRetriever(): PreviewUrlRetriever

fun onVoiceControlButtonClicked(eventId: String, messageAudioContent: MessageAudioContent)

fun onAddMoreReaction(event: TimelineEvent)
}

interface ReactionPillCallback {
Expand Down Expand Up @@ -283,13 +287,15 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
super.onAttachedToRecyclerView(recyclerView)
timeline?.addListener(this)
timelineMediaSizeProvider.recyclerView = recyclerView
reactionListFactory.onRequestBuild = { requestModelBuild() }
}

override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
timelineMediaSizeProvider.recyclerView = null
contentUploadStateTrackerBinder.clear()
contentDownloadStateTrackerBinder.clear()
timeline?.removeListener(this)
reactionListFactory.onRequestBuild = null
super.onDetachedFromRecyclerView(recyclerView)
}

Expand Down Expand Up @@ -383,7 +389,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
val event = currentSnapshot[position]
val nextEvent = currentSnapshot.nextOrNull(position)
// Should be build if not cached or if model should be refreshed
if (modelCache[position] == null || modelCache[position]?.isCacheable(partialState) == false) {
if (modelCache[position] == null || modelCache[position]?.isCacheable(partialState) == false || reactionListFactory.needsRebuild(event)) {
val prevEvent = currentSnapshot.prevOrNull(position)
val prevDisplayableEvent = currentSnapshot.subList(0, position).lastOrNull {
timelineEventVisibilityHelper.shouldShowEvent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory
Expand All @@ -50,7 +49,8 @@ import javax.inject.Inject
*/
class MessageInformationDataFactory @Inject constructor(private val session: Session,
private val dateFormatter: VectorDateFormatter,
private val messageLayoutFactory: TimelineMessageLayoutFactory) {
private val messageLayoutFactory: TimelineMessageLayoutFactory,
private val reactionsSummaryFactory: ReactionsSummaryFactory) {

fun create(params: TimelineItemFactoryParams): MessageInformationData {
val event = params.event
Expand Down Expand Up @@ -93,11 +93,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
avatarUrl = event.senderInfo.avatarUrl,
memberName = event.senderInfo.disambiguatedDisplayName,
messageLayout = messageLayout,
orderedReactionList = event.annotations?.reactionsSummary
// ?.filter { isSingleEmoji(it.key) }
?.map {
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
},
reactionsSummary = reactionsSummaryFactory.create(event, params.callback),
pollResponseAggregatedSummary = event.annotations?.pollResponseSummary?.let {
PollResponseData(
myVote = it.aggregatedContent?.myVote,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.app.features.home.room.detail.timeline.helper

import dagger.hilt.android.scopes.ActivityScoped
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData
import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryData
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import javax.inject.Inject

@ActivityScoped
class ReactionsSummaryFactory @Inject constructor() {

var onRequestBuild: (() -> Unit)? = null
private val showAllReactionsByEvent = HashSet<String>()
private val eventsRequestingBuild = HashSet<String>()

fun needsRebuild(event: TimelineEvent): Boolean {
return eventsRequestingBuild.remove(event.eventId)
}

fun create(event: TimelineEvent, callback: TimelineEventController.Callback?): ReactionsSummaryData {
val eventId = event.eventId
val showAllStates = showAllReactionsByEvent.contains(eventId)
val reactions = event.annotations?.reactionsSummary
?.map {
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
}
return ReactionsSummaryData(
reactions = reactions,
showAll = showAllStates,
onShowMoreClicked = {
showAllReactionsByEvent.add(eventId)
onRequestBuild(eventId)
},
onShowLessClicked = {
showAllReactionsByEvent.remove(eventId)
onRequestBuild(eventId)
},
onAddMoreClicked = {
callback?.onAddMoreReaction(event)
}
)
}

private fun onRequestBuild(eventId: String) {
eventsRequestingBuild.add(eventId)
onRequestBuild?.invoke()
}
}
Loading

0 comments on commit f1376ea

Please sign in to comment.