diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1c5cb01b95..2821fcbd04 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -218,7 +218,7 @@ dependencies { implementation(libs.network.okhttp.logging) implementation(libs.serialization.json) - implementation(libs.vanniktech.emoji) + implementation(libs.matrix.emojibase.bindings) implementation(libs.dagger) kapt(libs.dagger.compiler) diff --git a/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt b/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt index da8592771c..542ae7090d 100644 --- a/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt +++ b/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt @@ -23,7 +23,6 @@ import io.element.android.x.di.AppComponent import io.element.android.x.di.DaggerAppComponent import io.element.android.x.info.logApplicationInfo import io.element.android.x.initializer.CrashInitializer -import io.element.android.x.initializer.EmojiInitializer import io.element.android.x.initializer.TracingInitializer class ElementXApplication : Application(), DaggerComponentOwner { @@ -39,7 +38,6 @@ class ElementXApplication : Application(), DaggerComponentOwner { AppInitializer.getInstance(this).apply { initializeComponent(CrashInitializer::class.java) initializeComponent(TracingInitializer::class.java) - initializeComponent(EmojiInitializer::class.java) } logApplicationInfo() } diff --git a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt index a1d0b50522..f8ab5532b0 100644 --- a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt +++ b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt @@ -23,6 +23,8 @@ import androidx.preference.PreferenceManager import com.squareup.anvil.annotations.ContributesTo import dagger.Module import dagger.Provides +import io.element.android.features.messages.impl.timeline.components.customreaction.DefaultEmojibaseProvider +import io.element.android.features.messages.impl.timeline.components.customreaction.EmojibaseProvider import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType @@ -105,4 +107,10 @@ object AppModule { fun provideSnackbarDispatcher(): SnackbarDispatcher { return SnackbarDispatcher() } + + @Provides + @SingleIn(AppScope::class) + fun providesEmojibaseProvider(@ApplicationContext context: Context): EmojibaseProvider { + return DefaultEmojibaseProvider(context) + } } diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 71030c3ca0..1a61a2d8b6 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -61,7 +61,7 @@ dependencies { implementation(libs.accompanist.systemui) implementation(libs.vanniktech.blurhash) implementation(libs.telephoto.zoomableimage) - implementation(libs.vanniktech.emoji) + implementation(libs.matrix.emojibase.bindings) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index 9b3f5073a1..7eb1a0984e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -67,7 +67,7 @@ fun aMessagesState() = MessagesState( ), actionListState = anActionListState(), customReactionState = CustomReactionState( - selectedEventId = null, + target = CustomReactionState.Target.None, eventSink = {}, selectedEmoji = persistentSetOf(), ), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 92b15f4fc0..9810845228 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -141,7 +141,7 @@ fun MessagesView( } fun onMoreReactionsClicked(event: TimelineItem.Event) { - state.customReactionState.eventSink(CustomReactionEvents.UpdateSelectedEvent(event)) + state.customReactionState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) } Scaffold( @@ -194,18 +194,17 @@ fun MessagesView( state = state.actionListState, onActionSelected = ::onActionSelected, onCustomReactionClicked = { event -> - state.customReactionState.eventSink(CustomReactionEvents.UpdateSelectedEvent(event)) + if (event.eventId == null) return@ActionListView + state.customReactionState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) }, onEmojiReactionClicked = ::onEmojiReactionClicked, ) CustomReactionBottomSheet( state = state.customReactionState, - onEmojiSelected = { emoji -> - state.customReactionState.selectedEventId?.let { eventId -> + onEmojiSelected = { eventId, emoji -> state.eventSink(MessagesEvents.ToggleReaction(emoji.unicode, eventId)) - state.customReactionState.eventSink(CustomReactionEvents.UpdateSelectedEvent(null)) - } + state.customReactionState.eventSink(CustomReactionEvents.DismissCustomReactionSheet) } ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt index d817ec0cd4..3fe739a592 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt @@ -22,34 +22,35 @@ import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import com.vanniktech.emoji.Emoji -import io.element.android.features.messages.impl.timeline.components.EmojiPicker +import io.element.android.emojibasebindings.Emoji 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.core.EventId @OptIn(ExperimentalMaterial3Api::class) @Composable fun CustomReactionBottomSheet( state: CustomReactionState, - onEmojiSelected: (Emoji) -> Unit, + onEmojiSelected: (EventId, Emoji) -> Unit, modifier: Modifier = Modifier, ) { val sheetState = rememberModalBottomSheetState() val coroutineScope = rememberCoroutineScope() + val target = state.target as? CustomReactionState.Target.Success fun onDismiss() { - state.eventSink(CustomReactionEvents.UpdateSelectedEvent(null)) + state.eventSink(CustomReactionEvents.DismissCustomReactionSheet) } fun onEmojiSelectedDismiss(emoji: Emoji) { + if (target?.event?.eventId == null) return sheetState.hide(coroutineScope) { - state.eventSink(CustomReactionEvents.UpdateSelectedEvent(null)) - onEmojiSelected(emoji) + state.eventSink(CustomReactionEvents.DismissCustomReactionSheet) + onEmojiSelected(target.event.eventId, emoji) } } - val isVisible = state.selectedEventId != null - if (isVisible) { + if (target?.emojibaseStore != null && target.event.eventId != null) { ModalBottomSheet( onDismissRequest = ::onDismiss, sheetState = sheetState, @@ -57,8 +58,9 @@ fun CustomReactionBottomSheet( ) { EmojiPicker( onEmojiSelected = ::onEmojiSelectedDismiss, - modifier = Modifier.fillMaxSize(), + emojibaseStore = target.emojibaseStore, selectedEmojis = state.selectedEmoji, + modifier = Modifier.fillMaxSize(), ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt index a0d69df372..2458686a83 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt @@ -19,5 +19,6 @@ package io.element.android.features.messages.impl.timeline.components.customreac import io.element.android.features.messages.impl.timeline.model.TimelineItem sealed interface CustomReactionEvents { - data class UpdateSelectedEvent(val event: TimelineItem.Event?) : CustomReactionEvents + data class ShowCustomReactionSheet(val event: TimelineItem.Event) : CustomReactionEvents + object DismissCustomReactionSheet : CustomReactionEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt index f094f2dbc6..b048383b1f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt @@ -17,28 +17,53 @@ package io.element.android.features.messages.impl.timeline.components.customreaction import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import io.element.android.features.messages.impl.timeline.model.TimelineItem +import androidx.compose.runtime.rememberCoroutineScope import io.element.android.libraries.architecture.Presenter +import kotlinx.coroutines.launch +import io.element.android.features.messages.impl.timeline.model.TimelineItem import kotlinx.collections.immutable.toImmutableSet import javax.inject.Inject -class CustomReactionPresenter @Inject constructor() : Presenter { +class CustomReactionPresenter @Inject constructor( + private val emojibaseProvider: EmojibaseProvider +) : Presenter { @Composable override fun present(): CustomReactionState { - var selectedEvent by remember { mutableStateOf(null) } + val target: MutableState = remember { + mutableStateOf(CustomReactionState.Target.None) + } - fun handleEvents(event: CustomReactionEvents) { - when (event) { - is CustomReactionEvents.UpdateSelectedEvent -> selectedEvent = event.event + val localCoroutineScope = rememberCoroutineScope() + fun handleShowCustomReactionSheet(event: TimelineItem.Event) { + target.value = CustomReactionState.Target.Loading(event) + localCoroutineScope.launch { + target.value = CustomReactionState.Target.Success( + event = event, + emojibaseStore = emojibaseProvider.emojibaseStore + ) } } - val selectedEmoji = selectedEvent?.reactionsState?.reactions?.mapNotNull { if(it.isHighlighted) it.key else null }.orEmpty().toImmutableSet() - return CustomReactionState(selectedEventId = selectedEvent?.eventId, selectedEmoji = selectedEmoji, eventSink = ::handleEvents) + fun handleDismissCustomReactionSheet() { + target.value = CustomReactionState.Target.None + } + + fun handleEvents(event: CustomReactionEvents) { + when (event) { + is CustomReactionEvents.ShowCustomReactionSheet -> handleShowCustomReactionSheet(event.event) + is CustomReactionEvents.DismissCustomReactionSheet -> handleDismissCustomReactionSheet() + } + } + val event = (target.value as? CustomReactionState.Target.Success)?.event + val selectedEmoji = event?.reactionsState?.reactions?.mapNotNull { if(it.isHighlighted) it.key else null }.orEmpty().toImmutableSet() + return CustomReactionState( + target = target.value, + selectedEmoji = selectedEmoji, + eventSink = ::handleEvents + ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt index 9de1642dff..f6f7d2b0f9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt @@ -16,11 +16,23 @@ package io.element.android.features.messages.impl.timeline.components.customreaction -import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.emojibasebindings.EmojibaseStore +import io.element.android.features.messages.impl.timeline.model.TimelineItem import kotlinx.collections.immutable.ImmutableSet data class CustomReactionState( - val selectedEventId: EventId?, + val target: Target, val selectedEmoji: ImmutableSet, val eventSink: (CustomReactionEvents) -> Unit, -) +) { + sealed interface Target { + + data object None : Target + data class Loading(val event: TimelineItem.Event) : Target + data class Success( + val event: TimelineItem.Event, + val emojibaseStore: EmojibaseStore, + ) : Target + } +} + diff --git a/app/src/main/kotlin/io/element/android/x/initializer/EmojiInitializer.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt similarity index 59% rename from app/src/main/kotlin/io/element/android/x/initializer/EmojiInitializer.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt index dd1e7455c6..a68d6f0d4e 100644 --- a/app/src/main/kotlin/io/element/android/x/initializer/EmojiInitializer.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt @@ -14,16 +14,16 @@ * limitations under the License. */ -package io.element.android.x.initializer +package io.element.android.features.messages.impl.timeline.components.customreaction -import androidx.startup.Initializer -import com.vanniktech.emoji.EmojiManager -import com.vanniktech.emoji.google.GoogleEmojiProvider +import android.content.Context +import io.element.android.emojibasebindings.EmojibaseDatasource +import io.element.android.emojibasebindings.EmojibaseStore -class EmojiInitializer : Initializer { - override fun create(context: android.content.Context) { - EmojiManager.install(GoogleEmojiProvider()) +class DefaultEmojibaseProvider(val context: Context): EmojibaseProvider { + + override val emojibaseStore: EmojibaseStore by lazy { + EmojibaseDatasource().load(context) } - override fun dependencies(): MutableList>> = mutableListOf() } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/EmojiPicker.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiPicker.kt similarity index 83% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/EmojiPicker.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiPicker.kt index 45fd5bf186..1012d61020 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/EmojiPicker.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiPicker.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.messages.impl.timeline.components +package io.element.android.features.messages.impl.timeline.components.customreaction import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -41,11 +41,15 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.vanniktech.emoji.Emoji -import com.vanniktech.emoji.google.GoogleEmojiProvider +import io.element.android.emojibasebindings.Emoji +import io.element.android.emojibasebindings.EmojibaseCategory +import io.element.android.emojibasebindings.EmojibaseDatasource +import io.element.android.emojibasebindings.EmojibaseStore import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.theme.components.Icon @@ -59,24 +63,23 @@ import kotlinx.coroutines.launch @Composable fun EmojiPicker( onEmojiSelected: (Emoji) -> Unit, + emojibaseStore: EmojibaseStore, selectedEmojis: ImmutableSet, modifier: Modifier = Modifier, ) { val coroutineScope = rememberCoroutineScope() - - val emojiProvider = remember { GoogleEmojiProvider() } - val categories = remember { emojiProvider.categories } - val pagerState = rememberPagerState(pageCount = { emojiProvider.categories.size }) + val categories = remember { emojibaseStore.categories } + val pagerState = rememberPagerState(pageCount = { EmojibaseCategory.values().size }) Column(modifier) { TabRow( selectedTabIndex = pagerState.currentPage, ) { - categories.forEachIndexed { index, category -> + EmojibaseCategory.values().forEachIndexed { index, category -> Tab( text = { Icon( - resourceId = emojiProvider.getIcon(category), - contentDescription = category.categoryNames["en"] + imageVector = category.icon, + contentDescription = stringResource(id = category.title) ) }, selected = pagerState.currentPage == index, @@ -91,14 +94,16 @@ fun EmojiPicker( state = pagerState, modifier = Modifier.fillMaxWidth(), ) { index -> - val category = categories[index] + val category = EmojibaseCategory.values()[index] + val emojis = categories[category] ?: listOf() LazyVerticalGrid( modifier = Modifier.fillMaxSize(), columns = GridCells.Adaptive(minSize = 40.dp), contentPadding = PaddingValues(vertical = 10.dp, horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - items(category.emojis, key = { it.unicode }) { item -> + + items(emojis, key = { it.unicode }) { item -> val backgroundColor = if (selectedEmojis.contains(item.unicode)) { ElementTheme.colors.bgActionPrimaryRest } else { @@ -144,7 +149,8 @@ internal fun EmojiPickerDarkPreview() { private fun ContentToPreview() { EmojiPicker( onEmojiSelected = {}, + emojibaseStore = EmojibaseDatasource().load(LocalContext.current), + selectedEmojis = persistentSetOf("😀", "😄", "😃"), modifier = Modifier.fillMaxWidth(), - selectedEmojis = persistentSetOf("😀", "😄", "😃") ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseExtensions.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseExtensions.kt new file mode 100644 index 0000000000..fb111cce97 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseExtensions.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023 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 io.element.android.features.messages.impl.timeline.components.customreaction + +import androidx.annotation.StringRes +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.EmojiEvents +import androidx.compose.material.icons.outlined.EmojiFlags +import androidx.compose.material.icons.outlined.EmojiFoodBeverage +import androidx.compose.material.icons.outlined.EmojiNature +import androidx.compose.material.icons.outlined.EmojiObjects +import androidx.compose.material.icons.outlined.EmojiPeople +import androidx.compose.material.icons.outlined.EmojiSymbols +import androidx.compose.material.icons.outlined.EmojiTransportation +import androidx.compose.ui.graphics.vector.ImageVector +import io.element.android.emojibasebindings.EmojibaseCategory +import io.element.android.libraries.ui.strings.CommonStrings + +@get:StringRes +val EmojibaseCategory.title: Int get() = + when(this){ + EmojibaseCategory.People -> CommonStrings.emoji_picker_category_people + EmojibaseCategory.Nature -> CommonStrings.emoji_picker_category_nature + EmojibaseCategory.Foods -> CommonStrings.emoji_picker_category_foods + EmojibaseCategory.Activity -> CommonStrings.emoji_picker_category_activity + EmojibaseCategory.Places -> CommonStrings.emoji_picker_category_places + EmojibaseCategory.Objects -> CommonStrings.emoji_picker_category_objects + EmojibaseCategory.Symbols -> CommonStrings.emoji_picker_category_symbols + EmojibaseCategory.Flags -> CommonStrings.emoji_picker_category_flags + } + +val EmojibaseCategory.icon: ImageVector + get() = + when(this){ + EmojibaseCategory.People -> Icons.Outlined.EmojiPeople + EmojibaseCategory.Nature -> Icons.Outlined.EmojiNature + EmojibaseCategory.Foods -> Icons.Outlined.EmojiFoodBeverage + EmojibaseCategory.Activity -> Icons.Outlined.EmojiEvents + EmojibaseCategory.Places -> Icons.Outlined.EmojiTransportation + EmojibaseCategory.Objects -> Icons.Outlined.EmojiObjects + EmojibaseCategory.Symbols -> Icons.Outlined.EmojiSymbols + EmojibaseCategory.Flags -> Icons.Outlined.EmojiFlags + } + diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseProvider.kt new file mode 100644 index 0000000000..6a4f48a806 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseProvider.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 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 io.element.android.features.messages.impl.timeline.components.customreaction + +import io.element.android.emojibasebindings.EmojibaseStore + +interface EmojibaseProvider { + val emojibaseStore: EmojibaseStore +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt index f5d0185789..992ea60869 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt @@ -41,6 +41,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.media.FakeLocalMediaFactory +import io.element.android.features.messages.timeline.components.customreaction.FakeEmojibaseProvider import io.element.android.features.messages.utils.messagesummary.FakeMessageSummaryFormatter import io.element.android.features.networkmonitor.test.FakeNetworkMonitor import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper @@ -603,7 +604,7 @@ class MessagesPresenterTest { ) val buildMeta = aBuildMeta() val actionListPresenter = ActionListPresenter(buildMeta = buildMeta) - val customReactionPresenter = CustomReactionPresenter() + val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider()) val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom) val retrySendMenuPresenter = RetrySendMenuPresenter(room = matrixRoom) return MessagesPresenter( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/CustomReactionPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/CustomReactionPresenterTests.kt index 84628cedae..bb77605132 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/CustomReactionPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/CustomReactionPresenterTests.kt @@ -24,27 +24,34 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter +import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState import io.element.android.libraries.matrix.test.AN_EVENT_ID import kotlinx.coroutines.test.runTest import org.junit.Test class CustomReactionPresenterTests { - private val presenter = CustomReactionPresenter() + private val presenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider()) @Test fun `present - handle selecting and de-selecting an event`() = runTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + + val event = aTimelineItemEvent(eventId = AN_EVENT_ID) val initialState = awaitItem() - assertThat(initialState.selectedEventId).isNull() + assertThat(initialState.target).isEqualTo(CustomReactionState.Target.None) - initialState.eventSink(CustomReactionEvents.UpdateSelectedEvent(aTimelineItemEvent(eventId = AN_EVENT_ID))) - assertThat(awaitItem().selectedEventId).isEqualTo(AN_EVENT_ID) + initialState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) - initialState.eventSink(CustomReactionEvents.UpdateSelectedEvent(null)) - assertThat(awaitItem().selectedEventId).isNull() + assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.Loading(event)) + + val eventId = (awaitItem().target as? CustomReactionState.Target.Success)?.event?.eventId + assertThat(eventId).isEqualTo(AN_EVENT_ID) + + initialState.eventSink(CustomReactionEvents.DismissCustomReactionSheet) + assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.None) } } @@ -53,13 +60,19 @@ class CustomReactionPresenterTests { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - val initialState = awaitItem() - assertThat(initialState.selectedEventId).isNull() val reactions = aTimelineItemReactions(count = 1, isHighlighted = true) + val event = aTimelineItemEvent(eventId = AN_EVENT_ID, timelineItemReactions = reactions) + val initialState = awaitItem() + assertThat(initialState.target).isEqualTo(CustomReactionState.Target.None) + val key = reactions.reactions.first().key - initialState.eventSink(CustomReactionEvents.UpdateSelectedEvent(aTimelineItemEvent(eventId = AN_EVENT_ID, timelineItemReactions = reactions))) + initialState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) + + assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.Loading(event)) + val stateWithSelectedEmojis = awaitItem() - assertThat(stateWithSelectedEmojis.selectedEventId).isEqualTo(AN_EVENT_ID) + val eventId = (stateWithSelectedEmojis.target as? CustomReactionState.Target.Success)?.event?.eventId + assertThat(eventId).isEqualTo(AN_EVENT_ID) assertThat(stateWithSelectedEmojis.selectedEmoji).contains(key) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/FakeEmojibaseProvider.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/FakeEmojibaseProvider.kt new file mode 100644 index 0000000000..7bf993e2a4 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/FakeEmojibaseProvider.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 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 io.element.android.features.messages.timeline.components.customreaction + +import io.element.android.emojibasebindings.EmojibaseStore +import io.element.android.features.messages.impl.timeline.components.customreaction.EmojibaseProvider + +class FakeEmojibaseProvider: EmojibaseProvider { + override val emojibaseStore: EmojibaseStore + get() = EmojibaseStore(mapOf()) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 863d4812b8..549ee745ea 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -155,7 +155,6 @@ sqlite = "androidx.sqlite:sqlite:2.3.1" unifiedpush = "com.github.UnifiedPush:android-connector:2.1.1" otaliastudios_transcoder = "com.otaliastudios:transcoder:0.10.5" vanniktech_blurhash = "com.vanniktech:blurhash:0.1.0" -vanniktech_emoji = "com.vanniktech:emoji-google:0.16.0" telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" } statemachine = "com.freeletics.flowredux:compose:1.2.0" maplibre = "org.maplibre.gl:android-sdk:10.2.0" @@ -167,6 +166,9 @@ posthog = "com.posthog.android:posthog:2.0.3" sentry = "io.sentry:sentry-android:6.28.0" matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:42b2faa417c1e95f430bf8f6e379adba25ad5ef8" +# Emojibase +matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.0.5" + # Di inject = "javax.inject:javax.inject:1" dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" } @@ -178,7 +180,6 @@ anvil_compiler_utils = { module = "com.squareup.anvil:compiler-utils", version.r google_autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" } google_autoservice_annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "autoservice" } - # Miscellaneous # Add unused dependency to androidx.compose.compiler:compiler to let Renovate create PR to change the # value of `composecompiler` (which is used to set composeOptions.kotlinCompilerExtensionVersion. diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.customreaction_null_EmojiPickerDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.customreaction_null_EmojiPickerDark_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..aaab9d35c1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.customreaction_null_EmojiPickerDark_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ea42394330d37a0aa5ba8940e023fc4454d902caf6d265adec0e11c8a34d85f +size 187744 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.customreaction_null_EmojiPickerLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.customreaction_null_EmojiPickerLight_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..65a27f228d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.customreaction_null_EmojiPickerLight_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fb074cd58034c90010dc437fc01d6ea77a2a22aa07bc1d4cf0c1730b543b41a +size 186707 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerDark_0_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 8838c1cbf2..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerDark_0_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd761ca4ff5a70770869780c2575006ed8bd187ed4b3f197ffe1e4ea18d8390b -size 189559 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerLight_0_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 80a5da9cd1..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerLight_0_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8c6e3f139ca634c47c45fbeeaccf3e7e903fb8f83a88fdf8d7247455e76e4a9 -size 188653