Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions app/src/main/kotlin/io/element/android/x/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import io.element.android.x.BuildConfig
import io.element.android.x.R
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.plus
import java.io.File
Expand Down Expand Up @@ -107,11 +106,7 @@ object AppModule {
@Provides
@SingleIn(AppScope::class)
fun providesCoroutineDispatchers(): CoroutineDispatchers {
return CoroutineDispatchers(
io = Dispatchers.IO,
computation = Dispatchers.Default,
main = Dispatchers.Main,
)
return CoroutineDispatchers.Default
}

@Provides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.flatMap
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.designsystem.components.avatar.AvatarData
Expand All @@ -70,6 +71,7 @@ import io.element.android.libraries.matrix.api.core.toThreadId
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.recentemojis.AddRecentEmoji
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.RoomInfo
Expand Down Expand Up @@ -121,6 +123,7 @@ class MessagesPresenter(
private val analyticsService: AnalyticsService,
private val encryptionService: EncryptionService,
private val featureFlagService: FeatureFlagService,
private val addRecentEmoji: AddRecentEmoji,
) : Presenter<MessagesState> {
@AssistedFactory
interface Factory {
Expand Down Expand Up @@ -398,6 +401,7 @@ class MessagesPresenter(
) = launch(dispatchers.io) {
timelineController.invokeOnCurrentTimeline {
toggleReaction(emoji, eventOrTransactionId)
.flatMap { added -> if (added) addRecentEmoji(emoji) else Result.success(Unit) }
.onFailure { Timber.e(it) }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentSetOf

Expand Down Expand Up @@ -186,9 +187,11 @@ fun aReactionSummaryState(

fun aCustomReactionState(
target: CustomReactionState.Target = CustomReactionState.Target.None,
recentEmojis: ImmutableList<String> = persistentListOf(),
eventSink: (CustomReactionEvents) -> Unit = {},
) = CustomReactionState(
target = target,
recentEmojis = recentEmojis,
selectedEmoji = persistentSetOf(),
eventSink = eventSink,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.recentemojis.GetRecentEmojis
import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
Expand Down Expand Up @@ -73,6 +74,7 @@ class DefaultActionListPresenter(
private val userSendFailureFactory: VerifiedUserSendFailureFactory,
private val dateFormatter: DateFormatter,
private val featureFlagService: FeatureFlagService,
private val getRecentEmojis: GetRecentEmojis,
) : ActionListPresenter {
@AssistedFactory
@ContributesBinding(RoomScope::class)
Expand Down Expand Up @@ -153,14 +155,15 @@ class DefaultActionListPresenter(
),
displayEmojiReactions = displayEmojiReactions,
verifiedUserSendFailure = verifiedUserSendFailure,
actions = actions.toImmutableList()
actions = actions.toImmutableList(),
recentEmojis = getRecentEmojis().getOrNull()?.toImmutableList() ?: persistentListOf()
)
} else {
target.value = ActionListState.Target.None
}
}

private suspend fun buildActions(
private fun buildActions(
timelineItem: TimelineItem.Event,
usersEventPermissions: UserEventPermissions,
isDeveloperModeEnabled: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ data class ActionListState(
val event: TimelineItem.Event,
val sentTimeFull: String,
val displayEmojiReactions: Boolean,
val recentEmojis: ImmutableList<String>,
val verifiedUserSendFailure: VerifiedUserSendFailure,
val actions: ImmutableList<TimelineItemAction>,
) : Target
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList

open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
Expand All @@ -41,6 +42,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = aTimelineItemActionList(),
recentEmojis = persistentListOf(),
)
),
anActionListState(
Expand All @@ -56,6 +58,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList(
copyAction = TimelineItemAction.CopyCaption,
),
recentEmojis = persistentListOf(),
)
),
anActionListState(
Expand All @@ -70,6 +73,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList(
copyAction = TimelineItemAction.CopyCaption,
),
recentEmojis = persistentListOf(),
)
),
anActionListState(
Expand All @@ -84,6 +88,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList(
copyAction = null,
),
recentEmojis = persistentListOf(),
)
),
anActionListState(
Expand All @@ -98,6 +103,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList(
copyAction = TimelineItemAction.CopyCaption,
),
recentEmojis = persistentListOf(),
)
),
anActionListState(
Expand All @@ -112,6 +118,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList(
copyAction = null,
),
recentEmojis = persistentListOf(),
)
),
anActionListState(
Expand All @@ -124,6 +131,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = aTimelineItemActionList(),
recentEmojis = persistentListOf(),
)
),
anActionListState(
Expand All @@ -136,6 +144,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = false,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = aTimelineItemActionList(),
recentEmojis = persistentListOf(),
),
),
anActionListState(
Expand All @@ -148,6 +157,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = false,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = aTimelineItemPollActionList(),
recentEmojis = persistentListOf(),
),
),
anActionListState(
Expand All @@ -160,6 +170,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = aTimelineItemActionList(),
recentEmojis = persistentListOf(),
)
),
anActionListState(
Expand All @@ -169,6 +180,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = true,
verifiedUserSendFailure = anUnsignedDeviceSendFailure(),
actions = aTimelineItemActionList(),
recentEmojis = persistentListOf(),
)
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
Expand All @@ -35,6 +38,10 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
Expand Down Expand Up @@ -90,6 +97,8 @@ import io.element.android.libraries.matrix.ui.messages.sender.SenderName
import io.element.android.libraries.matrix.ui.messages.sender.SenderNameMode
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList

