Browse Source

Merge pull request #1162 from vector-im/feature/dla/emojibase_integration

Emojibase integration
pull/1206/head
David Langley 1 year ago committed by GitHub
parent
commit
5b6682f125
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/build.gradle.kts
  2. 2
      app/src/main/kotlin/io/element/android/x/ElementXApplication.kt
  3. 8
      app/src/main/kotlin/io/element/android/x/di/AppModule.kt
  4. 2
      features/messages/impl/build.gradle.kts
  5. 2
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt
  6. 11
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
  7. 20
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt
  8. 3
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt
  9. 43
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt
  10. 18
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt
  11. 16
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt
  12. 32
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiPicker.kt
  13. 58
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseExtensions.kt
  14. 23
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseProvider.kt
  15. 3
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt
  16. 33
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/CustomReactionPresenterTests.kt
  17. 25
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/FakeEmojibaseProvider.kt
  18. 5
      gradle/libs.versions.toml
  19. BIN
      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
  20. BIN
      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
  21. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerDark_0_null,NEXUS_5,1.0,en].png
  22. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerLight_0_null,NEXUS_5,1.0,en].png

2
app/build.gradle.kts

@ -218,7 +218,7 @@ dependencies { @@ -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)

2
app/src/main/kotlin/io/element/android/x/ElementXApplication.kt

@ -23,7 +23,6 @@ import io.element.android.x.di.AppComponent @@ -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 { @@ -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()
}

8
app/src/main/kotlin/io/element/android/x/di/AppModule.kt

@ -23,6 +23,8 @@ import androidx.preference.PreferenceManager @@ -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 { @@ -105,4 +107,10 @@ object AppModule {
fun provideSnackbarDispatcher(): SnackbarDispatcher {
return SnackbarDispatcher()
}
@Provides
@SingleIn(AppScope::class)
fun providesEmojibaseProvider(@ApplicationContext context: Context): EmojibaseProvider {
return DefaultEmojibaseProvider(context)
}
}

2
features/messages/impl/build.gradle.kts

@ -61,7 +61,7 @@ dependencies { @@ -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)

2
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt

@ -67,7 +67,7 @@ fun aMessagesState() = MessagesState( @@ -67,7 +67,7 @@ fun aMessagesState() = MessagesState(
),
actionListState = anActionListState(),
customReactionState = CustomReactionState(
selectedEventId = null,
target = CustomReactionState.Target.None,
eventSink = {},
selectedEmoji = persistentSetOf(),
),

11
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt

@ -141,7 +141,7 @@ fun MessagesView( @@ -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( @@ -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)
}
)

20
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 @@ -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( @@ -57,8 +58,9 @@ fun CustomReactionBottomSheet(
) {
EmojiPicker(
onEmojiSelected = ::onEmojiSelectedDismiss,
modifier = Modifier.fillMaxSize(),
emojibaseStore = target.emojibaseStore,
selectedEmojis = state.selectedEmoji,
modifier = Modifier.fillMaxSize(),
)
}
}

3
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 @@ -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
}

43
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt

@ -17,28 +17,53 @@ @@ -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<CustomReactionState> {
class CustomReactionPresenter @Inject constructor(
private val emojibaseProvider: EmojibaseProvider
) : Presenter<CustomReactionState> {
@Composable
override fun present(): CustomReactionState {
var selectedEvent by remember { mutableStateOf<TimelineItem.Event?>(null) }
val target: MutableState<CustomReactionState.Target> = remember {
mutableStateOf(CustomReactionState.Target.None)
}
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
)
}
}
fun handleDismissCustomReactionSheet() {
target.value = CustomReactionState.Target.None
}
fun handleEvents(event: CustomReactionEvents) {
when (event) {
is CustomReactionEvents.UpdateSelectedEvent -> selectedEvent = event.event
is CustomReactionEvents.ShowCustomReactionSheet -> handleShowCustomReactionSheet(event.event)
is CustomReactionEvents.DismissCustomReactionSheet -> handleDismissCustomReactionSheet()
}
}
val selectedEmoji = selectedEvent?.reactionsState?.reactions?.mapNotNull { if(it.isHighlighted) it.key else null }.orEmpty().toImmutableSet()
return CustomReactionState(selectedEventId = selectedEvent?.eventId, selectedEmoji = selectedEmoji, eventSink = ::handleEvents)
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
)
}
}

18
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt

@ -16,11 +16,23 @@ @@ -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<String>,
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
}
}

16
app/src/main/kotlin/io/element/android/x/initializer/EmojiInitializer.kt → features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt

@ -14,16 +14,16 @@ @@ -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<Unit> {
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<Class<out Initializer<*>>> = mutableListOf()
}

32
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/EmojiPicker.kt → features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiPicker.kt

@ -14,7 +14,7 @@ @@ -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 @@ -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 @@ -59,24 +63,23 @@ import kotlinx.coroutines.launch
@Composable
fun EmojiPicker(
onEmojiSelected: (Emoji) -> Unit,
emojibaseStore: EmojibaseStore,
selectedEmojis: ImmutableSet<String>,
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( @@ -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() { @@ -144,7 +149,8 @@ internal fun EmojiPickerDarkPreview() {
private fun ContentToPreview() {
EmojiPicker(
onEmojiSelected = {},
emojibaseStore = EmojibaseDatasource().load(LocalContext.current),
selectedEmojis = persistentSetOf("😀", "😄", "😃"),
modifier = Modifier.fillMaxWidth(),
selectedEmojis = persistentSetOf("😀", "😄", "😃")
)
}

58
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseExtensions.kt

@ -0,0 +1,58 @@ @@ -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
}

23
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseProvider.kt

@ -0,0 +1,23 @@ @@ -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
}

3
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 @@ -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 { @@ -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(

33
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 @@ -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.ShowCustomReactionSheet(event))
initialState.eventSink(CustomReactionEvents.UpdateSelectedEvent(aTimelineItemEvent(eventId = AN_EVENT_ID)))
assertThat(awaitItem().selectedEventId).isEqualTo(AN_EVENT_ID)
assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.Loading(event))
initialState.eventSink(CustomReactionEvents.UpdateSelectedEvent(null))
assertThat(awaitItem().selectedEventId).isNull()
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 { @@ -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)
}
}

25
features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/FakeEmojibaseProvider.kt

@ -0,0 +1,25 @@ @@ -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())
}

5
gradle/libs.versions.toml

@ -155,7 +155,6 @@ sqlite = "androidx.sqlite:sqlite:2.3.1" @@ -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" @@ -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 @@ -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.

BIN
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 (Stored with Git LFS)

Binary file not shown.

BIN
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 (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerDark_0_null,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_EmojiPickerLight_0_null,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.
Loading…
Cancel
Save