mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Merge pull request #7257 from element-hq/feature/fga/user-status-emoji-picker
Plug emoji picker for custom user status
This commit is contained in:
Generated
+34
@@ -1,5 +1,39 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<code_scheme name="Project" version="173">
|
<code_scheme name="Project" version="173">
|
||||||
|
<JavaCodeStyleSettings>
|
||||||
|
<option name="IMPORT_LAYOUT_TABLE">
|
||||||
|
<value>
|
||||||
|
<package name="android" withSubpackages="true" static="true" />
|
||||||
|
<package name="androidx" withSubpackages="true" static="true" />
|
||||||
|
<package name="com" withSubpackages="true" static="true" />
|
||||||
|
<package name="junit" withSubpackages="true" static="true" />
|
||||||
|
<package name="net" withSubpackages="true" static="true" />
|
||||||
|
<package name="org" withSubpackages="true" static="true" />
|
||||||
|
<package name="java" withSubpackages="true" static="true" />
|
||||||
|
<package name="javax" withSubpackages="true" static="true" />
|
||||||
|
<package name="" withSubpackages="true" static="true" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="android" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="androidx" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="com" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="junit" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="net" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="org" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="java" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="javax" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</JavaCodeStyleSettings>
|
||||||
<JetCodeStyleSettings>
|
<JetCodeStyleSettings>
|
||||||
<option name="LINE_BREAK_AFTER_MULTILINE_WHEN_ENTRY" value="false" />
|
<option name="LINE_BREAK_AFTER_MULTILINE_WHEN_ENTRY" value="false" />
|
||||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ import io.element.android.libraries.di.BaseDirectory
|
|||||||
import io.element.android.libraries.di.CacheDirectory
|
import io.element.android.libraries.di.CacheDirectory
|
||||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||||
import io.element.android.libraries.recentemojis.api.EmojibaseProvider
|
|
||||||
import io.element.android.libraries.recentemojis.impl.DefaultEmojibaseProvider
|
|
||||||
import io.element.android.x.BuildConfig
|
import io.element.android.x.BuildConfig
|
||||||
import io.element.android.x.R
|
import io.element.android.x.R
|
||||||
import kotlinx.coroutines.CoroutineName
|
import kotlinx.coroutines.CoroutineName
|
||||||
@@ -115,10 +113,4 @@ object AppModule {
|
|||||||
fun provideSnackbarDispatcher(): SnackbarDispatcher {
|
fun provideSnackbarDispatcher(): SnackbarDispatcher {
|
||||||
return SnackbarDispatcher()
|
return SnackbarDispatcher()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@SingleIn(AppScope::class)
|
|
||||||
fun providesEmojibaseProvider(@ApplicationContext context: Context): EmojibaseProvider {
|
|
||||||
return DefaultEmojibaseProvider(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ dependencies {
|
|||||||
implementation(projects.libraries.mediaupload.api)
|
implementation(projects.libraries.mediaupload.api)
|
||||||
implementation(projects.libraries.permissions.api)
|
implementation(projects.libraries.permissions.api)
|
||||||
implementation(projects.libraries.preferences.api)
|
implementation(projects.libraries.preferences.api)
|
||||||
implementation(projects.libraries.recentemojis.api)
|
implementation(projects.libraries.emoji.api)
|
||||||
implementation(projects.libraries.roomselect.api)
|
implementation(projects.libraries.roomselect.api)
|
||||||
implementation(projects.libraries.slashcommands.api)
|
implementation(projects.libraries.slashcommands.api)
|
||||||
implementation(projects.libraries.audio.api)
|
implementation(projects.libraries.audio.api)
|
||||||
@@ -106,6 +106,7 @@ dependencies {
|
|||||||
testImplementation(projects.libraries.testtags)
|
testImplementation(projects.libraries.testtags)
|
||||||
testImplementation(projects.features.poll.test)
|
testImplementation(projects.features.poll.test)
|
||||||
testImplementation(projects.libraries.eventformatter.test)
|
testImplementation(projects.libraries.eventformatter.test)
|
||||||
testImplementation(projects.libraries.recentemojis.test)
|
testImplementation(projects.libraries.emoji.impl)
|
||||||
|
testImplementation(projects.libraries.emoji.test)
|
||||||
testImplementation(projects.libraries.slashcommands.test)
|
testImplementation(projects.libraries.slashcommands.test)
|
||||||
}
|
}
|
||||||
|
|||||||
+12
@@ -38,6 +38,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer
|
|||||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.TimelinePresenter
|
import io.element.android.features.messages.impl.timeline.TimelinePresenter
|
||||||
|
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
|
||||||
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
||||||
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories
|
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
@@ -54,6 +55,7 @@ import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
|||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||||
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer
|
||||||
import io.element.android.libraries.matrix.api.analytics.toAnalyticsViewRoom
|
import io.element.android.libraries.matrix.api.analytics.toAnalyticsViewRoom
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
@@ -100,6 +102,7 @@ class MessagesNode(
|
|||||||
private val knockRequestsBannerRenderer: KnockRequestsBannerRenderer,
|
private val knockRequestsBannerRenderer: KnockRequestsBannerRenderer,
|
||||||
private val roomMemberModerationRenderer: RoomMemberModerationRenderer,
|
private val roomMemberModerationRenderer: RoomMemberModerationRenderer,
|
||||||
private val eventContentValidationCache: EventContentValidationCache,
|
private val eventContentValidationCache: EventContentValidationCache,
|
||||||
|
private val emojiPickerRenderer: EmojiPickerRenderer,
|
||||||
) : Node(buildContext, plugins = plugins), MessagesNavigator {
|
) : Node(buildContext, plugins = plugins), MessagesNavigator {
|
||||||
data class Inputs(
|
data class Inputs(
|
||||||
val focusedEventId: EventId?,
|
val focusedEventId: EventId?,
|
||||||
@@ -329,6 +332,15 @@ class MessagesNode(
|
|||||||
onViewRequestsClick = callback::navigateToKnockRequestsList,
|
onViewRequestsClick = callback::navigateToKnockRequestsList,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
customReactionBottomSheet = {
|
||||||
|
CustomReactionBottomSheet(
|
||||||
|
state = state.customReactionState,
|
||||||
|
onSelectEmoji = { uniqueId, emoji ->
|
||||||
|
state.eventSink(MessagesEvent.ToggleReaction(emoji.unicode, uniqueId))
|
||||||
|
},
|
||||||
|
emojiPickerRenderer = emojiPickerRenderer,
|
||||||
|
)
|
||||||
|
},
|
||||||
onThreadsListClick = callback::navigateToThreadsList,
|
onThreadsListClick = callback::navigateToThreadsList,
|
||||||
)
|
)
|
||||||
roomMemberModerationRenderer.Render(
|
roomMemberModerationRenderer.Render(
|
||||||
|
|||||||
+1
-1
@@ -69,6 +69,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatch
|
|||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
|
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
|
||||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||||
|
import io.element.android.libraries.emoji.api.recentemojis.AddRecentEmoji
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.matrix.api.core.toThreadId
|
import io.element.android.libraries.matrix.api.core.toThreadId
|
||||||
@@ -85,7 +86,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransa
|
|||||||
import io.element.android.libraries.matrix.ui.messages.reply.map
|
import io.element.android.libraries.matrix.ui.messages.reply.map
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
import io.element.android.libraries.matrix.ui.room.getDirectRoomMember
|
import io.element.android.libraries.matrix.ui.room.getDirectRoomMember
|
||||||
import io.element.android.libraries.recentemojis.api.AddRecentEmoji
|
|
||||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
|
|||||||
-3
@@ -53,7 +53,6 @@ import io.element.android.libraries.matrix.api.timeline.Timeline
|
|||||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||||
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
|
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
|
||||||
import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
|
import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.persistentSetOf
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
|
|
||||||
@@ -193,11 +192,9 @@ fun aReactionSummaryState(
|
|||||||
|
|
||||||
fun aCustomReactionState(
|
fun aCustomReactionState(
|
||||||
target: CustomReactionState.Target = CustomReactionState.Target.None,
|
target: CustomReactionState.Target = CustomReactionState.Target.None,
|
||||||
recentEmojis: ImmutableList<String> = persistentListOf(),
|
|
||||||
eventSink: (CustomReactionEvent) -> Unit = {},
|
eventSink: (CustomReactionEvent) -> Unit = {},
|
||||||
) = CustomReactionState(
|
) = CustomReactionState(
|
||||||
target = target,
|
target = target,
|
||||||
recentEmojis = recentEmojis,
|
|
||||||
selectedEmoji = persistentSetOf(),
|
selectedEmoji = persistentSetOf(),
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|||||||
+5
-8
@@ -81,7 +81,6 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemDaySepara
|
|||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineState
|
import io.element.android.features.messages.impl.timeline.aTimelineState
|
||||||
import io.element.android.features.messages.impl.timeline.components.CallMenuItem
|
import io.element.android.features.messages.impl.timeline.components.CallMenuItem
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent
|
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent
|
||||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvent
|
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvent
|
||||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryView
|
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryView
|
||||||
@@ -146,9 +145,10 @@ fun MessagesView(
|
|||||||
onJoinCallClick: (isAudioCall: Boolean) -> Unit,
|
onJoinCallClick: (isAudioCall: Boolean) -> Unit,
|
||||||
onViewAllPinnedMessagesClick: () -> Unit,
|
onViewAllPinnedMessagesClick: () -> Unit,
|
||||||
onThreadsListClick: () -> Unit,
|
onThreadsListClick: () -> Unit,
|
||||||
|
knockRequestsBannerView: @Composable () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
forceJumpToBottomVisibility: Boolean = false,
|
forceJumpToBottomVisibility: Boolean = false,
|
||||||
knockRequestsBannerView: @Composable () -> Unit,
|
customReactionBottomSheet: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val eventContentValidationState = LocalEventContentValidationState.current
|
val eventContentValidationState = LocalEventContentValidationState.current
|
||||||
|
|
||||||
@@ -409,12 +409,7 @@ fun MessagesView(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
CustomReactionBottomSheet(
|
customReactionBottomSheet()
|
||||||
state = state.customReactionState,
|
|
||||||
onSelectEmoji = { uniqueId, emoji ->
|
|
||||||
state.eventSink(MessagesEvent.ToggleReaction(emoji.unicode, uniqueId))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
ReactionSummaryView(state = state.reactionSummaryState)
|
ReactionSummaryView(state = state.reactionSummaryState)
|
||||||
ReadReceiptBottomSheet(
|
ReadReceiptBottomSheet(
|
||||||
@@ -669,6 +664,7 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
|
|||||||
onViewAllPinnedMessagesClick = { },
|
onViewAllPinnedMessagesClick = { },
|
||||||
forceJumpToBottomVisibility = true,
|
forceJumpToBottomVisibility = true,
|
||||||
knockRequestsBannerView = {},
|
knockRequestsBannerView = {},
|
||||||
|
customReactionBottomSheet = {},
|
||||||
onThreadsListClick = {},
|
onThreadsListClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -726,5 +722,6 @@ internal fun MessagesViewA11yPreview() = ElementPreview {
|
|||||||
onThreadsListClick = {},
|
onThreadsListClick = {},
|
||||||
forceJumpToBottomVisibility = true,
|
forceJumpToBottomVisibility = true,
|
||||||
knockRequestsBannerView = {},
|
knockRequestsBannerView = {},
|
||||||
|
customReactionBottomSheet = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -44,13 +44,13 @@ import io.element.android.libraries.architecture.Presenter
|
|||||||
import io.element.android.libraries.dateformatter.api.DateFormatter
|
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
|
import io.element.android.libraries.emoji.api.recentemojis.GetRecentEmojis
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
import io.element.android.libraries.recentemojis.api.GetRecentEmojis
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|||||||
+1
@@ -43,6 +43,7 @@ internal fun MessagesViewWithIdentityChangePreview(
|
|||||||
onJoinCallClick = {},
|
onJoinCallClick = {},
|
||||||
onViewAllPinnedMessagesClick = {},
|
onViewAllPinnedMessagesClick = {},
|
||||||
knockRequestsBannerView = {},
|
knockRequestsBannerView = {},
|
||||||
|
customReactionBottomSheet = {},
|
||||||
onThreadsListClick = {},
|
onThreadsListClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -322,6 +322,7 @@ class ThreadedMessagesNode(
|
|||||||
onViewAllPinnedMessagesClick = {},
|
onViewAllPinnedMessagesClick = {},
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
knockRequestsBannerView = {},
|
knockRequestsBannerView = {},
|
||||||
|
customReactionBottomSheet = {},
|
||||||
onThreadsListClick = {},
|
onThreadsListClick = {},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+31
-36
@@ -13,15 +13,13 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||||||
import androidx.compose.material3.SheetValue
|
import androidx.compose.material3.SheetValue
|
||||||
import androidx.compose.material3.rememberBottomSheetState
|
import androidx.compose.material3.rememberBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import io.element.android.emojibasebindings.Emoji
|
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.a11y.a11yReactionAction
|
||||||
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.ModalBottomSheet
|
||||||
import io.element.android.libraries.designsystem.theme.components.hide
|
import io.element.android.libraries.designsystem.theme.components.hide
|
||||||
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -29,46 +27,43 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransa
|
|||||||
fun CustomReactionBottomSheet(
|
fun CustomReactionBottomSheet(
|
||||||
state: CustomReactionState,
|
state: CustomReactionState,
|
||||||
onSelectEmoji: (EventOrTransactionId, Emoji) -> Unit,
|
onSelectEmoji: (EventOrTransactionId, Emoji) -> Unit,
|
||||||
|
emojiPickerRenderer: EmojiPickerRenderer,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val sheetState = rememberBottomSheetState(
|
val sheetState = rememberBottomSheetState(initialValue = SheetValue.Hidden)
|
||||||
initialValue = SheetValue.Hidden,
|
|
||||||
)
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val target = state.target as? CustomReactionState.Target.Success
|
|
||||||
|
|
||||||
fun onDismiss() {
|
when (state.target) {
|
||||||
state.eventSink(CustomReactionEvent.DismissCustomReactionSheet)
|
CustomReactionState.Target.None,
|
||||||
}
|
is CustomReactionState.Target.Loading -> Unit
|
||||||
|
is CustomReactionState.Target.Success -> {
|
||||||
|
fun onEmojiSelectedDismiss(emoji: Emoji) {
|
||||||
|
sheetState.hide(coroutineScope) {
|
||||||
|
state.eventSink(CustomReactionEvent.DismissCustomReactionSheet)
|
||||||
|
onSelectEmoji(state.target.event.eventOrTransactionId, emoji)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onEmojiSelectedDismiss(emoji: Emoji) {
|
fun onDismiss() {
|
||||||
if (target?.event == null) return
|
state.eventSink(CustomReactionEvent.DismissCustomReactionSheet)
|
||||||
sheetState.hide(coroutineScope) {
|
}
|
||||||
state.eventSink(CustomReactionEvent.DismissCustomReactionSheet)
|
|
||||||
onSelectEmoji(target.event.eventOrTransactionId, emoji)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target?.emojibaseStore != null && target.event.eventId != null) {
|
ModalBottomSheet(
|
||||||
ModalBottomSheet(
|
onDismissRequest = ::onDismiss,
|
||||||
onDismissRequest = ::onDismiss,
|
sheetState = sheetState,
|
||||||
sheetState = sheetState,
|
modifier = modifier,
|
||||||
modifier = modifier,
|
scrollable = false,
|
||||||
scrollable = false,
|
) {
|
||||||
) {
|
emojiPickerRenderer.Render(
|
||||||
val presenter = remember {
|
state = state.target.emojiPickerState,
|
||||||
EmojiPickerPresenter(
|
onSelectEmoji = ::onEmojiSelectedDismiss,
|
||||||
emojibaseStore = target.emojibaseStore,
|
selectedEmojis = state.selectedEmoji,
|
||||||
recentEmojis = state.recentEmojis,
|
modifier = Modifier.fillMaxSize(),
|
||||||
coroutineDispatchers = CoroutineDispatchers.Default,
|
contentDescription = { emoji, isSelected ->
|
||||||
|
a11yReactionAction(emoji = emoji.unicode, userAlreadyReacted = isSelected)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
EmojiPicker(
|
|
||||||
onSelectEmoji = ::onEmojiSelectedDismiss,
|
|
||||||
state = presenter.present(),
|
|
||||||
selectedEmojis = state.selectedEmoji,
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-36
@@ -9,59 +9,53 @@
|
|||||||
package io.element.android.features.messages.impl.timeline.components.customreaction
|
package io.element.android.features.messages.impl.timeline.components.customreaction
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.recentemojis.api.EmojibaseProvider
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerPresenter
|
||||||
import io.element.android.libraries.recentemojis.api.GetRecentEmojis
|
import io.element.android.libraries.emoji.api.recentemojis.GetRecentEmojis
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
class CustomReactionPresenter(
|
class CustomReactionPresenter(
|
||||||
private val emojibaseProvider: EmojibaseProvider,
|
emojiPickerPresenterFactory: EmojiPickerPresenter.Factory,
|
||||||
private val getRecentEmojis: GetRecentEmojis,
|
getRecentEmojis: GetRecentEmojis,
|
||||||
) : Presenter<CustomReactionState> {
|
) : Presenter<CustomReactionState> {
|
||||||
|
private val emojiPickerPresenter = emojiPickerPresenterFactory.create(getRecentEmojis)
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): CustomReactionState {
|
override fun present(): CustomReactionState {
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
var internalTarget by remember { mutableStateOf<InternalTarget>(InternalTarget.None) }
|
||||||
var recentEmojis by remember { mutableStateOf<ImmutableList<String>>(persistentListOf()) }
|
val emojiPickerState = emojiPickerPresenter.present()
|
||||||
|
|
||||||
val target: MutableState<CustomReactionState.Target> = remember {
|
|
||||||
mutableStateOf(CustomReactionState.Target.None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handleShowCustomReactionSheet(event: TimelineItem.Event) {
|
|
||||||
target.value = CustomReactionState.Target.Loading(event)
|
|
||||||
localCoroutineScope.launch {
|
|
||||||
recentEmojis = getRecentEmojis().getOrNull() ?: persistentListOf()
|
|
||||||
target.value = CustomReactionState.Target.Success(
|
|
||||||
event = event,
|
|
||||||
emojibaseStore = emojibaseProvider.emojibaseStore
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handleDismissCustomReactionSheet() {
|
|
||||||
target.value = CustomReactionState.Target.None
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handleEvent(event: CustomReactionEvent) {
|
fun handleEvent(event: CustomReactionEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is CustomReactionEvent.ShowCustomReactionSheet -> handleShowCustomReactionSheet(event.event)
|
is CustomReactionEvent.ShowCustomReactionSheet -> {
|
||||||
is CustomReactionEvent.DismissCustomReactionSheet -> handleDismissCustomReactionSheet()
|
internalTarget = InternalTarget.Showing(event.event)
|
||||||
|
}
|
||||||
|
is CustomReactionEvent.DismissCustomReactionSheet -> {
|
||||||
|
internalTarget = InternalTarget.None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val event = (target.value as? CustomReactionState.Target.Success)?.event
|
|
||||||
val selectedEmoji = event
|
val computedTarget = when (val target = internalTarget) {
|
||||||
|
InternalTarget.None -> CustomReactionState.Target.None
|
||||||
|
is InternalTarget.Showing -> if (emojiPickerState.isReady) {
|
||||||
|
CustomReactionState.Target.Success(
|
||||||
|
event = target.event,
|
||||||
|
emojiPickerState = emojiPickerState,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
CustomReactionState.Target.Loading(target.event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val eventForSelection = (computedTarget as? CustomReactionState.Target.Success)?.event
|
||||||
|
val selectedEmoji = eventForSelection
|
||||||
?.reactionsState
|
?.reactionsState
|
||||||
?.reactions
|
?.reactions
|
||||||
?.mapNotNull { if (it.isHighlighted) it.key else null }
|
?.mapNotNull { if (it.isHighlighted) it.key else null }
|
||||||
@@ -69,10 +63,14 @@ class CustomReactionPresenter(
|
|||||||
.toImmutableSet()
|
.toImmutableSet()
|
||||||
|
|
||||||
return CustomReactionState(
|
return CustomReactionState(
|
||||||
target = target.value,
|
target = computedTarget,
|
||||||
selectedEmoji = selectedEmoji,
|
selectedEmoji = selectedEmoji,
|
||||||
recentEmojis = recentEmojis,
|
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed interface InternalTarget {
|
||||||
|
data object None : InternalTarget
|
||||||
|
data class Showing(val event: TimelineItem.Event) : InternalTarget
|
||||||
|
}
|
||||||
|
|||||||
+2
-4
@@ -8,15 +8,13 @@
|
|||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.components.customreaction
|
package io.element.android.features.messages.impl.timeline.components.customreaction
|
||||||
|
|
||||||
import io.element.android.emojibasebindings.EmojibaseStore
|
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerState
|
||||||
import kotlinx.collections.immutable.ImmutableSet
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
|
|
||||||
data class CustomReactionState(
|
data class CustomReactionState(
|
||||||
val target: Target,
|
val target: Target,
|
||||||
val selectedEmoji: ImmutableSet<String>,
|
val selectedEmoji: ImmutableSet<String>,
|
||||||
val recentEmojis: ImmutableList<String>,
|
|
||||||
val eventSink: (CustomReactionEvent) -> Unit,
|
val eventSink: (CustomReactionEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
sealed interface Target {
|
sealed interface Target {
|
||||||
@@ -24,7 +22,7 @@ data class CustomReactionState(
|
|||||||
data class Loading(val event: TimelineItem.Event) : Target
|
data class Loading(val event: TimelineItem.Event) : Target
|
||||||
data class Success(
|
data class Success(
|
||||||
val event: TimelineItem.Event,
|
val event: TimelineItem.Event,
|
||||||
val emojibaseStore: EmojibaseStore,
|
val emojiPickerState: EmojiPickerState,
|
||||||
) : Target
|
) : Target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-114
@@ -1,114 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.components.customreaction.picker
|
|
||||||
|
|
||||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.platform.LocalInspectionMode
|
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
|
||||||
import io.element.android.emojibasebindings.Emoji
|
|
||||||
import io.element.android.emojibasebindings.EmojibaseStore
|
|
||||||
import io.element.android.features.messages.impl.R
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.icon
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.title
|
|
||||||
import io.element.android.libraries.architecture.Presenter
|
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
|
|
||||||
class EmojiPickerPresenter(
|
|
||||||
private val emojibaseStore: EmojibaseStore,
|
|
||||||
private val recentEmojis: ImmutableList<String>,
|
|
||||||
private val coroutineDispatchers: CoroutineDispatchers,
|
|
||||||
) : Presenter<EmojiPickerState> {
|
|
||||||
@Composable
|
|
||||||
override fun present(): EmojiPickerState {
|
|
||||||
val queryState = rememberTextFieldState()
|
|
||||||
var isSearchActive by remember { mutableStateOf(false) }
|
|
||||||
var emojiResults by remember { mutableStateOf<SearchBarResultState<ImmutableList<Emoji>>>(SearchBarResultState.Initial()) }
|
|
||||||
|
|
||||||
val recentEmojiIcon = CompoundIcons.History()
|
|
||||||
val categories = remember {
|
|
||||||
val providedCategories = emojibaseStore.categories.map { (category, emojis) ->
|
|
||||||
EmojiCategory(
|
|
||||||
titleId = category.title,
|
|
||||||
icon = IconSource.Vector(category.icon),
|
|
||||||
emojis = emojis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (recentEmojis.isNotEmpty()) {
|
|
||||||
val recentEmojis = recentEmojis.mapNotNull { recentEmoji ->
|
|
||||||
emojibaseStore.allEmojis.find { it.unicode == recentEmoji }
|
|
||||||
}.toImmutableList()
|
|
||||||
val recentCategory =
|
|
||||||
EmojiCategory(
|
|
||||||
titleId = R.string.emoji_picker_category_recent,
|
|
||||||
icon = IconSource.Vector(recentEmojiIcon),
|
|
||||||
emojis = recentEmojis
|
|
||||||
)
|
|
||||||
(listOf(recentCategory) + providedCategories).toImmutableList()
|
|
||||||
} else {
|
|
||||||
providedCategories.toImmutableList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val searchQuery = queryState.text.toString()
|
|
||||||
LaunchedEffect(searchQuery) {
|
|
||||||
emojiResults = if (searchQuery.isEmpty()) {
|
|
||||||
SearchBarResultState.Initial()
|
|
||||||
} else {
|
|
||||||
// Add a small delay to avoid doing too many computations when the user is typing quickly
|
|
||||||
delay(100.milliseconds)
|
|
||||||
|
|
||||||
val lowercaseQuery = searchQuery.lowercase()
|
|
||||||
val results = withContext(coroutineDispatchers.computation) {
|
|
||||||
emojibaseStore.allEmojis
|
|
||||||
.asSequence()
|
|
||||||
.filter { emoji ->
|
|
||||||
emoji.tags.orEmpty().any { it.contains(lowercaseQuery) } ||
|
|
||||||
emoji.shortcodes.any { it.contains(lowercaseQuery) }
|
|
||||||
}
|
|
||||||
.take(60)
|
|
||||||
.toImmutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchBarResultState.Results(results)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val isInPreview = LocalInspectionMode.current
|
|
||||||
fun handleEvent(event: EmojiPickerEvent) {
|
|
||||||
when (event) {
|
|
||||||
// For some reason, in preview mode the SearchBar emits this event with an `isActive = true` value automatically
|
|
||||||
is EmojiPickerEvent.ToggleSearchActive -> if (!isInPreview) {
|
|
||||||
isSearchActive = event.isActive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return EmojiPickerState(
|
|
||||||
categories = categories,
|
|
||||||
allEmojis = emojibaseStore.allEmojis,
|
|
||||||
searchQuery = queryState,
|
|
||||||
isSearchActive = isSearchActive,
|
|
||||||
searchResults = emojiResults,
|
|
||||||
eventSink = ::handleEvent,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,14 +6,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Зашыфравана невядомай ці выдаленай прыладай."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Зашыфравана невядомай ці выдаленай прыладай."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Зашыфравана прыладай, не пацверджанай яе ўладальнікам."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Зашыфравана прыладай, не пацверджанай яе ўладальнікам."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Зашыфравана неправераным карыстальнікам."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Зашыфравана неправераным карыстальнікам."</string>
|
||||||
<string name="emoji_picker_category_activity">"Актыўнасці"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Сцягі"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Ежа & Напоі"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Жывёлы & Прырода"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Аб\'екты"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Усмешкі & Удзельнікі"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Падарожжы & Месцы"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Сімвалы"</string>
|
|
||||||
<string name="screen_media_upload_preview_error_failed_processing">"Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз."</string>
|
<string name="screen_media_upload_preview_error_failed_processing">"Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_sending">"Не атрымалася загрузіць медыяфайлы, паспрабуйце яшчэ раз."</string>
|
<string name="screen_media_upload_preview_error_failed_sending">"Не атрымалася загрузіць медыяфайлы, паспрабуйце яшчэ раз."</string>
|
||||||
<string name="screen_media_upload_preview_processing">"Апрацоўка…"</string>
|
<string name="screen_media_upload_preview_processing">"Апрацоўка…"</string>
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="crypto_event_authenticity_sent_in_clear">"Без шифроване"</string>
|
<string name="crypto_event_authenticity_sent_in_clear">"Без шифроване"</string>
|
||||||
<string name="emoji_picker_category_activity">"Дейности"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Знамена"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Храна & Напитки"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Животни & Природа"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Обекти"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Усмивки & Хора"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Пътуване & Места"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Символи"</string>
|
|
||||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Докоснете, за да промените качеството на качване на видео"</string>
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"Докоснете, за да промените качеството на качване на видео"</string>
|
||||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Файлът не можа да бъде качен."</string>
|
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Файлът не можа да бъде качен."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_processing">"Неуспешна обработка на мултимедия за качване, моля, опитайте отново."</string>
|
<string name="screen_media_upload_preview_error_failed_processing">"Неуспешна обработка на мултимедия за качване, моля, опитайте отново."</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Xifrat per un dispositiu desconegut o eliminat."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Xifrat per un dispositiu desconegut o eliminat."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Xifrat per un dispositiu no verificat pel seu propietari."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Xifrat per un dispositiu no verificat pel seu propietari."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Xifrat per un usuari no verificat."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Xifrat per un usuari no verificat."</string>
|
||||||
<string name="emoji_picker_category_activity">"Activitats"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Banderes"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Menjar & begudes"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Animals i naturalesa"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Objectes"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Cares i persones"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Viatges i llocs"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Emoticones recents"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Símbols"</string>
|
|
||||||
<string name="screen_media_upload_preview_caption_warning">"És possible que les llegendes no siguin visibles pels que utilitzin aplicacions antigues."</string>
|
<string name="screen_media_upload_preview_caption_warning">"És possible que les llegendes no siguin visibles pels que utilitzin aplicacions antigues."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_processing">"No s\'ha pogut processar el contingut que s\'havia de pujar. Torna-ho a provar."</string>
|
<string name="screen_media_upload_preview_error_failed_processing">"No s\'ha pogut processar el contingut que s\'havia de pujar. Torna-ho a provar."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_sending">"No s\'ha pogut pujar el contingut. Torna-ho a provar."</string>
|
<string name="screen_media_upload_preview_error_failed_sending">"No s\'ha pogut pujar el contingut. Torna-ho a provar."</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Šifrováno neznámým nebo smazaným zařízením."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Šifrováno neznámým nebo smazaným zařízením."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Šifrováno zařízením, které nebylo ověřeno jeho vlastníkem."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Šifrováno zařízením, které nebylo ověřeno jeho vlastníkem."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Šifrováno neověřeným uživatelem."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Šifrováno neověřeným uživatelem."</string>
|
||||||
<string name="emoji_picker_category_activity">"Aktivity"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Vlajky"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Jídlo a nápoje"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Zvířata a příroda"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Předměty"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Smajlíci a lidé"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Cestování a místa"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Nedávné emotikony"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Symboly"</string>
|
|
||||||
<string name="screen_media_upload_preview_caption_warning">"Titulky nemusí být viditelné pro lidi, kteří používají starší aplikace."</string>
|
<string name="screen_media_upload_preview_caption_warning">"Titulky nemusí být viditelné pro lidi, kteří používají starší aplikace."</string>
|
||||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Klepnutím změníte kvalitu nahrávání videa"</string>
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"Klepnutím změníte kvalitu nahrávání videa"</string>
|
||||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Soubor nelze nahrát."</string>
|
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Soubor nelze nahrát."</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Wedi\'i amgryptio gan ddyfais anhysbys neu wedi\'i dileu."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Wedi\'i amgryptio gan ddyfais anhysbys neu wedi\'i dileu."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Wedi\'i amgryptio gan ddyfais nad yw wedi\'i wirio gan ei pherchennog."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Wedi\'i amgryptio gan ddyfais nad yw wedi\'i wirio gan ei pherchennog."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Wedi\'i amgryptio gan ddefnyddiwr heb ei wirio."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Wedi\'i amgryptio gan ddefnyddiwr heb ei wirio."</string>
|
||||||
<string name="emoji_picker_category_activity">"Gweithgareddau"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Baneri"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Bwyd a Diod"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Anifeiliaid a Natur"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Gwrthrychau"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Wynebau Hapus a Phobl"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Teithio a Llefydd"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Emojis diweddar"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Symbolau"</string>
|
|
||||||
<string name="screen_media_upload_preview_caption_warning">"Efallai na fydd capsiynau yn weladwy i bobl sy\'n defnyddio apiau hŷn."</string>
|
<string name="screen_media_upload_preview_caption_warning">"Efallai na fydd capsiynau yn weladwy i bobl sy\'n defnyddio apiau hŷn."</string>
|
||||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Tapiwch i newid ansawdd llwytho\'r fideo"</string>
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"Tapiwch i newid ansawdd llwytho\'r fideo"</string>
|
||||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Nid oedd modd llwytho\'r ffeil."</string>
|
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Nid oedd modd llwytho\'r ffeil."</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Krypteret af en ukendt eller slettet enhed."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Krypteret af en ukendt eller slettet enhed."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Krypteret af en enhed, der ikke er verificeret af sin ejer."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Krypteret af en enhed, der ikke er verificeret af sin ejer."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Krypteret af en ikke-verificeret bruger."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Krypteret af en ikke-verificeret bruger."</string>
|
||||||
<string name="emoji_picker_category_activity">"Aktiviteter"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Flag"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Mad og drikke"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Dyr og natur"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Objekter"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Smileys og personer"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Rejser og steder"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Seneste emojis"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Symboler"</string>
|
|
||||||
<string name="screen_media_upload_preview_caption_warning">"Billedtekster er muligvis ikke synlige for personer, der bruger ældre apps."</string>
|
<string name="screen_media_upload_preview_caption_warning">"Billedtekster er muligvis ikke synlige for personer, der bruger ældre apps."</string>
|
||||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Tryk for at ændre videokvaliteten i uploadet"</string>
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"Tryk for at ændre videokvaliteten i uploadet"</string>
|
||||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Filen kunne ikke uploades."</string>
|
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Filen kunne ikke uploades."</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Verschlüsselt von einem unbekannten oder gelöschten Gerät."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Verschlüsselt von einem unbekannten oder gelöschten Gerät."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Verschlüsselt durch ein Gerät, das nicht von seinem Besitzer verifiziert wurde."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Verschlüsselt durch ein Gerät, das nicht von seinem Besitzer verifiziert wurde."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Verschlüsselt durch einen nicht verifizierten Nutzer."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Verschlüsselt durch einen nicht verifizierten Nutzer."</string>
|
||||||
<string name="emoji_picker_category_activity">"Aktivitäten"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Flaggen"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Essen & Trinken"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Tiere & Natur"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Objekte"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Smileys & Menschen"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Reisen & Orte"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Zuletzt verwendete Emojis"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Symbole"</string>
|
|
||||||
<string name="screen_media_upload_preview_caption_warning">"Bildunterschriften sind für Nutzer älterer Apps möglicherweise nicht sichtbar."</string>
|
<string name="screen_media_upload_preview_caption_warning">"Bildunterschriften sind für Nutzer älterer Apps möglicherweise nicht sichtbar."</string>
|
||||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Tippe, um die Qualität des Video-Uploads zu ändern"</string>
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"Tippe, um die Qualität des Video-Uploads zu ändern"</string>
|
||||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Die Datei konnte nicht hochgeladen werden."</string>
|
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Die Datei konnte nicht hochgeladen werden."</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Κρυπτογραφημένο από άγνωστη ή διαγεγραμμένη συσκευή."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Κρυπτογραφημένο από άγνωστη ή διαγεγραμμένη συσκευή."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Κρυπτογραφημένο από μια συσκευή που δεν έχει επαληθευτεί από τον ιδιοκτήτη της."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Κρυπτογραφημένο από μια συσκευή που δεν έχει επαληθευτεί από τον ιδιοκτήτη της."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Κρυπτογραφημένο από μη επαληθευμένο χρήστη."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Κρυπτογραφημένο από μη επαληθευμένο χρήστη."</string>
|
||||||
<string name="emoji_picker_category_activity">"Δραστηριότητες"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Σημαίες"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Φαγητό & Ποτό"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Ζώα & Φύση"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Αντικείμενα"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Φατσούλες & Άνθρωποι"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Ταξίδια & Μέρη"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Πρόσφατα emoji"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Σύμβολα"</string>
|
|
||||||
<string name="screen_media_upload_preview_caption_warning">"Οι λεζάντες ενδέχεται να μην είναι ορατές σε άτομα που χρησιμοποιούν παλαιότερες εφαρμογές."</string>
|
<string name="screen_media_upload_preview_caption_warning">"Οι λεζάντες ενδέχεται να μην είναι ορατές σε άτομα που χρησιμοποιούν παλαιότερες εφαρμογές."</string>
|
||||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Πατήστε για να αλλάξετε την ποιότητα μεταφόρτωσης βίντεο"</string>
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"Πατήστε για να αλλάξετε την ποιότητα μεταφόρτωσης βίντεο"</string>
|
||||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Δεν ήταν δυνατή η μεταφόρτωση του αρχείου."</string>
|
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Δεν ήταν δυνατή η μεταφόρτωση του αρχείου."</string>
|
||||||
|
|||||||
@@ -7,14 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Cifrado por un dispositivo desconocido o eliminado."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Cifrado por un dispositivo desconocido o eliminado."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Cifrado por un dispositivo no verificado por su propietario."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Cifrado por un dispositivo no verificado por su propietario."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Cifrado por un usuario no verificado."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Cifrado por un usuario no verificado."</string>
|
||||||
<string name="emoji_picker_category_activity">"Actividades"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Banderas"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Comida y bebida"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Animales y naturaleza"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Objetos"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Emojis y personas"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Viajes y lugares"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Símbolos"</string>
|
|
||||||
<string name="screen_media_upload_preview_caption_warning">"Es posible que las leyendas no sean visibles para las personas que usan aplicaciones más antiguas."</string>
|
<string name="screen_media_upload_preview_caption_warning">"Es posible que las leyendas no sean visibles para las personas que usan aplicaciones más antiguas."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_processing">"Error al procesar el contenido multimedia, por favor inténtalo de nuevo."</string>
|
<string name="screen_media_upload_preview_error_failed_processing">"Error al procesar el contenido multimedia, por favor inténtalo de nuevo."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_sending">"Error al subir el contenido multimedia, por favor inténtalo de nuevo."</string>
|
<string name="screen_media_upload_preview_error_failed_sending">"Error al subir el contenido multimedia, por favor inténtalo de nuevo."</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Krüptitud tundmatu või kustutatud seadme poolt."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Krüptitud tundmatu või kustutatud seadme poolt."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Krüptitud seadme poolt, mida tema omanik pole verifitseerinud."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Krüptitud seadme poolt, mida tema omanik pole verifitseerinud."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Krüptitud verifitseerimata kasutaja poolt."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Krüptitud verifitseerimata kasutaja poolt."</string>
|
||||||
<string name="emoji_picker_category_activity">"Tegevused"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Lipud"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Toit ja jook"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Loomad ja loodus"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Esemed"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Emotikonid ja inimesed"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Reisimine ja kohad"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Hiljutised emojid"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Sümbolid"</string>
|
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally">"Pööra pilti ümber rõhttelje"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally">"Pööra pilti ümber rõhttelje"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Pilt on pööratud ümber rõhttelje"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Pilt on pööratud ümber rõhttelje"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Pildi algne variant"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Pildi algne variant"</string>
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="crypto_event_authenticity_sent_in_clear">"Ez dago zifratuta."</string>
|
<string name="crypto_event_authenticity_sent_in_clear">"Ez dago zifratuta."</string>
|
||||||
<string name="emoji_picker_category_activity">"Jarduerak"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Banderak"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Jana eta edana"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Animaliak eta natura"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Objektuak"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Irribartxoak eta jendea"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Bidaiak eta tokiak"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Ikurrak"</string>
|
|
||||||
<string name="screen_media_upload_preview_error_failed_sending">"Huts egin du multimedia igotzeak, saiatu berriro."</string>
|
<string name="screen_media_upload_preview_error_failed_sending">"Huts egin du multimedia igotzeak, saiatu berriro."</string>
|
||||||
<string name="screen_report_content_block_user">"Blokeatu erabiltzailea"</string>
|
<string name="screen_report_content_block_user">"Blokeatu erabiltzailea"</string>
|
||||||
<string name="screen_report_content_explanation">"Mezua zure zerbitzariko administratzaileari jakinaraziko zaio. Ezingo dute zifratutako mezurik irakurri."</string>
|
<string name="screen_report_content_explanation">"Mezua zure zerbitzariko administratzaileari jakinaraziko zaio. Ezingo dute zifratutako mezurik irakurri."</string>
|
||||||
|
|||||||
@@ -6,15 +6,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"رمز شده به دست افزارهای ناشناخته یا حذف شده."</string>
|
<string name="crypto_event_authenticity_unknown_device">"رمز شده به دست افزارهای ناشناخته یا حذف شده."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"رمز شده به دست افزارهای که از سوی مالکش تأیید نشده."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"رمز شده به دست افزارهای که از سوی مالکش تأیید نشده."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"رمز شده به دست کاربری تأیید نشده."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"رمز شده به دست کاربری تأیید نشده."</string>
|
||||||
<string name="emoji_picker_category_activity">"فعّالیتها"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"پرچمها"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"غذا و نوشیدنی"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"حیوانات و طبعیت"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"اشیا"</string>
|
|
||||||
<string name="emoji_picker_category_people">"شکلکها و افراد"</string>
|
|
||||||
<string name="emoji_picker_category_places">"سفر و مکانها"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"شکلکهای اخیر"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"نمادها"</string>
|
|
||||||
<string name="screen_media_upload_preview_error_failed_processing">"پردازش رسانه برای بارگذاری شکست خورد. لطفاً دوباره تلاش کنید."</string>
|
<string name="screen_media_upload_preview_error_failed_processing">"پردازش رسانه برای بارگذاری شکست خورد. لطفاً دوباره تلاش کنید."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_sending">"بارگذاری رسانه شکست خورد. لطفاً دوباره تلاش کنید."</string>
|
<string name="screen_media_upload_preview_error_failed_sending">"بارگذاری رسانه شکست خورد. لطفاً دوباره تلاش کنید."</string>
|
||||||
<string name="screen_media_upload_preview_processing">"پردازش کردن…"</string>
|
<string name="screen_media_upload_preview_processing">"پردازش کردن…"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Tuntemattoman tai poistetun laitteen salaama."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Tuntemattoman tai poistetun laitteen salaama."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Salattu laitteella, jota sen omistaja ei ole vahvistanut."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Salattu laitteella, jota sen omistaja ei ole vahvistanut."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Vahvistamattoman käyttäjän salaama."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Vahvistamattoman käyttäjän salaama."</string>
|
||||||
<string name="emoji_picker_category_activity">"Aktiviteetit"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Liput"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Ruoka ja juoma"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Eläimet ja luonto"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Esineet"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Hymiöt ja ihmiset"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Matkustaminen ja paikat"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Viimeaikaiset emojit"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Symbolit"</string>
|
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally">"Käännä kuva vaakasuunnassa"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally">"Käännä kuva vaakasuunnassa"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Käännetty vaakasuunnassa"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Käännetty vaakasuunnassa"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Alkuperäinen"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Alkuperäinen"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Chiffré par un appareil inconnu ou supprimé."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Chiffré par un appareil inconnu ou supprimé."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Chiffré par un appareil non vérifié par son propriétaire."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Chiffré par un appareil non vérifié par son propriétaire."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Chiffré par un utilisateur non vérifié."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Chiffré par un utilisateur non vérifié."</string>
|
||||||
<string name="emoji_picker_category_activity">"Activités"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Drapeaux"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Nourriture et boissons"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Animaux et nature"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Objets"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Émoticônes et personnes"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Voyages & lieux"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Emojis récents"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Symboles"</string>
|
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally">"Retourner l’image horizontalement"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally">"Retourner l’image horizontalement"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Retournée horizontalement"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Retournée horizontalement"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Originale"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Originale"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Šifrirano nepoznatim ili izbrisanim uređajem."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Šifrirano nepoznatim ili izbrisanim uređajem."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Šifrirano uređajem koji vlasnik nije potvrdio."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Šifrirano uređajem koji vlasnik nije potvrdio."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Šifrirao neprovjereni korisnik."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Šifrirao neprovjereni korisnik."</string>
|
||||||
<string name="emoji_picker_category_activity">"Aktivnosti"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Zastave"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Hrana i piće"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Životinje i priroda"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Objekti"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Emotikoni i osobe"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Putovanja i mjesta"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Nedavni emotikoni"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Simboli"</string>
|
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally">"Okreni sliku vodoravno"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally">"Okreni sliku vodoravno"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Okrenuto vodoravno"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Okrenuto vodoravno"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Izvornik"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Izvornik"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Ismeretlen vagy törölt eszköz által titkosítva."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Ismeretlen vagy törölt eszköz által titkosítva."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"A tulajdonos által nem ellenőrzött eszköz által titkosítva."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"A tulajdonos által nem ellenőrzött eszköz által titkosítva."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Nem ellenőrzött felhasználó által titkosítva."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Nem ellenőrzött felhasználó által titkosítva."</string>
|
||||||
<string name="emoji_picker_category_activity">"Tevékenységek"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Zászlók"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Étel és ital"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Állatok és természet"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Tárgyak"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Mosolyok és emberek"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Utazás és helyek"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Legutóbbi emodzsik"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Szimbólumok"</string>
|
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally">"Kép vízszintes tükrözése"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally">"Kép vízszintes tükrözése"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Vízszintesen tükrözve"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Vízszintesen tükrözve"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Eredeti"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Eredeti"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Dienkripsi oleh perangkat yang tidak dikenal atau dihapus."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Dienkripsi oleh perangkat yang tidak dikenal atau dihapus."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Dienkripsi oleh perangkat yang tidak diverifikasi oleh pemiliknya."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Dienkripsi oleh perangkat yang tidak diverifikasi oleh pemiliknya."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Dienkripsi oleh pengguna yang tidak terverifikasi."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Dienkripsi oleh pengguna yang tidak terverifikasi."</string>
|
||||||
<string name="emoji_picker_category_activity">"Aktivitas"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Bendera"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Makanan & Minuman"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Hewan & Alam"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Objek"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Senyuman & Orang"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Wisata & Tempat"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Emojis Sebelumnya"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Simbol"</string>
|
|
||||||
<string name="screen_image_edition_a11y_rotate_to_the_left">"Putar gambar ke kiri"</string>
|
<string name="screen_image_edition_a11y_rotate_to_the_left">"Putar gambar ke kiri"</string>
|
||||||
<plurals name="screen_image_edition_a11y_rotation_state">
|
<plurals name="screen_image_edition_a11y_rotation_state">
|
||||||
<item quantity="other">"%1$d derajat"</item>
|
<item quantity="other">"%1$d derajat"</item>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Cifrato da un dispositivo sconosciuto o eliminato."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Cifrato da un dispositivo sconosciuto o eliminato."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Cifrato da un dispositivo non verificato dal proprietario."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Cifrato da un dispositivo non verificato dal proprietario."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Cifrato da un utente non verificato."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Cifrato da un utente non verificato."</string>
|
||||||
<string name="emoji_picker_category_activity">"Attività"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Bandiere"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Cibi & Bevande"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Animali & Natura"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Oggetti"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Faccine & Persone"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Viaggi & Luoghi"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Emoji recenti"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Simboli"</string>
|
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally">"Capovolgi l\'immagine orizzontalmente"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally">"Capovolgi l\'immagine orizzontalmente"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Capovolto orizzontalmente"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Capovolto orizzontalmente"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Originale"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Originale"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"削除されたまたは不明な端末により暗号化されています。"</string>
|
<string name="crypto_event_authenticity_unknown_device">"削除されたまたは不明な端末により暗号化されています。"</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"所有者に検証されていない端末により暗号化されています。"</string>
|
<string name="crypto_event_authenticity_unsigned_device">"所有者に検証されていない端末により暗号化されています。"</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"未検証のユーザーにより暗号化されています。"</string>
|
<string name="crypto_event_authenticity_unverified_identity">"未検証のユーザーにより暗号化されています。"</string>
|
||||||
<string name="emoji_picker_category_activity">"アクティビティ"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"旗"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"食べ物"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"動物・自然"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"物"</string>
|
|
||||||
<string name="emoji_picker_category_people">"顔・人"</string>
|
|
||||||
<string name="emoji_picker_category_places">"旅・場所"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"最近使用"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"記号"</string>
|
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally">"水平に反転"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally">"水平に反転"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"垂直に反転"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"垂直に反転"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"元画像"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"元画像"</string>
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="emoji_picker_category_activity">"აქტივობები"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"დროშები"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Საჭმელ-სასმელი"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"ცხოველები & ბუნება"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"ობიექტები"</string>
|
|
||||||
<string name="emoji_picker_category_people">"ღიმილები & ხალხი"</string>
|
|
||||||
<string name="emoji_picker_category_places">"მოგზაურობა და ადგილები"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"სიმბოლოები"</string>
|
|
||||||
<string name="screen_media_upload_preview_error_failed_processing">"მედიის ატვირთვა ვერ მოხერხდა. გთხოვთ, სცადოთ ხელახლა."</string>
|
<string name="screen_media_upload_preview_error_failed_processing">"მედიის ატვირთვა ვერ მოხერხდა. გთხოვთ, სცადოთ ხელახლა."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_sending">"მედიის ატვირთვა ვერ მოხერხდა, გთხოვთ, სცადოთ ხელახლა."</string>
|
<string name="screen_media_upload_preview_error_failed_sending">"მედიის ატვირთვა ვერ მოხერხდა, გთხოვთ, სცადოთ ხელახლა."</string>
|
||||||
<string name="screen_report_content_block_user">"მომხმარებლის დაბლოკვა"</string>
|
<string name="screen_report_content_block_user">"მომხმარებლის დაბლოკვა"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"알 수 없거나 삭제된 장치에 의해 암호화됩니다."</string>
|
<string name="crypto_event_authenticity_unknown_device">"알 수 없거나 삭제된 장치에 의해 암호화됩니다."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"소유자가 확인하지 않은 장치에 의해 암호화되었습니다."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"소유자가 확인하지 않은 장치에 의해 암호화되었습니다."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"검증되지 않은 사용자에 의해 암호화되었습니다."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"검증되지 않은 사용자에 의해 암호화되었습니다."</string>
|
||||||
<string name="emoji_picker_category_activity">"활동"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"깃발"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"음식 & 음료"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"동물 & 자연"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"사물"</string>
|
|
||||||
<string name="emoji_picker_category_people">"표정 & 사람"</string>
|
|
||||||
<string name="emoji_picker_category_places">"여행 & 장소"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"최근 이모지"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"상징"</string>
|
|
||||||
<string name="screen_image_edition_a11y_rotate_to_the_left">"이미지를 왼쪽으로 회전"</string>
|
<string name="screen_image_edition_a11y_rotate_to_the_left">"이미지를 왼쪽으로 회전"</string>
|
||||||
<plurals name="screen_image_edition_a11y_rotation_state">
|
<plurals name="screen_image_edition_a11y_rotation_state">
|
||||||
<item quantity="other">"%1$d도"</item>
|
<item quantity="other">"%1$d도"</item>
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="emoji_picker_category_activity">"Veikla"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Vėliavos"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Maistas ir Gėrimai"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Gyvūnai ir Gamta"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Objektai"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Šypsenėlės ir Žmonės"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Kelionės ir Vietovės"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Simboliai"</string>
|
|
||||||
<string name="screen_media_upload_preview_error_failed_processing">"Nepavyko apdoroti įkeliamos laikmenos, bandykite dar kartą."</string>
|
<string name="screen_media_upload_preview_error_failed_processing">"Nepavyko apdoroti įkeliamos laikmenos, bandykite dar kartą."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_sending">"Nepavyko įkelti laikmenos, pabandykite dar kartą."</string>
|
<string name="screen_media_upload_preview_error_failed_sending">"Nepavyko įkelti laikmenos, pabandykite dar kartą."</string>
|
||||||
<string name="screen_report_content_block_user">"Blokuoti vartotoją"</string>
|
<string name="screen_report_content_block_user">"Blokuoti vartotoją"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Kryptert av en ukjent eller slettet enhet."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Kryptert av en ukjent eller slettet enhet."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Kryptert med en enhet som ikke er verifisert av eieren."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Kryptert med en enhet som ikke er verifisert av eieren."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Kryptert av en ubekreftet bruker."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Kryptert av en ubekreftet bruker."</string>
|
||||||
<string name="emoji_picker_category_activity">"Aktiviteter"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Flagg"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Mat og drikke"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Dyr og natur"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Gjenstander"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Smilefjes og mennesker"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Reising og steder"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Nylige emojier"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Symboler"</string>
|
|
||||||
<string name="screen_media_upload_preview_caption_warning">"Teksting er kanskje ikke synlig for personer som bruker eldre apper."</string>
|
<string name="screen_media_upload_preview_caption_warning">"Teksting er kanskje ikke synlig for personer som bruker eldre apper."</string>
|
||||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Trykk for å endre kvaliteten på videoopplastingen"</string>
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"Trykk for å endre kvaliteten på videoopplastingen"</string>
|
||||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Filen kunne ikke lastes opp."</string>
|
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Filen kunne ikke lastes opp."</string>
|
||||||
|
|||||||
@@ -6,14 +6,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Versleuteld door een onbekend of verwijderd apparaat."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Versleuteld door een onbekend of verwijderd apparaat."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Versleuteld door een apparaat dat niet is geverifieerd door de eigenaar."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Versleuteld door een apparaat dat niet is geverifieerd door de eigenaar."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Versleuteld door een niet-geverifieerde gebruiker."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Versleuteld door een niet-geverifieerde gebruiker."</string>
|
||||||
<string name="emoji_picker_category_activity">"Activiteiten"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Vlaggen"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Eten & Drinken"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Dieren & Natuur"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Voorwerpen"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Smileys & Mensen"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Reizen & Locaties"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Symbolen"</string>
|
|
||||||
<string name="screen_media_upload_preview_error_failed_processing">"Het verwerken van media voor uploaden is mislukt. Probeer het opnieuw."</string>
|
<string name="screen_media_upload_preview_error_failed_processing">"Het verwerken van media voor uploaden is mislukt. Probeer het opnieuw."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_sending">"Het uploaden van media is mislukt. Probeer het opnieuw."</string>
|
<string name="screen_media_upload_preview_error_failed_sending">"Het uploaden van media is mislukt. Probeer het opnieuw."</string>
|
||||||
<string name="screen_report_content_block_user">"Gebruiker blokkeren"</string>
|
<string name="screen_report_content_block_user">"Gebruiker blokkeren"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Zaszyfrowana przez nieznane lub usunięte urządzenie."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Zaszyfrowana przez nieznane lub usunięte urządzenie."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Zaszyfrowana przez urządzenie niezweryfikowane przez jego właściciela."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Zaszyfrowana przez urządzenie niezweryfikowane przez jego właściciela."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Zaszyfrowana przez niezweryfikowanego użytkownika."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Zaszyfrowana przez niezweryfikowanego użytkownika."</string>
|
||||||
<string name="emoji_picker_category_activity">"Aktywności"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Flagi"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Jedzenie i napoje"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Zwierzęta i natura"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Obiekty"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Buźki i osoby"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Podróż i miejsca"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Ostatnie emoji"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Symbole"</string>
|
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally">"Odwróć obraz w poziomie"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally">"Odwróć obraz w poziomie"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Odwrócono w poziomie"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Odwrócono w poziomie"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Oryginalny"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Oryginalny"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Criptografado por um dispositivo desconhecido ou apagado."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Criptografado por um dispositivo desconhecido ou apagado."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Criptografado por um dispositivo que não foi verificado pelo seu dono."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Criptografado por um dispositivo que não foi verificado pelo seu dono."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Criptografado por um usuário não verificado."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Criptografado por um usuário não verificado."</string>
|
||||||
<string name="emoji_picker_category_activity">"Atividades"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Bandeiras"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Comida & Bebida"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Animais & Natureza"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Objetos"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Sorrisos & Pessoas"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Viagens & Lugares"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Emojis recentes"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Símbolos"</string>
|
|
||||||
<string name="screen_media_upload_preview_caption_warning">"As legendas podem não ser visíveis para pessoas que usam apps mais antigos."</string>
|
<string name="screen_media_upload_preview_caption_warning">"As legendas podem não ser visíveis para pessoas que usam apps mais antigos."</string>
|
||||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Toque para alterar a qualidade do envio do vídeo"</string>
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"Toque para alterar a qualidade do envio do vídeo"</string>
|
||||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"O arquivo não pôde ser enviado."</string>
|
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"O arquivo não pôde ser enviado."</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Cifragem com origem num dispositivo eliminado ou desconhecido."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Cifragem com origem num dispositivo eliminado ou desconhecido."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Cifragem com origem num dispositivo não verificado pelo seu dono."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Cifragem com origem num dispositivo não verificado pelo seu dono."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Cifragem com origem num utilizador não verificado."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Cifragem com origem num utilizador não verificado."</string>
|
||||||
<string name="emoji_picker_category_activity">"Atividades"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Bandeiras"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Comidas e Bebidas"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Animais e Natureza"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Objetos"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Caras e Pessoas"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Viagens e Lugares"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Emojis recentes"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Símbolos"</string>
|
|
||||||
<string name="screen_media_upload_preview_caption_warning">"As legendas poderão não ser visíveis em versões mais antigas da aplicação."</string>
|
<string name="screen_media_upload_preview_caption_warning">"As legendas poderão não ser visíveis em versões mais antigas da aplicação."</string>
|
||||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Toca para alterar a qualidade de carregamento do vídeo"</string>
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"Toca para alterar a qualidade de carregamento do vídeo"</string>
|
||||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Não foi possível enviar o ficheiro"</string>
|
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Não foi possível enviar o ficheiro"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Criptat de un dispozitiv necunoscut sau șters."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Criptat de un dispozitiv necunoscut sau șters."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Criptat de un dispozitiv care nu este verificat de proprietarul său."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Criptat de un dispozitiv care nu este verificat de proprietarul său."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Criptat de un utilizator neverificat."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Criptat de un utilizator neverificat."</string>
|
||||||
<string name="emoji_picker_category_activity">"Activități"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Steaguri"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Mâncare & Băutură"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Animale și Natură"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Obiecte"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Fețe zâmbitoare & Oameni"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Călătorii & Locuri"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Emoticoane recente"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Simboluri"</string>
|
|
||||||
<string name="screen_media_upload_preview_caption_warning">"Este posibil ca descrierile să nu fie vizibile pentru persoanele care folosesc aplicații mai vechi."</string>
|
<string name="screen_media_upload_preview_caption_warning">"Este posibil ca descrierile să nu fie vizibile pentru persoanele care folosesc aplicații mai vechi."</string>
|
||||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Atingeți pentru a modifica calitatea încărcării videoclipului"</string>
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"Atingeți pentru a modifica calitatea încărcării videoclipului"</string>
|
||||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Fișierul nu a putut fi încărcat."</string>
|
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Fișierul nu a putut fi încărcat."</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Зашифровано неизвестным или удаленным устройством."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Зашифровано неизвестным или удаленным устройством."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Зашифровано устройством, не проверенным его владельцем."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Зашифровано устройством, не проверенным его владельцем."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Зашифровано непроверенным пользователем."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Зашифровано непроверенным пользователем."</string>
|
||||||
<string name="emoji_picker_category_activity">"Активности"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Флаги"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Еда и напитки"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Животные и природа"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Объекты"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Эмодзи и люди"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Путешествия и места"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Недавние эмодзи"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Символы"</string>
|
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally">"Перевернуть изображение по горизонтали"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally">"Перевернуть изображение по горизонтали"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Перевернуто по горизонтали"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Перевернуто по горизонтали"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Оригинал"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Оригинал"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Zašifrované neznámym alebo odstráneným zariadením."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Zašifrované neznámym alebo odstráneným zariadením."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Šifrované zariadením, ktoré nie je overené jeho majiteľom."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Šifrované zariadením, ktoré nie je overené jeho majiteľom."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Šifrované neovereným používateľom."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Šifrované neovereným používateľom."</string>
|
||||||
<string name="emoji_picker_category_activity">"Aktivity"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Vlajky"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Jedlo a nápoje"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Zvieratá a príroda"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Predmety"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Smajlíky a ľudia"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Cestovanie a miesta"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Nedávne emotikony"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Symboly"</string>
|
|
||||||
<string name="screen_media_upload_preview_caption_warning">"Titulky nemusia byť viditeľné pre ľudí používajúcich staršie aplikácie."</string>
|
<string name="screen_media_upload_preview_caption_warning">"Titulky nemusia byť viditeľné pre ľudí používajúcich staršie aplikácie."</string>
|
||||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Ťuknutím zmeníte kvalitu nahratého videa"</string>
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"Ťuknutím zmeníte kvalitu nahratého videa"</string>
|
||||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Súbor sa nepodarilo nahrať."</string>
|
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Súbor sa nepodarilo nahrať."</string>
|
||||||
|
|||||||
@@ -7,14 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Krypterad av en okänd eller raderad enhet."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Krypterad av en okänd eller raderad enhet."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Krypterad av en enhet som inte verifierats av ägaren."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Krypterad av en enhet som inte verifierats av ägaren."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Krypterad av en overifierad användare."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Krypterad av en overifierad användare."</string>
|
||||||
<string name="emoji_picker_category_activity">"Aktiviteter"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Flaggor"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Mat & dryck"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Djur & natur"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Föremål"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Smileys & personer"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Resor & platser"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Symboler"</string>
|
|
||||||
<string name="screen_media_upload_preview_caption_warning">"Bildtexter kanske inte är synliga för personer som använder äldre appar."</string>
|
<string name="screen_media_upload_preview_caption_warning">"Bildtexter kanske inte är synliga för personer som använder äldre appar."</string>
|
||||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Tryck för att ändra videouppladdningskvaliteten"</string>
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"Tryck för att ändra videouppladdningskvaliteten"</string>
|
||||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Filen kunde inte laddas upp."</string>
|
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Filen kunde inte laddas upp."</string>
|
||||||
|
|||||||
@@ -7,14 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Bilinmeyen veya silinmiş bir cihaz tarafından şifrelenmiştir."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Bilinmeyen veya silinmiş bir cihaz tarafından şifrelenmiştir."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Sahibi tarafından doğrulanmamış bir cihaz tarafından şifrelenmiştir."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Sahibi tarafından doğrulanmamış bir cihaz tarafından şifrelenmiştir."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Doğrulanmamış bir kullanıcı tarafından şifrelenmiştir."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Doğrulanmamış bir kullanıcı tarafından şifrelenmiştir."</string>
|
||||||
<string name="emoji_picker_category_activity">"Aktiviteler"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Bayraklar"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Yiyecek ve İçecek"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Hayvanlar ve Doğa"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Nesneler"</string>
|
|
||||||
<string name="emoji_picker_category_people">"İfadeler ve İnsanlar"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Seyahat ve Yerler"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Simgeler"</string>
|
|
||||||
<string name="screen_media_upload_preview_caption_warning">"Açıklamalar, eski uygulamaları kullanan kişiler tarafından görülemeyebilir."</string>
|
<string name="screen_media_upload_preview_caption_warning">"Açıklamalar, eski uygulamaları kullanan kişiler tarafından görülemeyebilir."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_processing">"Medya yüklenemedi, lütfen tekrar deneyin."</string>
|
<string name="screen_media_upload_preview_error_failed_processing">"Medya yüklenemedi, lütfen tekrar deneyin."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_sending">"Medya yüklenemedi, lütfen tekrar deneyin."</string>
|
<string name="screen_media_upload_preview_error_failed_sending">"Medya yüklenemedi, lütfen tekrar deneyin."</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Зашифровано невідомим або видаленим пристроєм."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Зашифровано невідомим або видаленим пристроєм."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Зашифровано пристроєм, який не звірено його власником."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Зашифровано пристроєм, який не звірено його власником."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Зашифровано не звіреним користувачем."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Зашифровано не звіреним користувачем."</string>
|
||||||
<string name="emoji_picker_category_activity">"Дії"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Прапори"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Їжа та напої"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Тварини та природа"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Об\'єкти"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Смайлики та люди"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Подорожі та місця"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Нещодавні емодзі"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Символи"</string>
|
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally">"Повернути зображення по горизонталі"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally">"Повернути зображення по горизонталі"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Перевернуто по горизонталі"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Перевернуто по горизонталі"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Оригінал"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Оригінал"</string>
|
||||||
|
|||||||
@@ -6,14 +6,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"کسی نامعلوم یا حذف شدہ آلے کے ذریعے مرموز کردہ۔"</string>
|
<string name="crypto_event_authenticity_unknown_device">"کسی نامعلوم یا حذف شدہ آلے کے ذریعے مرموز کردہ۔"</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"کسی ایسے آلے کے ذریعے مرموز کردہ جس کی توثیق اسکے مالک سے نہیں ہوئی۔"</string>
|
<string name="crypto_event_authenticity_unsigned_device">"کسی ایسے آلے کے ذریعے مرموز کردہ جس کی توثیق اسکے مالک سے نہیں ہوئی۔"</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"ایک غیر توثیق شدہ صارف کے ذریعے مرموز کردہ۔"</string>
|
<string name="crypto_event_authenticity_unverified_identity">"ایک غیر توثیق شدہ صارف کے ذریعے مرموز کردہ۔"</string>
|
||||||
<string name="emoji_picker_category_activity">"سرگرمیاں"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"جھنڈے"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"طعم و مشروب"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"حیوانات و فطرت"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"اشیاء"</string>
|
|
||||||
<string name="emoji_picker_category_people">"مسکراہٹیں و لوگ"</string>
|
|
||||||
<string name="emoji_picker_category_places">"سفر و مقامات"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"علامتیں"</string>
|
|
||||||
<string name="screen_media_upload_preview_error_failed_processing">"وسائط کا معالجہ برائے ترفیع ناکام، برائے مہربانی دوبارہ کوشش کریں۔"</string>
|
<string name="screen_media_upload_preview_error_failed_processing">"وسائط کا معالجہ برائے ترفیع ناکام، برائے مہربانی دوبارہ کوشش کریں۔"</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_sending">"وسائط رفع کرنے میں ناکام، برائے مہربانی دوبارہ کوشش کریں۔"</string>
|
<string name="screen_media_upload_preview_error_failed_sending">"وسائط رفع کرنے میں ناکام، برائے مہربانی دوبارہ کوشش کریں۔"</string>
|
||||||
<string name="screen_report_content_block_user">"صارف کو مسدود کریں"</string>
|
<string name="screen_report_content_block_user">"صارف کو مسدود کریں"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Nomaʼlum yoki oʻchirib tashlangan qurilma tomonidan shifrlangan."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Nomaʼlum yoki oʻchirib tashlangan qurilma tomonidan shifrlangan."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Egasi tasdiqlamagan qurilma tomonidan shifrlangan."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Egasi tasdiqlamagan qurilma tomonidan shifrlangan."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Tasdiqlanmagan foydalanuvchi tomonidan shifrlangan."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Tasdiqlanmagan foydalanuvchi tomonidan shifrlangan."</string>
|
||||||
<string name="emoji_picker_category_activity">"Faoliyatlar"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Bayroqlar"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Oziq-ovqat va ichimliklar"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Hayvonlar va tabiat"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Ob\'ektlar"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Smayllar va odamlar"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Sayohat va Joylar"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Oxirgi kulgichlar"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Belgilar"</string>
|
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally">"Rasmni gorizontal ravishda aylantiring"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally">"Rasmni gorizontal ravishda aylantiring"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Gorizontal ravishda aylantirildi"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Gorizontal ravishda aylantirildi"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Asl nusxasi"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Asl nusxasi"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Được mã hóa bởi một thiết bị không xác định hoặc đã bị xóa."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Được mã hóa bởi một thiết bị không xác định hoặc đã bị xóa."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Được mã hóa bởi một thiết bị chưa được chủ sở hữu xác minh."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Được mã hóa bởi một thiết bị chưa được chủ sở hữu xác minh."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Được mã hóa bởi một người dùng chưa được xác minh."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Được mã hóa bởi một người dùng chưa được xác minh."</string>
|
||||||
<string name="emoji_picker_category_activity">"Hoạt động"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Cờ"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Thực phẩm và đồ uống"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Động vật và thiên nhiên"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Đồ vật"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Mặt cười & mọi người"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Du lịch và địa danh"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Biểu tượng cảm xúc gần đây"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Biểu tượng"</string>
|
|
||||||
<string name="screen_media_upload_preview_error_failed_processing">"Xử lý phương tiện tải lên không thành công, vui lòng thử lại."</string>
|
<string name="screen_media_upload_preview_error_failed_processing">"Xử lý phương tiện tải lên không thành công, vui lòng thử lại."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_sending">"Không thể tải lên tệp phương tiện. Vui lòng thử lại."</string>
|
<string name="screen_media_upload_preview_error_failed_sending">"Không thể tải lên tệp phương tiện. Vui lòng thử lại."</string>
|
||||||
<string name="screen_report_content_block_user">"Chặn người dùng"</string>
|
<string name="screen_report_content_block_user">"Chặn người dùng"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"由未知或已刪除的裝置加密。"</string>
|
<string name="crypto_event_authenticity_unknown_device">"由未知或已刪除的裝置加密。"</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"由未經其擁有者驗證的裝置加密。"</string>
|
<string name="crypto_event_authenticity_unsigned_device">"由未經其擁有者驗證的裝置加密。"</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"由未經驗證的使用者加密。"</string>
|
<string name="crypto_event_authenticity_unverified_identity">"由未經驗證的使用者加密。"</string>
|
||||||
<string name="emoji_picker_category_activity">"活動"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"旗幟"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"食物與飲料"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"動物與大自然"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"物品"</string>
|
|
||||||
<string name="emoji_picker_category_people">"表情與人物"</string>
|
|
||||||
<string name="emoji_picker_category_places">"旅行與景點"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"最近使用的表情符號"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"標誌"</string>
|
|
||||||
<string name="screen_media_upload_preview_caption_warning">"使用舊應用程式的使用者可能看不到標題。"</string>
|
<string name="screen_media_upload_preview_caption_warning">"使用舊應用程式的使用者可能看不到標題。"</string>
|
||||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"輕點即可變更影片上傳品質"</string>
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"輕點即可變更影片上傳品質"</string>
|
||||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"無法上傳檔案。"</string>
|
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"無法上傳檔案。"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"由未知或已删除的设备加密。"</string>
|
<string name="crypto_event_authenticity_unknown_device">"由未知或已删除的设备加密。"</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"由未经其所有者验证的设备加密。"</string>
|
<string name="crypto_event_authenticity_unsigned_device">"由未经其所有者验证的设备加密。"</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"由未经验证的用户加密。"</string>
|
<string name="crypto_event_authenticity_unverified_identity">"由未经验证的用户加密。"</string>
|
||||||
<string name="emoji_picker_category_activity">"节假日"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"旗帜"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"饮食"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"动物和自然"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"日常物品"</string>
|
|
||||||
<string name="emoji_picker_category_people">"表情和人物"</string>
|
|
||||||
<string name="emoji_picker_category_places">"文旅景点"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"最近的 Emoji"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"符号"</string>
|
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally">"水平翻转图像"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally">"水平翻转图像"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"水平翻转"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"水平翻转"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"原始"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"原始"</string>
|
||||||
|
|||||||
@@ -7,15 +7,6 @@
|
|||||||
<string name="crypto_event_authenticity_unknown_device">"Encrypted by an unknown or deleted device."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Encrypted by an unknown or deleted device."</string>
|
||||||
<string name="crypto_event_authenticity_unsigned_device">"Encrypted by a device not verified by its owner."</string>
|
<string name="crypto_event_authenticity_unsigned_device">"Encrypted by a device not verified by its owner."</string>
|
||||||
<string name="crypto_event_authenticity_unverified_identity">"Encrypted by an unverified user."</string>
|
<string name="crypto_event_authenticity_unverified_identity">"Encrypted by an unverified user."</string>
|
||||||
<string name="emoji_picker_category_activity">"Activities"</string>
|
|
||||||
<string name="emoji_picker_category_flags">"Flags"</string>
|
|
||||||
<string name="emoji_picker_category_foods">"Food & Drink"</string>
|
|
||||||
<string name="emoji_picker_category_nature">"Animals & Nature"</string>
|
|
||||||
<string name="emoji_picker_category_objects">"Objects"</string>
|
|
||||||
<string name="emoji_picker_category_people">"Smileys & People"</string>
|
|
||||||
<string name="emoji_picker_category_places">"Travel & Places"</string>
|
|
||||||
<string name="emoji_picker_category_recent">"Recent emojis"</string>
|
|
||||||
<string name="emoji_picker_category_symbols">"Symbols"</string>
|
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally">"Flip image horizontally"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally">"Flip image horizontally"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Flipped horizontally"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Flipped horizontally"</string>
|
||||||
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Original"</string>
|
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Original"</string>
|
||||||
|
|||||||
+1
-1
@@ -52,6 +52,7 @@ import io.element.android.libraries.core.mimetype.MimeTypes
|
|||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||||
|
import io.element.android.libraries.emoji.api.recentemojis.AddRecentEmoji
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
@@ -94,7 +95,6 @@ import io.element.android.libraries.matrix.test.room.threads.FakeThreadsListServ
|
|||||||
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
||||||
import io.element.android.libraries.matrix.test.timeline.aTimelineItemDebugInfo
|
import io.element.android.libraries.matrix.test.timeline.aTimelineItemDebugInfo
|
||||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||||
import io.element.android.libraries.recentemojis.api.AddRecentEmoji
|
|
||||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||||
import io.element.android.libraries.textcomposer.model.TextEditorState
|
import io.element.android.libraries.textcomposer.model.TextEditorState
|
||||||
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
|
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
|
||||||
|
|||||||
+26
-15
@@ -31,7 +31,6 @@ import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
|||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import io.element.android.emojibasebindings.Emoji
|
import io.element.android.emojibasebindings.Emoji
|
||||||
import io.element.android.emojibasebindings.EmojibaseCategory
|
import io.element.android.emojibasebindings.EmojibaseCategory
|
||||||
import io.element.android.emojibasebindings.EmojibaseStore
|
|
||||||
import io.element.android.features.messages.impl.actionlist.ActionListEvent
|
import io.element.android.features.messages.impl.actionlist.ActionListEvent
|
||||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
import io.element.android.features.messages.impl.actionlist.ActionListState
|
||||||
import io.element.android.features.messages.impl.actionlist.anActionListState
|
import io.element.android.features.messages.impl.actionlist.anActionListState
|
||||||
@@ -48,6 +47,7 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemList
|
|||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemReadReceipts
|
import io.element.android.features.messages.impl.timeline.aTimelineItemReadReceipts
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
|
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineState
|
import io.element.android.features.messages.impl.timeline.aTimelineState
|
||||||
|
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent
|
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
|
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
|
||||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvent
|
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvent
|
||||||
@@ -56,6 +56,8 @@ import io.element.android.features.messages.impl.timeline.components.receipt.bot
|
|||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||||
import io.element.android.features.roomcall.api.aStandByCallState
|
import io.element.android.features.roomcall.api.aStandByCallState
|
||||||
|
import io.element.android.libraries.emoji.impl.picker.DefaultEmojiPickerRenderer
|
||||||
|
import io.element.android.libraries.emoji.impl.picker.anEmojiPickerState
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
||||||
@@ -538,24 +540,23 @@ class MessagesViewTest : RobolectricTest() {
|
|||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||||
|
val emojisByCategory = persistentMapOf(
|
||||||
|
EmojibaseCategory.People to persistentListOf(
|
||||||
|
Emoji(
|
||||||
|
hexcode = "",
|
||||||
|
label = "",
|
||||||
|
tags = persistentListOf(),
|
||||||
|
shortcodes = persistentListOf(),
|
||||||
|
unicode = aUnicode,
|
||||||
|
skins = null,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
val stateWithCustomReactionState = state.copy(
|
val stateWithCustomReactionState = state.copy(
|
||||||
customReactionState = aCustomReactionState(
|
customReactionState = aCustomReactionState(
|
||||||
target = CustomReactionState.Target.Success(
|
target = CustomReactionState.Target.Success(
|
||||||
event = timelineItem,
|
event = timelineItem,
|
||||||
emojibaseStore = EmojibaseStore(
|
emojiPickerState = anEmojiPickerState(emojis = emojisByCategory),
|
||||||
categories = persistentMapOf(
|
|
||||||
EmojibaseCategory.People to persistentListOf(
|
|
||||||
Emoji(
|
|
||||||
hexcode = "",
|
|
||||||
label = "",
|
|
||||||
tags = persistentListOf(),
|
|
||||||
shortcodes = persistentListOf(),
|
|
||||||
unicode = aUnicode,
|
|
||||||
skins = null,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
eventSink = customReactionStateEventsRecorder
|
eventSink = customReactionStateEventsRecorder
|
||||||
),
|
),
|
||||||
@@ -695,6 +696,7 @@ private fun AndroidComposeUiTest<ComponentActivity>.setMessagesView(
|
|||||||
onViewAllPinnedMessagesClick: () -> Unit = EnsureNeverCalled(),
|
onViewAllPinnedMessagesClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onThreadsListClicked: () -> Unit = EnsureNeverCalled(),
|
onThreadsListClicked: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
|
val emojiPickerRenderer = DefaultEmojiPickerRenderer()
|
||||||
setSafeContent {
|
setSafeContent {
|
||||||
// Cannot use the RichTextEditor, so simulate a LocalInspectionMode
|
// Cannot use the RichTextEditor, so simulate a LocalInspectionMode
|
||||||
CompositionLocalProvider(LocalInspectionMode provides true) {
|
CompositionLocalProvider(LocalInspectionMode provides true) {
|
||||||
@@ -711,6 +713,15 @@ private fun AndroidComposeUiTest<ComponentActivity>.setMessagesView(
|
|||||||
onJoinCallClick = onJoinCallClick,
|
onJoinCallClick = onJoinCallClick,
|
||||||
onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick,
|
onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick,
|
||||||
knockRequestsBannerView = {},
|
knockRequestsBannerView = {},
|
||||||
|
customReactionBottomSheet = {
|
||||||
|
CustomReactionBottomSheet(
|
||||||
|
state = state.customReactionState,
|
||||||
|
onSelectEmoji = { uniqueId, emoji ->
|
||||||
|
state.eventSink(MessagesEvent.ToggleReaction(emoji.unicode, uniqueId))
|
||||||
|
},
|
||||||
|
emojiPickerRenderer = emojiPickerRenderer,
|
||||||
|
)
|
||||||
|
},
|
||||||
onThreadsListClick = onThreadsListClicked,
|
onThreadsListClick = onThreadsListClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -27,6 +27,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.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
|
||||||
import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList
|
import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList
|
||||||
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||||
|
import io.element.android.libraries.emoji.api.recentemojis.GetRecentEmojis
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.matrix.api.notification.CallIntent
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
@@ -42,7 +43,6 @@ import io.element.android.libraries.matrix.test.A_USER_ID
|
|||||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||||
import io.element.android.libraries.recentemojis.api.GetRecentEmojis
|
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|||||||
+4
-8
@@ -11,11 +11,11 @@ package io.element.android.features.messages.impl.timeline.components.customreac
|
|||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
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.aTimelineItemReactions
|
||||||
|
import io.element.android.libraries.emoji.api.recentemojis.EmptyGetRecentEmojis
|
||||||
|
import io.element.android.libraries.emoji.test.fakeEmojiPickerPresenterFactory
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
import io.element.android.libraries.recentemojis.test.FakeEmojibaseProvider
|
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -25,8 +25,8 @@ class CustomReactionPresenterTest {
|
|||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
private val presenter = CustomReactionPresenter(
|
private val presenter = CustomReactionPresenter(
|
||||||
emojibaseProvider = FakeEmojibaseProvider(),
|
emojiPickerPresenterFactory = fakeEmojiPickerPresenterFactory(),
|
||||||
getRecentEmojis = { Result.success(persistentListOf()) },
|
getRecentEmojis = EmptyGetRecentEmojis,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -38,8 +38,6 @@ class CustomReactionPresenterTest {
|
|||||||
|
|
||||||
initialState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event))
|
initialState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event))
|
||||||
|
|
||||||
assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.Loading(event))
|
|
||||||
|
|
||||||
val eventId = (awaitItem().target as? CustomReactionState.Target.Success)?.event?.eventId
|
val eventId = (awaitItem().target as? CustomReactionState.Target.Success)?.event?.eventId
|
||||||
assertThat(eventId).isEqualTo(AN_EVENT_ID)
|
assertThat(eventId).isEqualTo(AN_EVENT_ID)
|
||||||
|
|
||||||
@@ -59,8 +57,6 @@ class CustomReactionPresenterTest {
|
|||||||
val key = reactions.reactions.first().key
|
val key = reactions.reactions.first().key
|
||||||
initialState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event))
|
initialState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event))
|
||||||
|
|
||||||
assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.Loading(event))
|
|
||||||
|
|
||||||
val stateWithSelectedEmojis = awaitItem()
|
val stateWithSelectedEmojis = awaitItem()
|
||||||
val eventId = (stateWithSelectedEmojis.target as? CustomReactionState.Target.Success)?.event?.eventId
|
val eventId = (stateWithSelectedEmojis.target as? CustomReactionState.Target.Success)?.event?.eventId
|
||||||
assertThat(eventId).isEqualTo(AN_EVENT_ID)
|
assertThat(eventId).isEqualTo(AN_EVENT_ID)
|
||||||
|
|||||||
-125
@@ -1,125 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.components.customreaction.picker
|
|
||||||
|
|
||||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
|
||||||
import androidx.compose.runtime.InternalComposeApi
|
|
||||||
import androidx.compose.runtime.currentComposer
|
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import app.cash.molecule.RecompositionMode
|
|
||||||
import app.cash.molecule.moleculeFlow
|
|
||||||
import app.cash.turbine.TurbineTestContext
|
|
||||||
import app.cash.turbine.test
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import io.element.android.emojibasebindings.Emoji
|
|
||||||
import io.element.android.emojibasebindings.EmojibaseCategory
|
|
||||||
import io.element.android.emojibasebindings.EmojibaseStore
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
|
||||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
import kotlinx.collections.immutable.toImmutableMap
|
|
||||||
import kotlinx.coroutines.test.TestScope
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class EmojiPickerPresenterTest : RobolectricTest() {
|
|
||||||
@get:Rule
|
|
||||||
val warmUpRule = WarmUpRule()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `updating search query loads new results`() = runTest {
|
|
||||||
testPresenter {
|
|
||||||
skipItems(1)
|
|
||||||
|
|
||||||
val initialState = awaitItem()
|
|
||||||
assertThat(initialState.searchQuery.text.toString()).isEmpty()
|
|
||||||
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
|
|
||||||
|
|
||||||
initialState.searchQuery.setTextAndPlaceCursorAtEnd("smile")
|
|
||||||
assertThat(awaitItem().searchQuery.text.toString()).isEqualTo("smile")
|
|
||||||
|
|
||||||
val stateWithResults = awaitItem()
|
|
||||||
assertThat(stateWithResults.searchQuery.text.toString()).isEqualTo("smile")
|
|
||||||
assertThat(stateWithResults.searchResults).isInstanceOf(SearchBarResultState.Results::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `ToggleSearchActive toggles the search state`() = runTest {
|
|
||||||
testPresenter {
|
|
||||||
skipItems(1)
|
|
||||||
|
|
||||||
val initialState = awaitItem()
|
|
||||||
assertThat(initialState.isSearchActive).isFalse()
|
|
||||||
|
|
||||||
initialState.eventSink(EmojiPickerEvent.ToggleSearchActive(true))
|
|
||||||
assertThat(awaitItem().isSearchActive).isTrue()
|
|
||||||
|
|
||||||
initialState.eventSink(EmojiPickerEvent.ToggleSearchActive(false))
|
|
||||||
assertThat(awaitItem().isSearchActive).isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `recent emojis are automatically added to the categories if present`() = runTest {
|
|
||||||
val providedCategories = persistentListOf(emojiCategory(EmojibaseCategory.Activity))
|
|
||||||
val presenter = createPresenter(
|
|
||||||
categories = providedCategories,
|
|
||||||
recentEmojis = persistentListOf("😊"),
|
|
||||||
)
|
|
||||||
testPresenter(presenter) {
|
|
||||||
skipItems(1)
|
|
||||||
|
|
||||||
val initialState = awaitItem()
|
|
||||||
assertThat(providedCategories.size).isNotEqualTo(initialState.categories.size)
|
|
||||||
assertThat(initialState.categories.size).isEqualTo(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun TestScope.createPresenter(
|
|
||||||
categories: ImmutableList<Pair<EmojibaseCategory, ImmutableList<Emoji>>> = persistentListOf(emojiCategory()),
|
|
||||||
recentEmojis: ImmutableList<String> = persistentListOf(),
|
|
||||||
) = EmojiPickerPresenter(
|
|
||||||
emojibaseStore = EmojibaseStore(categories.toMap().toImmutableMap()),
|
|
||||||
recentEmojis = recentEmojis,
|
|
||||||
coroutineDispatchers = testCoroutineDispatchers(),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun emojiCategory(
|
|
||||||
category: EmojibaseCategory = EmojibaseCategory.Activity,
|
|
||||||
emojis: ImmutableList<Emoji> = persistentListOf(
|
|
||||||
Emoji("1F3C3", "Smile", persistentListOf("smile"), persistentListOf("smile"), "😊", skins = null)
|
|
||||||
)
|
|
||||||
) = category to emojis
|
|
||||||
|
|
||||||
@OptIn(InternalComposeApi::class)
|
|
||||||
private suspend fun TestScope.testPresenter(
|
|
||||||
presenter: EmojiPickerPresenter = createPresenter(),
|
|
||||||
testBlock: suspend TurbineTestContext<EmojiPickerState>.() -> Unit,
|
|
||||||
) {
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
// These are needed to load the history icon in the presenter
|
|
||||||
currentComposer.startProviders(arrayOf(
|
|
||||||
LocalContext provides InstrumentationRegistry.getInstrumentation().context,
|
|
||||||
LocalConfiguration provides InstrumentationRegistry.getInstrumentation().context.resources.configuration,
|
|
||||||
))
|
|
||||||
val state = presenter.present()
|
|
||||||
currentComposer.endProviders()
|
|
||||||
state
|
|
||||||
}.test {
|
|
||||||
testBlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -81,6 +81,7 @@ dependencies {
|
|||||||
implementation(projects.features.deactivation.api)
|
implementation(projects.features.deactivation.api)
|
||||||
implementation(projects.features.home.api)
|
implementation(projects.features.home.api)
|
||||||
implementation(projects.features.invite.api)
|
implementation(projects.features.invite.api)
|
||||||
|
implementation(projects.libraries.emoji.api)
|
||||||
implementation(projects.services.analytics.api)
|
implementation(projects.services.analytics.api)
|
||||||
implementation(projects.services.analytics.compose)
|
implementation(projects.services.analytics.compose)
|
||||||
implementation(projects.services.appnavstate.api)
|
implementation(projects.services.appnavstate.api)
|
||||||
@@ -98,6 +99,7 @@ dependencies {
|
|||||||
|
|
||||||
testCommonDependencies(libs, true)
|
testCommonDependencies(libs, true)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
testImplementation(projects.libraries.emoji.test)
|
||||||
testImplementation(projects.libraries.featureflag.test)
|
testImplementation(projects.libraries.featureflag.test)
|
||||||
testImplementation(projects.libraries.mediapickers.test)
|
testImplementation(projects.libraries.mediapickers.test)
|
||||||
testImplementation(projects.libraries.mediaupload.test)
|
testImplementation(projects.libraries.mediaupload.test)
|
||||||
|
|||||||
+3
@@ -24,6 +24,7 @@ import io.element.android.features.logout.api.direct.DirectLogoutView
|
|||||||
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
|
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
|
||||||
import io.element.android.libraries.architecture.callback
|
import io.element.android.libraries.architecture.callback
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
@@ -33,6 +34,7 @@ class PreferencesRootNode(
|
|||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val presenter: PreferencesRootPresenter,
|
private val presenter: PreferencesRootPresenter,
|
||||||
private val directLogoutView: DirectLogoutView,
|
private val directLogoutView: DirectLogoutView,
|
||||||
|
private val emojiPickerRenderer: EmojiPickerRenderer,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun navigateToAddAccount()
|
fun navigateToAddAccount()
|
||||||
@@ -75,6 +77,7 @@ class PreferencesRootNode(
|
|||||||
val isDark = ElementTheme.isLightTheme.not()
|
val isDark = ElementTheme.isLightTheme.not()
|
||||||
PreferencesRootView(
|
PreferencesRootView(
|
||||||
state = state,
|
state = state,
|
||||||
|
emojiPickerRenderer = emojiPickerRenderer,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onBackClick = this::navigateUp,
|
onBackClick = this::navigateUp,
|
||||||
onAddAccountClick = callback::navigateToAddAccount,
|
onAddAccountClick = callback::navigateToAddAccount,
|
||||||
|
|||||||
+11
-1
@@ -41,6 +41,8 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
|||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
|
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
|
||||||
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer
|
||||||
|
import io.element.android.libraries.emoji.api.picker.NoOpEmojiPickerRenderer
|
||||||
import io.element.android.libraries.matrix.api.core.DeviceId
|
import io.element.android.libraries.matrix.api.core.DeviceId
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
|
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
|
||||||
@@ -49,6 +51,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||||||
@Composable
|
@Composable
|
||||||
fun PreferencesRootView(
|
fun PreferencesRootView(
|
||||||
state: PreferencesRootState,
|
state: PreferencesRootState,
|
||||||
|
emojiPickerRenderer: EmojiPickerRenderer,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
onAddAccountClick: () -> Unit,
|
onAddAccountClick: () -> Unit,
|
||||||
onSecureBackupClick: () -> Unit,
|
onSecureBackupClick: () -> Unit,
|
||||||
@@ -90,7 +93,11 @@ fun PreferencesRootView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (state.userStatusState != null) {
|
if (state.userStatusState != null) {
|
||||||
UserStatusSection(state.userStatusState, showTopDivider = !state.isMultiAccountEnabled)
|
UserStatusSection(
|
||||||
|
userStatusState = state.userStatusState,
|
||||||
|
emojiPickerRenderer = emojiPickerRenderer,
|
||||||
|
showTopDivider = !state.isMultiAccountEnabled,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// 'Account' section
|
// 'Account' section
|
||||||
ManageAccountSection(
|
ManageAccountSection(
|
||||||
@@ -135,6 +142,7 @@ fun PreferencesRootView(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ColumnScope.UserStatusSection(
|
private fun ColumnScope.UserStatusSection(
|
||||||
userStatusState: UserStatusState,
|
userStatusState: UserStatusState,
|
||||||
|
emojiPickerRenderer: EmojiPickerRenderer,
|
||||||
showTopDivider: Boolean,
|
showTopDivider: Boolean,
|
||||||
) {
|
) {
|
||||||
if (showTopDivider) {
|
if (showTopDivider) {
|
||||||
@@ -145,6 +153,7 @@ private fun ColumnScope.UserStatusSection(
|
|||||||
}
|
}
|
||||||
UserStatusView(
|
UserStatusView(
|
||||||
state = userStatusState,
|
state = userStatusState,
|
||||||
|
emojiPickerRenderer = emojiPickerRenderer,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
HorizontalDivider(
|
HorizontalDivider(
|
||||||
@@ -369,6 +378,7 @@ internal fun PreferencesRootViewDarkPreview(@PreviewParameter(PreferencesRootSta
|
|||||||
private fun ContentToPreview(state: PreferencesRootState) {
|
private fun ContentToPreview(state: PreferencesRootState) {
|
||||||
PreferencesRootView(
|
PreferencesRootView(
|
||||||
state = state,
|
state = state,
|
||||||
|
emojiPickerRenderer = NoOpEmojiPickerRenderer,
|
||||||
onBackClick = {},
|
onBackClick = {},
|
||||||
onAddAccountClick = {},
|
onAddAccountClick = {},
|
||||||
onOpenAnalytics = {},
|
onOpenAnalytics = {},
|
||||||
|
|||||||
+2
@@ -16,5 +16,7 @@ sealed interface UserStatusEvent {
|
|||||||
data object OpenCustomInput : UserStatusEvent
|
data object OpenCustomInput : UserStatusEvent
|
||||||
data object CancelCustomInput : UserStatusEvent
|
data object CancelCustomInput : UserStatusEvent
|
||||||
data class UpdateCustomEmoji(val emoji: String) : UserStatusEvent
|
data class UpdateCustomEmoji(val emoji: String) : UserStatusEvent
|
||||||
|
data object OpenEmojiPicker : UserStatusEvent
|
||||||
|
data object DismissEmojiPicker : UserStatusEvent
|
||||||
data object ClearStatus : UserStatusEvent
|
data object ClearStatus : UserStatusEvent
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-14
@@ -19,36 +19,32 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerPresenter
|
||||||
|
import io.element.android.libraries.emoji.api.recentemojis.EmptyGetRecentEmojis
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
class UserStatusPresenter(
|
class UserStatusPresenter(
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
|
emojiPickerPresenterFactory: EmojiPickerPresenter.Factory,
|
||||||
) : Presenter<UserStatusState> {
|
) : Presenter<UserStatusState> {
|
||||||
|
private val emojiPickerPresenter = emojiPickerPresenterFactory.create(EmptyGetRecentEmojis)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): UserStatusState {
|
override fun present(): UserStatusState {
|
||||||
val userProfile by matrixClient.userProfile.collectAsState()
|
val userProfile by matrixClient.userProfile.collectAsState()
|
||||||
var pickerState by remember { mutableStateOf<UserStatusPickerState>(UserStatusPickerState.Hidden) }
|
var pickerState by remember { mutableStateOf<UserStatusPickerState>(UserStatusPickerState.Hidden) }
|
||||||
|
var isEmojiPickerVisible by remember { mutableStateOf(false) }
|
||||||
val customTextFieldState = rememberTextFieldState()
|
val customTextFieldState = rememberTextFieldState()
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
fun handleEvent(event: UserStatusEvent) {
|
fun handleEvent(event: UserStatusEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
|
// Mode transitions
|
||||||
UserStatusEvent.OpenPicker -> pickerState = UserStatusPickerState.ShowingPicker
|
UserStatusEvent.OpenPicker -> pickerState = UserStatusPickerState.ShowingPicker
|
||||||
UserStatusEvent.DismissPicker -> pickerState = UserStatusPickerState.Hidden
|
UserStatusEvent.DismissPicker -> pickerState = UserStatusPickerState.Hidden
|
||||||
UserStatusEvent.OpenCustomInput -> {
|
UserStatusEvent.CancelCustomInput -> pickerState = UserStatusPickerState.Hidden
|
||||||
val raw = userProfile.rawStatus
|
|
||||||
if (raw != null) {
|
|
||||||
customTextFieldState.setTextAndPlaceCursorAtEnd(raw.text)
|
|
||||||
} else {
|
|
||||||
customTextFieldState.clearText()
|
|
||||||
}
|
|
||||||
pickerState = UserStatusPickerState.CustomInput(
|
|
||||||
emoji = raw?.emoji ?: "😀",
|
|
||||||
textFieldState = customTextFieldState,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is UserStatusEvent.SetStatus -> {
|
is UserStatusEvent.SetStatus -> {
|
||||||
pickerState = UserStatusPickerState.Hidden
|
pickerState = UserStatusPickerState.Hidden
|
||||||
coroutineScope.launch { matrixClient.setUserStatus(event.status) }
|
coroutineScope.launch { matrixClient.setUserStatus(event.status) }
|
||||||
@@ -57,19 +53,58 @@ class UserStatusPresenter(
|
|||||||
pickerState = UserStatusPickerState.Hidden
|
pickerState = UserStatusPickerState.Hidden
|
||||||
coroutineScope.launch { matrixClient.clearUserStatus() }
|
coroutineScope.launch { matrixClient.clearUserStatus() }
|
||||||
}
|
}
|
||||||
UserStatusEvent.CancelCustomInput -> pickerState = UserStatusPickerState.Hidden
|
UserStatusEvent.OpenCustomInput -> {
|
||||||
|
val raw = userProfile.rawStatus
|
||||||
|
if (raw != null) {
|
||||||
|
customTextFieldState.setTextAndPlaceCursorAtEnd(raw.text)
|
||||||
|
} else {
|
||||||
|
customTextFieldState.clearText()
|
||||||
|
}
|
||||||
|
isEmojiPickerVisible = false
|
||||||
|
pickerState = UserStatusPickerState.CustomInput(
|
||||||
|
emoji = raw?.emoji ?: "😀",
|
||||||
|
textFieldState = customTextFieldState,
|
||||||
|
emojiPickerSheetState = EmojiPickerSheetState.Hidden,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Custom-input mutations
|
||||||
is UserStatusEvent.UpdateCustomEmoji -> {
|
is UserStatusEvent.UpdateCustomEmoji -> {
|
||||||
|
isEmojiPickerVisible = false
|
||||||
val current = pickerState as? UserStatusPickerState.CustomInput ?: return
|
val current = pickerState as? UserStatusPickerState.CustomInput ?: return
|
||||||
pickerState = current.copy(emoji = event.emoji)
|
pickerState = current.copy(emoji = event.emoji)
|
||||||
}
|
}
|
||||||
|
// Emoji-picker visibility (only meaningful within CustomInput mode)
|
||||||
|
UserStatusEvent.OpenEmojiPicker -> isEmojiPickerVisible = true
|
||||||
|
UserStatusEvent.DismissEmojiPicker -> isEmojiPickerVisible = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In CustomInput mode, override the picker state's `emojiPickerSheetState` with the freshly composed emoji-picker
|
||||||
|
// sub-state so recompositions of the picker propagate up. Other modes pass through unchanged.
|
||||||
|
val effectivePickerState = when (val current = pickerState) {
|
||||||
|
is UserStatusPickerState.CustomInput -> {
|
||||||
|
val emojiPickerState = presentEmojiPickerSheetState(isEmojiPickerVisible)
|
||||||
|
current.copy(emojiPickerSheetState = emojiPickerState)
|
||||||
|
}
|
||||||
|
else -> current
|
||||||
|
}
|
||||||
|
|
||||||
return UserStatusState(
|
return UserStatusState(
|
||||||
displayedStatus = userProfile.displayedStatus,
|
displayedStatus = userProfile.displayedStatus,
|
||||||
rawStatus = userProfile.rawStatus,
|
rawStatus = userProfile.rawStatus,
|
||||||
pickerState = pickerState,
|
pickerState = effectivePickerState,
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun presentEmojiPickerSheetState(isVisible: Boolean): EmojiPickerSheetState {
|
||||||
|
if (!isVisible) return EmojiPickerSheetState.Hidden
|
||||||
|
val emojiPickerState = emojiPickerPresenter.present()
|
||||||
|
return if (emojiPickerState.isReady) {
|
||||||
|
EmojiPickerSheetState.Shown(emojiPickerState)
|
||||||
|
} else {
|
||||||
|
EmojiPickerSheetState.Loading
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
@@ -11,6 +11,7 @@ import androidx.annotation.StringRes
|
|||||||
import androidx.compose.foundation.text.input.TextFieldState
|
import androidx.compose.foundation.text.input.TextFieldState
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.features.preferences.impl.R
|
import io.element.android.features.preferences.impl.R
|
||||||
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerState
|
||||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import io.element.android.libraries.matrix.api.user.UserStatus
|
import io.element.android.libraries.matrix.api.user.UserStatus
|
||||||
|
|
||||||
@@ -28,9 +29,17 @@ sealed interface UserStatusPickerState {
|
|||||||
data class CustomInput(
|
data class CustomInput(
|
||||||
val emoji: String,
|
val emoji: String,
|
||||||
val textFieldState: TextFieldState,
|
val textFieldState: TextFieldState,
|
||||||
|
val emojiPickerSheetState: EmojiPickerSheetState,
|
||||||
) : UserStatusPickerState
|
) : UserStatusPickerState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
sealed interface EmojiPickerSheetState {
|
||||||
|
data object Hidden : EmojiPickerSheetState
|
||||||
|
data object Loading : EmojiPickerSheetState
|
||||||
|
data class Shown(val state: EmojiPickerState) : EmojiPickerSheetState
|
||||||
|
}
|
||||||
|
|
||||||
enum class PredefinedUserStatus(val emoji: String, @StringRes val labelRes: Int) {
|
enum class PredefinedUserStatus(val emoji: String, @StringRes val labelRes: Int) {
|
||||||
IN_A_MEETING("💬", R.string.common_user_status_in_a_meeting),
|
IN_A_MEETING("💬", R.string.common_user_status_in_a_meeting),
|
||||||
FOCUS_TIME("💡", R.string.common_user_status_focus_time),
|
FOCUS_TIME("💡", R.string.common_user_status_focus_time),
|
||||||
|
|||||||
+14
-2
@@ -23,8 +23,20 @@ internal class UserStatusStateProvider : PreviewParameterProvider<UserStatusStat
|
|||||||
rawStatus = UserStatus("🌴", "Away"),
|
rawStatus = UserStatus("🌴", "Away"),
|
||||||
pickerState = UserStatusPickerState.ShowingPicker,
|
pickerState = UserStatusPickerState.ShowingPicker,
|
||||||
),
|
),
|
||||||
aUserStatusState(pickerState = UserStatusPickerState.CustomInput(emoji = "😀", textFieldState = TextFieldState())),
|
aUserStatusState(
|
||||||
aUserStatusState(pickerState = UserStatusPickerState.CustomInput(emoji = "🚀", textFieldState = TextFieldState("Working on something"))),
|
pickerState = UserStatusPickerState.CustomInput(
|
||||||
|
emoji = "😀",
|
||||||
|
textFieldState = TextFieldState(),
|
||||||
|
emojiPickerSheetState = EmojiPickerSheetState.Hidden
|
||||||
|
)
|
||||||
|
),
|
||||||
|
aUserStatusState(
|
||||||
|
pickerState = UserStatusPickerState.CustomInput(
|
||||||
|
emoji = "🚀",
|
||||||
|
textFieldState = TextFieldState("Working on something"),
|
||||||
|
emojiPickerSheetState = EmojiPickerSheetState.Hidden
|
||||||
|
)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+49
-2
@@ -10,6 +10,7 @@ package io.element.android.features.preferences.impl.userstatus
|
|||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
@@ -58,13 +59,17 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
|||||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||||
import io.element.android.libraries.designsystem.theme.components.hide
|
import io.element.android.libraries.designsystem.theme.components.hide
|
||||||
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer
|
||||||
|
import io.element.android.libraries.emoji.api.picker.NoOpEmojiPickerRenderer
|
||||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import io.element.android.libraries.matrix.api.user.UserStatus
|
import io.element.android.libraries.matrix.api.user.UserStatus
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UserStatusView(
|
fun UserStatusView(
|
||||||
state: UserStatusState,
|
state: UserStatusState,
|
||||||
|
emojiPickerRenderer: EmojiPickerRenderer,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
when (val pickerState = state.pickerState) {
|
when (val pickerState = state.pickerState) {
|
||||||
@@ -103,8 +108,18 @@ fun UserStatusView(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
onCancel = { state.eventSink(UserStatusEvent.CancelCustomInput) },
|
onCancel = { state.eventSink(UserStatusEvent.CancelCustomInput) },
|
||||||
|
onEmojiClick = { state.eventSink(UserStatusEvent.OpenEmojiPicker) },
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
|
when (val sheet = pickerState.emojiPickerSheetState) {
|
||||||
|
EmojiPickerSheetState.Hidden, EmojiPickerSheetState.Loading -> Unit
|
||||||
|
is EmojiPickerSheetState.Shown -> EmojiPickerBottomSheet(
|
||||||
|
pickerState = sheet.state,
|
||||||
|
emojiPickerRenderer = emojiPickerRenderer,
|
||||||
|
onSelectEmoji = { emoji -> state.eventSink(UserStatusEvent.UpdateCustomEmoji(emoji.unicode)) },
|
||||||
|
onDismiss = { state.eventSink(UserStatusEvent.DismissEmojiPicker) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,6 +246,7 @@ private fun CustomStatusInputRow(
|
|||||||
rawStatus: UserStatus?,
|
rawStatus: UserStatus?,
|
||||||
onConfirm: () -> Unit,
|
onConfirm: () -> Unit,
|
||||||
onCancel: () -> Unit,
|
onCancel: () -> Unit,
|
||||||
|
onEmojiClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val hasText by remember { derivedStateOf { textFieldState.text.isNotEmpty() } }
|
val hasText by remember { derivedStateOf { textFieldState.text.isNotEmpty() } }
|
||||||
@@ -297,7 +313,7 @@ private fun CustomStatusInputRow(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(50.dp)
|
.size(50.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.clickable { },
|
.clickable(onClick = onEmojiClick),
|
||||||
) {
|
) {
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
EmojiText(emoji)
|
EmojiText(emoji)
|
||||||
@@ -308,8 +324,39 @@ private fun CustomStatusInputRow(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun EmojiPickerBottomSheet(
|
||||||
|
pickerState: io.element.android.libraries.emoji.api.picker.EmojiPickerState,
|
||||||
|
emojiPickerRenderer: EmojiPickerRenderer,
|
||||||
|
onSelectEmoji: (io.element.android.emojibasebindings.Emoji) -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
|
val sheetState = rememberBottomSheetState(initialValue = SheetValue.Hidden)
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
sheetState = sheetState,
|
||||||
|
scrollable = false,
|
||||||
|
) {
|
||||||
|
emojiPickerRenderer.Render(
|
||||||
|
state = pickerState,
|
||||||
|
onSelectEmoji = { emoji ->
|
||||||
|
sheetState.hide(coroutineScope) {
|
||||||
|
onSelectEmoji(emoji)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedEmojis = persistentSetOf(),
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun UserStatusViewPreview(@PreviewParameter(UserStatusStateProvider::class) state: UserStatusState) = ElementPreview {
|
internal fun UserStatusViewPreview(@PreviewParameter(UserStatusStateProvider::class) state: UserStatusState) = ElementPreview {
|
||||||
UserStatusView(state = state)
|
UserStatusView(
|
||||||
|
state = state,
|
||||||
|
emojiPickerRenderer = NoOpEmojiPickerRenderer,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -19,6 +19,7 @@ import androidx.compose.ui.test.performClick
|
|||||||
import androidx.compose.ui.test.performScrollTo
|
import androidx.compose.ui.test.performScrollTo
|
||||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import io.element.android.features.preferences.impl.R
|
import io.element.android.features.preferences.impl.R
|
||||||
|
import io.element.android.libraries.emoji.api.picker.NoOpEmojiPickerRenderer
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||||
@@ -466,6 +467,7 @@ private fun AndroidComposeUiTest<ComponentActivity>.setView(
|
|||||||
setContent {
|
setContent {
|
||||||
PreferencesRootView(
|
PreferencesRootView(
|
||||||
state = state,
|
state = state,
|
||||||
|
emojiPickerRenderer = NoOpEmojiPickerRenderer,
|
||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
onAddAccountClick = onAddAccountClick,
|
onAddAccountClick = onAddAccountClick,
|
||||||
onSecureBackupClick = onSecureBackupClick,
|
onSecureBackupClick = onSecureBackupClick,
|
||||||
|
|||||||
+5
-1
@@ -8,6 +8,7 @@
|
|||||||
package io.element.android.features.preferences.impl.userstatus
|
package io.element.android.features.preferences.impl.userstatus
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.emoji.test.fakeEmojiPickerPresenterFactory
|
||||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import io.element.android.libraries.matrix.api.user.UserStatus
|
import io.element.android.libraries.matrix.api.user.UserStatus
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
@@ -17,7 +18,10 @@ import org.junit.Test
|
|||||||
|
|
||||||
class UserStatusPresenterTest {
|
class UserStatusPresenterTest {
|
||||||
private fun createPresenter(client: FakeMatrixClient = FakeMatrixClient()) =
|
private fun createPresenter(client: FakeMatrixClient = FakeMatrixClient()) =
|
||||||
UserStatusPresenter(matrixClient = client)
|
UserStatusPresenter(
|
||||||
|
matrixClient = client,
|
||||||
|
emojiPickerPresenterFactory = fakeEmojiPickerPresenterFactory(),
|
||||||
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `initial state - no status set`() = runTest {
|
fun `initial state - no status set`() = runTest {
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-library")
|
id("io.element.android-compose-library")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.libraries.recentemojis.api"
|
namespace = "io.element.android.libraries.emoji.api"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -19,5 +18,5 @@ dependencies {
|
|||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
|
||||||
implementation(libs.kotlinx.collections.immutable)
|
implementation(libs.kotlinx.collections.immutable)
|
||||||
implementation(libs.matrix.emojibase.bindings)
|
api(libs.matrix.emojibase.bindings)
|
||||||
}
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.emoji.api.picker
|
||||||
|
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.emoji.api.recentemojis.GetRecentEmojis
|
||||||
|
|
||||||
|
fun interface EmojiPickerPresenter : Presenter<EmojiPickerState> {
|
||||||
|
fun interface Factory {
|
||||||
|
fun create(getRecentEmojis: GetRecentEmojis): EmojiPickerPresenter
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.emoji.api.picker
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import io.element.android.emojibasebindings.Emoji
|
||||||
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
interface EmojiPickerRenderer {
|
||||||
|
/**
|
||||||
|
* @param state opaque state produced by [EmojiPickerPresenter.present].
|
||||||
|
* @param onSelectEmoji invoked when the user taps an emoji (including a skin-tone variant).
|
||||||
|
* @param selectedEmojis emojis to visually mark as already selected.
|
||||||
|
* @param modifier layout modifier for the picker container.
|
||||||
|
* @param contentDescription accessibility label for each emoji cell.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun Render(
|
||||||
|
state: EmojiPickerState,
|
||||||
|
onSelectEmoji: (Emoji) -> Unit,
|
||||||
|
selectedEmojis: ImmutableSet<String>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
contentDescription: @Composable (Emoji, Boolean) -> String = { emoji, _ -> emoji.unicode },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object NoOpEmojiPickerRenderer : EmojiPickerRenderer {
|
||||||
|
@Composable
|
||||||
|
override fun Render(
|
||||||
|
state: EmojiPickerState,
|
||||||
|
onSelectEmoji: (Emoji) -> Unit,
|
||||||
|
selectedEmojis: ImmutableSet<String>,
|
||||||
|
modifier: Modifier,
|
||||||
|
contentDescription: @Composable (Emoji, Boolean) -> String,
|
||||||
|
) = Unit
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.emoji.api.picker
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opaque state produced by an [EmojiPickerPresenter] and consumed by an [EmojiPickerRenderer].
|
||||||
|
*/
|
||||||
|
interface EmojiPickerState {
|
||||||
|
/**
|
||||||
|
* `true` once the picker has finished loading its data and is safe to display.
|
||||||
|
* Callers should typically defer showing the picker (or its container, e.g. a bottom sheet)
|
||||||
|
* until this flips to `true` to avoid flashing an empty picker.
|
||||||
|
*/
|
||||||
|
val isReady: Boolean
|
||||||
|
}
|
||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.recentemojis.api
|
package io.element.android.libraries.emoji.api.recentemojis
|
||||||
|
|
||||||
fun interface AddRecentEmoji {
|
fun interface AddRecentEmoji {
|
||||||
suspend operator fun invoke(emoji: String): Result<Unit>
|
suspend operator fun invoke(emoji: String): Result<Unit>
|
||||||
+10
-1
@@ -6,9 +6,10 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.recentemojis.api
|
package io.element.android.libraries.emoji.api.recentemojis
|
||||||
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of recently used emojis for reactions.
|
* Returns the list of recently used emojis for reactions.
|
||||||
@@ -16,3 +17,11 @@ import kotlinx.collections.immutable.ImmutableList
|
|||||||
fun interface GetRecentEmojis {
|
fun interface GetRecentEmojis {
|
||||||
suspend operator fun invoke(): Result<ImmutableList<String>>
|
suspend operator fun invoke(): Result<ImmutableList<String>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [GetRecentEmojis] that never returns any recent emojis. Handy for pickers that don't want
|
||||||
|
* a "Recent" tab (e.g. the user-status picker).
|
||||||
|
*/
|
||||||
|
object EmptyGetRecentEmojis : GetRecentEmojis {
|
||||||
|
override suspend fun invoke(): Result<ImmutableList<String>> = Result.success(persistentListOf())
|
||||||
|
}
|
||||||
+15
-12
@@ -1,37 +1,40 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import extension.setupDependencyInjection
|
import extension.setupDependencyInjection
|
||||||
|
import extension.testCommonDependencies
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
id("kotlin-parcelize")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.libraries.recentemojis.impl"
|
namespace = "io.element.android.libraries.emoji.impl"
|
||||||
|
testOptions {
|
||||||
|
unitTests {
|
||||||
|
isIncludeAndroidResources = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupDependencyInjection()
|
setupDependencyInjection()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.libraries.recentemojis.api)
|
api(projects.libraries.emoji.api)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
|
implementation(projects.libraries.core)
|
||||||
|
implementation(projects.libraries.designsystem)
|
||||||
|
implementation(projects.libraries.di)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
implementation(projects.libraries.uiStrings)
|
||||||
|
|
||||||
implementation(libs.kotlinx.collections.immutable)
|
implementation(libs.kotlinx.collections.immutable)
|
||||||
implementation(libs.matrix.emojibase.bindings)
|
implementation(libs.matrix.emojibase.bindings)
|
||||||
|
|
||||||
testImplementation(projects.libraries.recentemojis.test)
|
|
||||||
testImplementation(libs.test.junit)
|
|
||||||
testImplementation(libs.coroutines.test)
|
|
||||||
testImplementation(libs.molecule.runtime)
|
|
||||||
testImplementation(libs.test.truth)
|
|
||||||
testImplementation(libs.test.turbine)
|
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.tests.testutils)
|
testCommonDependencies(libs)
|
||||||
}
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Element Creations Ltd.
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.emoji.impl
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import dev.zacsweers.metro.AppScope
|
||||||
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
|
import dev.zacsweers.metro.SingleIn
|
||||||
|
import io.element.android.emojibasebindings.EmojibaseDatasource
|
||||||
|
import io.element.android.emojibasebindings.EmojibaseStore
|
||||||
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
|
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||||
|
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.CoroutineStart
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
|
||||||
|
@SingleIn(AppScope::class)
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultEmojibaseProvider(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
dispatchers: CoroutineDispatchers,
|
||||||
|
@AppCoroutineScope appScope: CoroutineScope,
|
||||||
|
) : EmojibaseProvider {
|
||||||
|
private val deferred = appScope.async(dispatchers.io, start = CoroutineStart.LAZY) {
|
||||||
|
EmojibaseDatasource().load(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getStore(): EmojibaseStore = deferred.await()
|
||||||
|
}
|
||||||
+3
-4
@@ -1,15 +1,14 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.recentemojis.api
|
package io.element.android.libraries.emoji.impl
|
||||||
|
|
||||||
import io.element.android.emojibasebindings.EmojibaseStore
|
import io.element.android.emojibasebindings.EmojibaseStore
|
||||||
|
|
||||||
interface EmojibaseProvider {
|
interface EmojibaseProvider {
|
||||||
val emojibaseStore: EmojibaseStore
|
suspend fun getStore(): EmojibaseStore
|
||||||
}
|
}
|
||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Element Creations Ltd.
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.emoji.impl.picker
|
||||||
|
|
||||||
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
|
import dev.zacsweers.metro.Assisted
|
||||||
|
import dev.zacsweers.metro.AssistedFactory
|
||||||
|
import dev.zacsweers.metro.AssistedInject
|
||||||
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
|
import io.element.android.emojibasebindings.Emoji
|
||||||
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerPresenter
|
||||||
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerState
|
||||||
|
import io.element.android.libraries.emoji.api.recentemojis.GetRecentEmojis
|
||||||
|
import io.element.android.libraries.emoji.impl.EmojibaseProvider
|
||||||
|
import io.element.android.libraries.emoji.impl.R
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
|
@AssistedInject
|
||||||
|
class DefaultEmojiPickerPresenter(
|
||||||
|
private val emojibaseProvider: EmojibaseProvider,
|
||||||
|
@Assisted private val getRecentEmojis: GetRecentEmojis,
|
||||||
|
private val coroutineDispatchers: CoroutineDispatchers,
|
||||||
|
) : EmojiPickerPresenter {
|
||||||
|
@AssistedFactory
|
||||||
|
@ContributesBinding(SessionScope::class)
|
||||||
|
interface Factory : EmojiPickerPresenter.Factory {
|
||||||
|
override fun create(getRecentEmojis: GetRecentEmojis): DefaultEmojiPickerPresenter
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun present(): EmojiPickerState {
|
||||||
|
val queryState = rememberTextFieldState()
|
||||||
|
var isSearchActive by remember { mutableStateOf(false) }
|
||||||
|
var emojiResults by remember { mutableStateOf<SearchBarResultState<ImmutableList<Emoji>>>(SearchBarResultState.Initial()) }
|
||||||
|
|
||||||
|
val data by produceState(EmojiPickerData.Empty) {
|
||||||
|
val storeDeferred = async { emojibaseProvider.getStore() }
|
||||||
|
val recentsDeferred = async { getRecentEmojis().getOrNull() ?: persistentListOf() }
|
||||||
|
val store = storeDeferred.await()
|
||||||
|
val recentEmojiUnicodes = recentsDeferred.await()
|
||||||
|
value = withContext(coroutineDispatchers.computation) {
|
||||||
|
val baseCategories = store.categories.map { (category, emojis) ->
|
||||||
|
EmojiCategory(
|
||||||
|
titleId = category.title,
|
||||||
|
icon = IconSource.Vector(category.icon),
|
||||||
|
emojis = emojis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val recentEmojis = recentEmojiUnicodes
|
||||||
|
.mapNotNull { unicode -> store.allEmojis.find { it.unicode == unicode } }
|
||||||
|
.toImmutableList()
|
||||||
|
val categories = if (recentEmojis.isEmpty()) {
|
||||||
|
baseCategories.toImmutableList()
|
||||||
|
} else {
|
||||||
|
val recentCategory = EmojiCategory(
|
||||||
|
titleId = R.string.emoji_picker_category_recent,
|
||||||
|
icon = IconSource.Resource(io.element.android.compound.R.drawable.ic_compound_history),
|
||||||
|
emojis = recentEmojis,
|
||||||
|
)
|
||||||
|
(listOf(recentCategory) + baseCategories).toImmutableList()
|
||||||
|
}
|
||||||
|
EmojiPickerData(
|
||||||
|
categories = categories,
|
||||||
|
allEmojis = store.allEmojis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val searchQuery = queryState.text.toString()
|
||||||
|
LaunchedEffect(searchQuery, data) {
|
||||||
|
if (searchQuery.isEmpty() || data.allEmojis.isEmpty()) {
|
||||||
|
// `SearchBarResultState.Initial` has no `equals` override, so reassigning a new instance
|
||||||
|
// would trigger a redundant emission each time this effect re-runs while the query is empty.
|
||||||
|
if (emojiResults !is SearchBarResultState.Initial) {
|
||||||
|
emojiResults = SearchBarResultState.Initial()
|
||||||
|
}
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
|
delay(100.milliseconds)
|
||||||
|
val lowercaseQuery = searchQuery.lowercase()
|
||||||
|
val results = withContext(coroutineDispatchers.computation) {
|
||||||
|
data.allEmojis
|
||||||
|
.asSequence()
|
||||||
|
.filter { emoji ->
|
||||||
|
emoji.tags.orEmpty().any { it.contains(lowercaseQuery) } ||
|
||||||
|
emoji.shortcodes.any { it.contains(lowercaseQuery) }
|
||||||
|
}
|
||||||
|
.take(60)
|
||||||
|
.toImmutableList()
|
||||||
|
}
|
||||||
|
emojiResults = SearchBarResultState.Results(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
val isInPreview = LocalInspectionMode.current
|
||||||
|
fun handleEvent(event: EmojiPickerEvent) {
|
||||||
|
when (event) {
|
||||||
|
is EmojiPickerEvent.ToggleSearchActive -> if (!isInPreview) {
|
||||||
|
isSearchActive = event.isActive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultEmojiPickerState(
|
||||||
|
categories = data.categories,
|
||||||
|
searchQuery = queryState,
|
||||||
|
isSearchActive = isSearchActive,
|
||||||
|
searchResults = emojiResults,
|
||||||
|
eventSink = ::handleEvent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class EmojiPickerData(
|
||||||
|
val categories: ImmutableList<EmojiCategory>,
|
||||||
|
val allEmojis: ImmutableList<Emoji>,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val Empty = EmojiPickerData(persistentListOf(), persistentListOf())
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.emoji.impl.picker
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
|
import io.element.android.emojibasebindings.Emoji
|
||||||
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer
|
||||||
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerState
|
||||||
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
|
|
||||||
|
@ContributesBinding(SessionScope::class)
|
||||||
|
class DefaultEmojiPickerRenderer : EmojiPickerRenderer {
|
||||||
|
@Composable
|
||||||
|
override fun Render(
|
||||||
|
state: EmojiPickerState,
|
||||||
|
onSelectEmoji: (Emoji) -> Unit,
|
||||||
|
selectedEmojis: ImmutableSet<String>,
|
||||||
|
modifier: Modifier,
|
||||||
|
contentDescription: @Composable (Emoji, Boolean) -> String,
|
||||||
|
) {
|
||||||
|
if (state is DefaultEmojiPickerState) {
|
||||||
|
EmojiPickerView(
|
||||||
|
state = state,
|
||||||
|
onSelectEmoji = onSelectEmoji,
|
||||||
|
selectedEmojis = selectedEmojis,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
error("Unsupported state type: ${state::class.java}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-9
@@ -6,7 +6,7 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.components.customreaction.picker
|
package io.element.android.libraries.emoji.impl.picker
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.text.input.TextFieldState
|
import androidx.compose.foundation.text.input.TextFieldState
|
||||||
@@ -14,23 +14,21 @@ import androidx.compose.runtime.Immutable
|
|||||||
import io.element.android.emojibasebindings.Emoji
|
import io.element.android.emojibasebindings.Emoji
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerState
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
// Emoji is unstable (because from an external library?), so we annotate with @Immutable
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class EmojiPickerState(
|
internal data class DefaultEmojiPickerState(
|
||||||
val categories: ImmutableList<EmojiCategory>,
|
val categories: ImmutableList<EmojiCategory>,
|
||||||
val allEmojis: ImmutableList<Emoji>,
|
|
||||||
val searchQuery: TextFieldState,
|
val searchQuery: TextFieldState,
|
||||||
val isSearchActive: Boolean,
|
val isSearchActive: Boolean,
|
||||||
val searchResults: SearchBarResultState<ImmutableList<Emoji>>,
|
val searchResults: SearchBarResultState<ImmutableList<Emoji>>,
|
||||||
val eventSink: (EmojiPickerEvent) -> Unit,
|
val eventSink: (EmojiPickerEvent) -> Unit,
|
||||||
)
|
) : EmojiPickerState {
|
||||||
|
override val isReady = categories.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
internal data class EmojiCategory(
|
||||||
* Represents a category of emojis with a title id, icon, and the list of associated emojis.
|
|
||||||
*/
|
|
||||||
data class EmojiCategory(
|
|
||||||
@StringRes val titleId: Int,
|
@StringRes val titleId: Int,
|
||||||
val icon: IconSource,
|
val icon: IconSource,
|
||||||
val emojis: ImmutableList<Emoji>,
|
val emojis: ImmutableList<Emoji>,
|
||||||
+32
-24
@@ -6,29 +6,26 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.components.customreaction.picker
|
package io.element.android.libraries.emoji.impl.picker
|
||||||
|
|
||||||
import androidx.compose.foundation.text.input.TextFieldState
|
import androidx.compose.foundation.text.input.TextFieldState
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.emojibasebindings.Emoji
|
import io.element.android.emojibasebindings.Emoji
|
||||||
import io.element.android.emojibasebindings.EmojibaseCategory
|
import io.element.android.emojibasebindings.EmojibaseCategory
|
||||||
import io.element.android.features.messages.impl.R
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.icon
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.title
|
|
||||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerState
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
class EmojiPickerStateProvider : PreviewParameterProvider<EmojiPickerState> {
|
internal class DefaultEmojiPickerStateProvider : PreviewParameterProvider<DefaultEmojiPickerState> {
|
||||||
override val values: Sequence<EmojiPickerState>
|
override val values: Sequence<DefaultEmojiPickerState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
anEmojiPickerState(),
|
aDefaultEmojiPickerState(),
|
||||||
anEmojiPickerState(isSearchActive = true),
|
aDefaultEmojiPickerState(isSearchActive = true),
|
||||||
anEmojiPickerState(isSearchActive = true, searchQuery = "smile"),
|
aDefaultEmojiPickerState(isSearchActive = true, searchQuery = "smile"),
|
||||||
anEmojiPickerState(
|
aDefaultEmojiPickerState(
|
||||||
isSearchActive = true,
|
isSearchActive = true,
|
||||||
searchQuery = "smile",
|
searchQuery = "smile",
|
||||||
searchResults = SearchBarResultState.Results(emojiList())
|
searchResults = SearchBarResultState.Results(emojiList())
|
||||||
@@ -36,12 +33,6 @@ class EmojiPickerStateProvider : PreviewParameterProvider<EmojiPickerState> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun recentEmojisCategory() = EmojiCategory(
|
|
||||||
titleId = R.string.emoji_picker_category_recent,
|
|
||||||
icon = IconSource.Resource(CompoundDrawables.ic_compound_history),
|
|
||||||
emojis = emojiList(),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun emojiList(): ImmutableList<Emoji> = persistentListOf(
|
private fun emojiList(): ImmutableList<Emoji> = persistentListOf(
|
||||||
Emoji(
|
Emoji(
|
||||||
"0x00",
|
"0x00",
|
||||||
@@ -56,29 +47,46 @@ private fun emojiList(): ImmutableList<Emoji> = persistentListOf(
|
|||||||
"crying face",
|
"crying face",
|
||||||
persistentListOf("crying"),
|
persistentListOf("crying"),
|
||||||
persistentListOf("smile, crying"),
|
persistentListOf("smile, crying"),
|
||||||
"\uD83E\uDD72",
|
"🥲",
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun anEmojiPickerState(
|
internal fun aDefaultEmojiPickerState(
|
||||||
categories: ImmutableList<EmojiCategory> = (listOf(recentEmojisCategory()) + EmojibaseCategory.entries.map {
|
categories: ImmutableList<EmojiCategory> = EmojibaseCategory.entries.map {
|
||||||
EmojiCategory(
|
EmojiCategory(
|
||||||
titleId = it.title,
|
titleId = it.title,
|
||||||
icon = IconSource.Vector(it.icon),
|
icon = IconSource.Vector(it.icon),
|
||||||
emojis = emojiList(),
|
emojis = emojiList(),
|
||||||
)
|
)
|
||||||
}).toImmutableList(),
|
}.toImmutableList(),
|
||||||
allEmojis: ImmutableList<Emoji> = categories.flatMap { it.emojis }.toImmutableList(),
|
|
||||||
searchQuery: String = "",
|
searchQuery: String = "",
|
||||||
isSearchActive: Boolean = false,
|
isSearchActive: Boolean = false,
|
||||||
searchResults: SearchBarResultState<ImmutableList<Emoji>> = SearchBarResultState.Initial(),
|
searchResults: SearchBarResultState<ImmutableList<Emoji>> = SearchBarResultState.Initial(),
|
||||||
eventSink: (EmojiPickerEvent) -> Unit = {},
|
eventSink: (EmojiPickerEvent) -> Unit = {},
|
||||||
) = EmojiPickerState(
|
) = DefaultEmojiPickerState(
|
||||||
categories = categories,
|
categories = categories,
|
||||||
allEmojis = allEmojis,
|
|
||||||
searchQuery = TextFieldState(initialText = searchQuery),
|
searchQuery = TextFieldState(initialText = searchQuery),
|
||||||
isSearchActive = isSearchActive,
|
isSearchActive = isSearchActive,
|
||||||
searchResults = searchResults,
|
searchResults = searchResults,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public helper for external tests that need an [EmojiPickerState] instance the impl's
|
||||||
|
* [DefaultEmojiPickerRenderer] will accept. The no-arg overload returns a ready state
|
||||||
|
* (see [EmojiPickerState.isReady]); use the [emojis] overload to inject specific categories.
|
||||||
|
*/
|
||||||
|
fun anEmojiPickerState(): EmojiPickerState = aDefaultEmojiPickerState()
|
||||||
|
|
||||||
|
fun anEmojiPickerState(
|
||||||
|
emojis: Map<EmojibaseCategory, ImmutableList<Emoji>>,
|
||||||
|
): EmojiPickerState = aDefaultEmojiPickerState(
|
||||||
|
categories = emojis.map { (category, list) ->
|
||||||
|
EmojiCategory(
|
||||||
|
titleId = category.title,
|
||||||
|
icon = IconSource.Vector(category.icon),
|
||||||
|
emojis = list,
|
||||||
|
)
|
||||||
|
}.toImmutableList(),
|
||||||
|
)
|
||||||
+5
-13
@@ -6,7 +6,7 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.components.customreaction
|
package io.element.android.libraries.emoji.impl.picker
|
||||||
|
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -52,12 +52,6 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.compose.ui.window.Popup
|
import androidx.compose.ui.window.Popup
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.emojibasebindings.Emoji
|
import io.element.android.emojibasebindings.Emoji
|
||||||
import io.element.android.features.messages.impl.timeline.a11y.a11yReactionAction
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.SkinTonePadding
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.SkinTonePicker
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.SkinToneSlotSize
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.SkinToneSlotSpacing
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.aSkinList
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
@@ -66,7 +60,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||||||
import kotlinx.collections.immutable.persistentSetOf
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EmojiItem(
|
internal fun EmojiItem(
|
||||||
item: Emoji,
|
item: Emoji,
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
onSelectEmoji: (Emoji) -> Unit,
|
onSelectEmoji: (Emoji) -> Unit,
|
||||||
@@ -77,6 +71,7 @@ fun EmojiItem(
|
|||||||
hasSelectedSkin: Boolean,
|
hasSelectedSkin: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
emojiSize: TextUnit = 20.sp,
|
emojiSize: TextUnit = 20.sp,
|
||||||
|
contentDescription: @Composable (emoji: Emoji, isSelected: Boolean) -> String = { emoji, _ -> emoji.unicode },
|
||||||
) {
|
) {
|
||||||
val backgroundColor = when {
|
val backgroundColor = when {
|
||||||
isSelected -> ElementTheme.colors.bgActionPrimaryRest
|
isSelected -> ElementTheme.colors.bgActionPrimaryRest
|
||||||
@@ -89,10 +84,7 @@ fun EmojiItem(
|
|||||||
var hoveredIndex by remember { mutableIntStateOf(-1) }
|
var hoveredIndex by remember { mutableIntStateOf(-1) }
|
||||||
var dismissed by remember { mutableStateOf(false) }
|
var dismissed by remember { mutableStateOf(false) }
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val description = a11yReactionAction(
|
val description = contentDescription(item, isSelected)
|
||||||
emoji = item.unicode,
|
|
||||||
userAlreadyReacted = isSelected,
|
|
||||||
)
|
|
||||||
val hasSkinTones = !item.skins.isNullOrEmpty()
|
val hasSkinTones = !item.skins.isNullOrEmpty()
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@@ -210,7 +202,7 @@ fun EmojiItem(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.clearAndSetSemantics {
|
.clearAndSetSemantics {
|
||||||
contentDescription = description
|
this.contentDescription = description
|
||||||
},
|
},
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
+2
-2
@@ -6,8 +6,8 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.components.customreaction.picker
|
package io.element.android.libraries.emoji.impl.picker
|
||||||
|
|
||||||
sealed interface EmojiPickerEvent {
|
internal sealed interface EmojiPickerEvent {
|
||||||
data class ToggleSearchActive(val isActive: Boolean) : EmojiPickerEvent
|
data class ToggleSearchActive(val isActive: Boolean) : EmojiPickerEvent
|
||||||
}
|
}
|
||||||
+13
-9
@@ -6,7 +6,7 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.components.customreaction.picker
|
package io.element.android.libraries.emoji.impl.picker
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -35,14 +35,13 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.emojibasebindings.Emoji
|
import io.element.android.emojibasebindings.Emoji
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.EmojiItem
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.text.toSp
|
import io.element.android.libraries.designsystem.text.toSp
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBar
|
import io.element.android.libraries.designsystem.theme.components.SearchBar
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.emoji.impl.R
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.ImmutableSet
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
import kotlinx.collections.immutable.persistentSetOf
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
@@ -50,11 +49,12 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun EmojiPicker(
|
internal fun EmojiPickerView(
|
||||||
|
state: DefaultEmojiPickerState,
|
||||||
onSelectEmoji: (Emoji) -> Unit,
|
onSelectEmoji: (Emoji) -> Unit,
|
||||||
state: EmojiPickerState,
|
|
||||||
selectedEmojis: ImmutableSet<String>,
|
selectedEmojis: ImmutableSet<String>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
contentDescription: @Composable (emoji: Emoji, isSelected: Boolean) -> String = { emoji, _ -> emoji.unicode },
|
||||||
) {
|
) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val pagerState = rememberPagerState(pageCount = { state.categories.size })
|
val pagerState = rememberPagerState(pageCount = { state.categories.size })
|
||||||
@@ -68,7 +68,7 @@ fun EmojiPicker(
|
|||||||
active = state.isSearchActive,
|
active = state.isSearchActive,
|
||||||
onActiveChange = { state.eventSink(EmojiPickerEvent.ToggleSearchActive(it)) },
|
onActiveChange = { state.eventSink(EmojiPickerEvent.ToggleSearchActive(it)) },
|
||||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
placeHolderTitle = stringResource(CommonStrings.emoji_picker_search_placeholder),
|
placeHolderTitle = stringResource(R.string.emoji_picker_search_placeholder),
|
||||||
) { emojis ->
|
) { emojis ->
|
||||||
EmojiResults(
|
EmojiResults(
|
||||||
emojis = emojis,
|
emojis = emojis,
|
||||||
@@ -78,6 +78,7 @@ fun EmojiPicker(
|
|||||||
skinPickerEmoji = skinPickerEmoji,
|
skinPickerEmoji = skinPickerEmoji,
|
||||||
onDismissSkinPicker = { skinPickerEmoji = null },
|
onDismissSkinPicker = { skinPickerEmoji = null },
|
||||||
selectedEmojis = selectedEmojis,
|
selectedEmojis = selectedEmojis,
|
||||||
|
contentDescription = contentDescription,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +121,7 @@ fun EmojiPicker(
|
|||||||
skinPickerEmoji = skinPickerEmoji,
|
skinPickerEmoji = skinPickerEmoji,
|
||||||
onDismissSkinPicker = { skinPickerEmoji = null },
|
onDismissSkinPicker = { skinPickerEmoji = null },
|
||||||
selectedEmojis = selectedEmojis,
|
selectedEmojis = selectedEmojis,
|
||||||
|
contentDescription = contentDescription,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,6 +137,7 @@ private fun EmojiResults(
|
|||||||
skinPickerEmoji: Emoji?,
|
skinPickerEmoji: Emoji?,
|
||||||
onDismissSkinPicker: () -> Unit,
|
onDismissSkinPicker: () -> Unit,
|
||||||
selectedEmojis: ImmutableSet<String>,
|
selectedEmojis: ImmutableSet<String>,
|
||||||
|
contentDescription: @Composable (emoji: Emoji, isSelected: Boolean) -> String,
|
||||||
) {
|
) {
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
@@ -155,6 +158,7 @@ private fun EmojiResults(
|
|||||||
emojiSize = 32.dp.toSp(),
|
emojiSize = 32.dp.toSp(),
|
||||||
selectedSkinUnicodes = selectedEmojis,
|
selectedSkinUnicodes = selectedEmojis,
|
||||||
hasSelectedSkin = item.skins?.any { skin -> skin.unicode in selectedEmojis } == true,
|
hasSelectedSkin = item.skins?.any { skin -> skin.unicode in selectedEmojis } == true,
|
||||||
|
contentDescription = contentDescription,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,10 +166,10 @@ private fun EmojiResults(
|
|||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun EmojiPickerPreview(@PreviewParameter(EmojiPickerStateProvider::class) state: EmojiPickerState) = ElementPreview {
|
internal fun EmojiPickerViewPreview(@PreviewParameter(DefaultEmojiPickerStateProvider::class) state: DefaultEmojiPickerState) = ElementPreview {
|
||||||
EmojiPicker(
|
EmojiPickerView(
|
||||||
onSelectEmoji = {},
|
|
||||||
state = state,
|
state = state,
|
||||||
|
onSelectEmoji = {},
|
||||||
selectedEmojis = persistentSetOf("😀", "😄", "😃"),
|
selectedEmojis = persistentSetOf("😀", "😄", "😃"),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
+4
-4
@@ -6,7 +6,7 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.components.customreaction
|
package io.element.android.libraries.emoji.impl.picker
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -20,10 +20,10 @@ import androidx.compose.material.icons.outlined.EmojiSymbols
|
|||||||
import androidx.compose.material.icons.outlined.EmojiTransportation
|
import androidx.compose.material.icons.outlined.EmojiTransportation
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import io.element.android.emojibasebindings.EmojibaseCategory
|
import io.element.android.emojibasebindings.EmojibaseCategory
|
||||||
import io.element.android.features.messages.impl.R
|
import io.element.android.libraries.emoji.impl.R
|
||||||
|
|
||||||
@get:StringRes
|
@get:StringRes
|
||||||
val EmojibaseCategory.title: Int
|
internal val EmojibaseCategory.title: Int
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
EmojibaseCategory.People -> R.string.emoji_picker_category_people
|
EmojibaseCategory.People -> R.string.emoji_picker_category_people
|
||||||
EmojibaseCategory.Nature -> R.string.emoji_picker_category_nature
|
EmojibaseCategory.Nature -> R.string.emoji_picker_category_nature
|
||||||
@@ -35,7 +35,7 @@ val EmojibaseCategory.title: Int
|
|||||||
EmojibaseCategory.Flags -> R.string.emoji_picker_category_flags
|
EmojibaseCategory.Flags -> R.string.emoji_picker_category_flags
|
||||||
}
|
}
|
||||||
|
|
||||||
val EmojibaseCategory.icon: ImageVector
|
internal val EmojibaseCategory.icon: ImageVector
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
EmojibaseCategory.People -> Icons.Outlined.EmojiPeople
|
EmojibaseCategory.People -> Icons.Outlined.EmojiPeople
|
||||||
EmojibaseCategory.Nature -> Icons.Outlined.EmojiNature
|
EmojibaseCategory.Nature -> Icons.Outlined.EmojiNature
|
||||||
+5
-5
@@ -5,7 +5,7 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.components.customreaction.picker
|
package io.element.android.libraries.emoji.impl.picker
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@@ -36,12 +36,12 @@ import kotlinx.collections.immutable.ImmutableSet
|
|||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.persistentSetOf
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
|
|
||||||
val SkinToneSlotSize = 48.dp
|
internal val SkinToneSlotSize = 48.dp
|
||||||
val SkinToneSlotSpacing = 2.dp
|
internal val SkinToneSlotSpacing = 2.dp
|
||||||
val SkinTonePadding = 4.dp
|
internal val SkinTonePadding = 4.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SkinTonePicker(
|
internal fun SkinTonePicker(
|
||||||
emoji: Emoji,
|
emoji: Emoji,
|
||||||
hoveredIndex: Int,
|
hoveredIndex: Int,
|
||||||
selectedUnicodes: ImmutableSet<String>,
|
selectedUnicodes: ImmutableSet<String>,
|
||||||
+2
-2
@@ -6,13 +6,13 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.recentemojis.impl
|
package io.element.android.libraries.emoji.impl.recentemojis
|
||||||
|
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.emoji.api.recentemojis.AddRecentEmoji
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.recentemojis.api.AddRecentEmoji
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@ContributesBinding(SessionScope::class)
|
@ContributesBinding(SessionScope::class)
|
||||||
+4
-4
@@ -6,14 +6,14 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.recentemojis.impl
|
package io.element.android.libraries.emoji.impl.recentemojis
|
||||||
|
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.emoji.api.recentemojis.GetRecentEmojis
|
||||||
|
import io.element.android.libraries.emoji.impl.EmojibaseProvider
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.recentemojis.api.EmojibaseProvider
|
|
||||||
import io.element.android.libraries.recentemojis.api.GetRecentEmojis
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -25,7 +25,7 @@ class DefaultGetRecentEmojis(
|
|||||||
private val emojibaseProvider: EmojibaseProvider,
|
private val emojibaseProvider: EmojibaseProvider,
|
||||||
) : GetRecentEmojis {
|
) : GetRecentEmojis {
|
||||||
override suspend operator fun invoke(): Result<ImmutableList<String>> = withContext(dispatchers.io) {
|
override suspend operator fun invoke(): Result<ImmutableList<String>> = withContext(dispatchers.io) {
|
||||||
val allEmojis = emojibaseProvider.emojibaseStore.allEmojis
|
val allEmojis = emojibaseProvider.getStore().allEmojis
|
||||||
client.getRecentEmojis()
|
client.getRecentEmojis()
|
||||||
.map { emojis ->
|
.map { emojis ->
|
||||||
// Remove any possible duplicates
|
// Remove any possible duplicates
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="emoji_picker_category_activity">"Актыўнасці"</string>
|
||||||
|
<string name="emoji_picker_category_flags">"Сцягі"</string>
|
||||||
|
<string name="emoji_picker_category_foods">"Ежа & Напоі"</string>
|
||||||
|
<string name="emoji_picker_category_nature">"Жывёлы & Прырода"</string>
|
||||||
|
<string name="emoji_picker_category_objects">"Аб\'екты"</string>
|
||||||
|
<string name="emoji_picker_category_people">"Усмешкі & Удзельнікі"</string>
|
||||||
|
<string name="emoji_picker_category_places">"Падарожжы & Месцы"</string>
|
||||||
|
<string name="emoji_picker_category_symbols">"Сімвалы"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="emoji_picker_category_activity">"Дейности"</string>
|
||||||
|
<string name="emoji_picker_category_flags">"Знамена"</string>
|
||||||
|
<string name="emoji_picker_category_foods">"Храна & Напитки"</string>
|
||||||
|
<string name="emoji_picker_category_nature">"Животни & Природа"</string>
|
||||||
|
<string name="emoji_picker_category_objects">"Обекти"</string>
|
||||||
|
<string name="emoji_picker_category_people">"Усмивки & Хора"</string>
|
||||||
|
<string name="emoji_picker_category_places">"Пътуване & Места"</string>
|
||||||
|
<string name="emoji_picker_category_symbols">"Символи"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="emoji_picker_category_activity">"Activitats"</string>
|
||||||
|
<string name="emoji_picker_category_flags">"Banderes"</string>
|
||||||
|
<string name="emoji_picker_category_foods">"Menjar & begudes"</string>
|
||||||
|
<string name="emoji_picker_category_nature">"Animals i naturalesa"</string>
|
||||||
|
<string name="emoji_picker_category_objects">"Objectes"</string>
|
||||||
|
<string name="emoji_picker_category_people">"Cares i persones"</string>
|
||||||
|
<string name="emoji_picker_category_places">"Viatges i llocs"</string>
|
||||||
|
<string name="emoji_picker_category_recent">"Emoticones recents"</string>
|
||||||
|
<string name="emoji_picker_category_symbols">"Símbols"</string>
|
||||||
|
<string name="emoji_picker_search_placeholder">"Cerca emoticones"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="emoji_picker_category_activity">"Aktivity"</string>
|
||||||
|
<string name="emoji_picker_category_flags">"Vlajky"</string>
|
||||||
|
<string name="emoji_picker_category_foods">"Jídlo a nápoje"</string>
|
||||||
|
<string name="emoji_picker_category_nature">"Zvířata a příroda"</string>
|
||||||
|
<string name="emoji_picker_category_objects">"Předměty"</string>
|
||||||
|
<string name="emoji_picker_category_people">"Smajlíci a lidé"</string>
|
||||||
|
<string name="emoji_picker_category_places">"Cestování a místa"</string>
|
||||||
|
<string name="emoji_picker_category_recent">"Nedávné emotikony"</string>
|
||||||
|
<string name="emoji_picker_category_symbols">"Symboly"</string>
|
||||||
|
<string name="emoji_picker_search_placeholder">"Hledat emotikony"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="emoji_picker_category_activity">"Gweithgareddau"</string>
|
||||||
|
<string name="emoji_picker_category_flags">"Baneri"</string>
|
||||||
|
<string name="emoji_picker_category_foods">"Bwyd a Diod"</string>
|
||||||
|
<string name="emoji_picker_category_nature">"Anifeiliaid a Natur"</string>
|
||||||
|
<string name="emoji_picker_category_objects">"Gwrthrychau"</string>
|
||||||
|
<string name="emoji_picker_category_people">"Wynebau Hapus a Phobl"</string>
|
||||||
|
<string name="emoji_picker_category_places">"Teithio a Llefydd"</string>
|
||||||
|
<string name="emoji_picker_category_recent">"Emojis diweddar"</string>
|
||||||
|
<string name="emoji_picker_category_symbols">"Symbolau"</string>
|
||||||
|
<string name="emoji_picker_search_placeholder">"Chwilio emojis"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="emoji_picker_category_activity">"Aktiviteter"</string>
|
||||||
|
<string name="emoji_picker_category_flags">"Flag"</string>
|
||||||
|
<string name="emoji_picker_category_foods">"Mad og drikke"</string>
|
||||||
|
<string name="emoji_picker_category_nature">"Dyr og natur"</string>
|
||||||
|
<string name="emoji_picker_category_objects">"Objekter"</string>
|
||||||
|
<string name="emoji_picker_category_people">"Smileys og personer"</string>
|
||||||
|
<string name="emoji_picker_category_places">"Rejser og steder"</string>
|
||||||
|
<string name="emoji_picker_category_recent">"Seneste emojis"</string>
|
||||||
|
<string name="emoji_picker_category_symbols">"Symboler"</string>
|
||||||
|
<string name="emoji_picker_search_placeholder">"Søg emojis"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="emoji_picker_category_activity">"Aktivitäten"</string>
|
||||||
|
<string name="emoji_picker_category_flags">"Flaggen"</string>
|
||||||
|
<string name="emoji_picker_category_foods">"Essen & Trinken"</string>
|
||||||
|
<string name="emoji_picker_category_nature">"Tiere & Natur"</string>
|
||||||
|
<string name="emoji_picker_category_objects">"Objekte"</string>
|
||||||
|
<string name="emoji_picker_category_people">"Smileys & Menschen"</string>
|
||||||
|
<string name="emoji_picker_category_places">"Reisen & Orte"</string>
|
||||||
|
<string name="emoji_picker_category_recent">"Zuletzt verwendete Emojis"</string>
|
||||||
|
<string name="emoji_picker_category_symbols">"Symbole"</string>
|
||||||
|
<string name="emoji_picker_search_placeholder">"Emojis suchen"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="emoji_picker_category_activity">"Δραστηριότητες"</string>
|
||||||
|
<string name="emoji_picker_category_flags">"Σημαίες"</string>
|
||||||
|
<string name="emoji_picker_category_foods">"Φαγητό & Ποτό"</string>
|
||||||
|
<string name="emoji_picker_category_nature">"Ζώα & Φύση"</string>
|
||||||
|
<string name="emoji_picker_category_objects">"Αντικείμενα"</string>
|
||||||
|
<string name="emoji_picker_category_people">"Φατσούλες & Άνθρωποι"</string>
|
||||||
|
<string name="emoji_picker_category_places">"Ταξίδια & Μέρη"</string>
|
||||||
|
<string name="emoji_picker_category_recent">"Πρόσφατα emoji"</string>
|
||||||
|
<string name="emoji_picker_category_symbols">"Σύμβολα"</string>
|
||||||
|
<string name="emoji_picker_search_placeholder">"Αναζήτηση emoji"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="emoji_picker_category_activity">"Actividades"</string>
|
||||||
|
<string name="emoji_picker_category_flags">"Banderas"</string>
|
||||||
|
<string name="emoji_picker_category_foods">"Comida y bebida"</string>
|
||||||
|
<string name="emoji_picker_category_nature">"Animales y naturaleza"</string>
|
||||||
|
<string name="emoji_picker_category_objects">"Objetos"</string>
|
||||||
|
<string name="emoji_picker_category_people">"Emojis y personas"</string>
|
||||||
|
<string name="emoji_picker_category_places">"Viajes y lugares"</string>
|
||||||
|
<string name="emoji_picker_category_symbols">"Símbolos"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="emoji_picker_category_activity">"Tegevused"</string>
|
||||||
|
<string name="emoji_picker_category_flags">"Lipud"</string>
|
||||||
|
<string name="emoji_picker_category_foods">"Toit ja jook"</string>
|
||||||
|
<string name="emoji_picker_category_nature">"Loomad ja loodus"</string>
|
||||||
|
<string name="emoji_picker_category_objects">"Esemed"</string>
|
||||||
|
<string name="emoji_picker_category_people">"Emotikonid ja inimesed"</string>
|
||||||
|
<string name="emoji_picker_category_places">"Reisimine ja kohad"</string>
|
||||||
|
<string name="emoji_picker_category_recent">"Hiljutised emojid"</string>
|
||||||
|
<string name="emoji_picker_category_symbols">"Sümbolid"</string>
|
||||||
|
<string name="emoji_picker_search_placeholder">"Otsi emojisid"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="emoji_picker_category_activity">"Jarduerak"</string>
|
||||||
|
<string name="emoji_picker_category_flags">"Banderak"</string>
|
||||||
|
<string name="emoji_picker_category_foods">"Jana eta edana"</string>
|
||||||
|
<string name="emoji_picker_category_nature">"Animaliak eta natura"</string>
|
||||||
|
<string name="emoji_picker_category_objects">"Objektuak"</string>
|
||||||
|
<string name="emoji_picker_category_people">"Irribartxoak eta jendea"</string>
|
||||||
|
<string name="emoji_picker_category_places">"Bidaiak eta tokiak"</string>
|
||||||
|
<string name="emoji_picker_category_symbols">"Ikurrak"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="emoji_picker_category_activity">"فعّالیتها"</string>
|
||||||
|
<string name="emoji_picker_category_flags">"پرچمها"</string>
|
||||||
|
<string name="emoji_picker_category_foods">"غذا و نوشیدنی"</string>
|
||||||
|
<string name="emoji_picker_category_nature">"حیوانات و طبعیت"</string>
|
||||||
|
<string name="emoji_picker_category_objects">"اشیا"</string>
|
||||||
|
<string name="emoji_picker_category_people">"شکلکها و افراد"</string>
|
||||||
|
<string name="emoji_picker_category_places">"سفر و مکانها"</string>
|
||||||
|
<string name="emoji_picker_category_recent">"شکلکهای اخیر"</string>
|
||||||
|
<string name="emoji_picker_category_symbols">"نمادها"</string>
|
||||||
|
<string name="emoji_picker_search_placeholder">"حستوجوی شکلکها"</string>
|
||||||
|
</resources>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user