@OptIn(ExperimentalMaterial3Api::class)
@Composable
Expand Down Expand Up @@ -218,6 +227,7 @@ private fun ActionListViewContent(
if (target.displayEmojiReactions) {
item {
EmojiReactionsRow(
recentEmojis = target.recentEmojis,
highlightedEmojis = target.event.reactionsState.highlightedKeys,
onEmojiReactionClick = onEmojiReactionClick,
onCustomReactionClick = onCustomReactionClick,
Expand Down Expand Up @@ -335,43 +345,67 @@ private fun MessageSummary(
}

private val emojiRippleRadius = 24.dp
private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏")

@Composable
private fun EmojiReactionsRow(
recentEmojis: ImmutableList<String>,
highlightedEmojis: ImmutableList<String>,
onEmojiReactionClick: (String) -> Unit,
onCustomReactionClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = modifier.padding(horizontal = 24.dp, vertical = 16.dp)
modifier = modifier.padding(end = 16.dp, top = 16.dp, bottom = 16.dp),
) {
// TODO use most recently used emojis here when available from the Rust SDK
val defaultEmojis = sequenceOf(
"👍️",
"👎️",
"🔥",
"❤️",
"👏"
)
for (emoji in defaultEmojis) {
val isHighlighted = highlightedEmojis.contains(emoji)
EmojiButton(
modifier = Modifier
// Make it appear after the more useful actions for the accessibility service
.semantics {
traversalIndex = 1f
},
emoji = emoji,
isHighlighted = isHighlighted,
onClick = onEmojiReactionClick
)
val backgroundColor = ElementTheme.colors.bgCanvasDefault

val emojis = remember(recentEmojis) {
(suggestedEmojis + recentEmojis.filter { it !in suggestedEmojis })
.take(100)
.toImmutableList()
}
Box(

LazyRow(
modifier = Modifier
.size(48.dp),
contentAlignment = Alignment.Center,
.weight(1f, fill = true)
.drawWithContent {
val gradientWidth = 24.dp.toPx()
val width = size.width
drawContent()

drawRect(
brush = Brush.horizontalGradient(
0.0f to Color.Transparent,
1.0f to backgroundColor,
startX = width - gradientWidth,
endX = width,
),
topLeft = Offset(width - gradientWidth, 0f),
size = Size(gradientWidth, size.height)
)
},
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(emojis) { emoji ->
val isHighlighted = highlightedEmojis.contains(emoji)
EmojiButton(
modifier = Modifier
// Make it appear after the more useful actions for the accessibility service
.semantics {
traversalIndex = 1f
},
emoji = emoji,
isHighlighted = isHighlighted,
onClick = onEmojiReactionClick
)
}
}

Box(
modifier = Modifier.padding(end = 10.dp).requiredSize(48.dp),
contentAlignment = Alignment.CenterEnd,
) {
Icon(
imageVector = CompoundIcons.ReactionAdd(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.compose.ui.Modifier
import io.element.android.emojibasebindings.Emoji
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.EmojiPicker
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.EmojiPickerPresenter
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
import io.element.android.libraries.designsystem.theme.components.hide
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
Expand Down Expand Up @@ -50,7 +51,13 @@ fun CustomReactionBottomSheet(
sheetState = sheetState,
modifier = modifier
) {
val presenter = remember { EmojiPickerPresenter(target.emojibaseStore) }
val presenter = remember {
EmojiPickerPresenter(
emojibaseStore = target.emojibaseStore,
recentEmojis = state.recentEmojis,
coroutineDispatchers = CoroutineDispatchers.Default,
)
}
EmojiPicker(
onSelectEmoji = ::onEmojiSelectedDismiss,
state = presenter.present(),
Expand Down
Loading
Loading