mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-07 20:36:05 +00:00
Merge branch 'develop' into feature/valere/timeline_active_call_rendering
# Conflicts: # tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.customreaction_EmojiItemWithPopup_Night_0_en.png
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-11
@@ -63,6 +63,8 @@ import io.element.android.libraries.designsystem.theme.roomListRoomName
|
|||||||
import io.element.android.libraries.designsystem.theme.unreadIndicator
|
import io.element.android.libraries.designsystem.theme.unreadIndicator
|
||||||
import io.element.android.libraries.matrix.api.notification.CallIntent
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
|
import io.element.android.libraries.matrix.ui.components.DisplayNameWithStatus
|
||||||
import io.element.android.libraries.matrix.ui.components.InviteSenderView
|
import io.element.android.libraries.matrix.ui.components.InviteSenderView
|
||||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
@@ -126,7 +128,8 @@ internal fun RoomSummaryRow(
|
|||||||
NameAndTimestampRow(
|
NameAndTimestampRow(
|
||||||
name = room.name,
|
name = room.name,
|
||||||
timestamp = room.timestamp,
|
timestamp = room.timestamp,
|
||||||
isHighlighted = room.isHighlighted
|
isHighlighted = room.isHighlighted,
|
||||||
|
dmUserStatus = room.dmUserStatus,
|
||||||
)
|
)
|
||||||
MessagePreviewAndIndicatorRow(room = room, showUnreadCount = showUnreadCount)
|
MessagePreviewAndIndicatorRow(room = room, showUnreadCount = showUnreadCount)
|
||||||
}
|
}
|
||||||
@@ -142,7 +145,8 @@ internal fun RoomSummaryRow(
|
|||||||
NameAndTimestampRow(
|
NameAndTimestampRow(
|
||||||
name = room.name,
|
name = room.name,
|
||||||
timestamp = null,
|
timestamp = null,
|
||||||
isHighlighted = room.isHighlighted
|
isHighlighted = room.isHighlighted,
|
||||||
|
dmUserStatus = null,
|
||||||
)
|
)
|
||||||
if (room.canonicalAlias != null) {
|
if (room.canonicalAlias != null) {
|
||||||
Text(
|
Text(
|
||||||
@@ -218,22 +222,21 @@ private fun NameAndTimestampRow(
|
|||||||
name: String?,
|
name: String?,
|
||||||
timestamp: String?,
|
timestamp: String?,
|
||||||
isHighlighted: Boolean,
|
isHighlighted: Boolean,
|
||||||
|
dmUserStatus: DisplayedStatus?,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = spacedBy(16.dp)
|
horizontalArrangement = spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
val displayName = name?.toSafeLength(ellipsize = true) ?: stringResource(id = CommonStrings.common_no_room_name)
|
||||||
modifier = Modifier
|
DisplayNameWithStatus(
|
||||||
.weight(1f)
|
name = displayName,
|
||||||
.clipToBounds(),
|
status = dmUserStatus,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
style = ElementTheme.typography.fontBodyLgMedium,
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
text = name?.toSafeLength(ellipsize = true) ?: stringResource(id = CommonStrings.common_no_room_name),
|
nameColor = ElementTheme.colors.roomListRoomName,
|
||||||
fontStyle = FontStyle.Italic.takeIf { name == null },
|
nameFontStyle = FontStyle.Italic.takeIf { name == null },
|
||||||
color = ElementTheme.colors.roomListRoomName,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
)
|
||||||
// Timestamp
|
// Timestamp
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
+2
@@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.room.CallIntentConsensus
|
|||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
import io.element.android.libraries.matrix.api.roomlist.LatestEventValue
|
import io.element.android.libraries.matrix.api.roomlist.LatestEventValue
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
|
import io.element.android.libraries.matrix.ui.model.dmUserStatus
|
||||||
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.model.toInviteSender
|
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
@@ -76,6 +77,7 @@ class RoomListRoomSummaryFactory(
|
|||||||
}.toImmutableList(),
|
}.toImmutableList(),
|
||||||
isTombstoned = roomInfo.successorRoom != null,
|
isTombstoned = roomInfo.successorRoom != null,
|
||||||
isSpace = roomInfo.isSpace,
|
isSpace = roomInfo.isSpace,
|
||||||
|
dmUserStatus = roomInfo.dmUserStatus(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
@@ -15,6 +15,7 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
|
|||||||
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.notification.CallIntent
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ data class RoomListRoomSummary(
|
|||||||
val isTombstoned: Boolean,
|
val isTombstoned: Boolean,
|
||||||
val heroes: ImmutableList<AvatarData>,
|
val heroes: ImmutableList<AvatarData>,
|
||||||
val isSpace: Boolean,
|
val isSpace: Boolean,
|
||||||
|
val dmUserStatus: DisplayedStatus?,
|
||||||
) {
|
) {
|
||||||
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
|
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
|
||||||
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) ||
|
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) ||
|
||||||
|
|||||||
+4
-1
@@ -20,6 +20,7 @@ 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.notification.CallIntent
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
@@ -182,6 +183,7 @@ internal fun aRoomListRoomSummary(
|
|||||||
heroes: List<AvatarData> = emptyList(),
|
heroes: List<AvatarData> = emptyList(),
|
||||||
isTombstoned: Boolean = false,
|
isTombstoned: Boolean = false,
|
||||||
isSpace: Boolean = false,
|
isSpace: Boolean = false,
|
||||||
|
dmUserStatus: DisplayedStatus? = null,
|
||||||
) = RoomListRoomSummary(
|
) = RoomListRoomSummary(
|
||||||
id = id,
|
id = id,
|
||||||
roomId = RoomId(id),
|
roomId = RoomId(id),
|
||||||
@@ -204,5 +206,6 @@ internal fun aRoomListRoomSummary(
|
|||||||
canonicalAlias = canonicalAlias,
|
canonicalAlias = canonicalAlias,
|
||||||
heroes = heroes.toImmutableList(),
|
heroes = heroes.toImmutableList(),
|
||||||
isTombstoned = isTombstoned,
|
isTombstoned = isTombstoned,
|
||||||
isSpace = isSpace
|
isSpace = isSpace,
|
||||||
|
dmUserStatus = dmUserStatus,
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-1
@@ -12,6 +12,7 @@ import com.google.common.truth.Truth.assertThat
|
|||||||
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.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
@@ -87,6 +88,7 @@ internal fun createRoomListRoomSummary(
|
|||||||
timestamp: String? = null,
|
timestamp: String? = null,
|
||||||
isTombstoned: Boolean = false,
|
isTombstoned: Boolean = false,
|
||||||
isSpace: Boolean = false,
|
isSpace: Boolean = false,
|
||||||
|
dmUserStatus: DisplayedStatus? = null,
|
||||||
) = RoomListRoomSummary(
|
) = RoomListRoomSummary(
|
||||||
id = A_ROOM_ID.value,
|
id = A_ROOM_ID.value,
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
@@ -109,5 +111,6 @@ internal fun createRoomListRoomSummary(
|
|||||||
isDm = false,
|
isDm = false,
|
||||||
heroes = heroes.toImmutableList(),
|
heroes = heroes.toImmutableList(),
|
||||||
isTombstoned = isTombstoned,
|
isTombstoned = isTombstoned,
|
||||||
isSpace = isSpace
|
isSpace = isSpace,
|
||||||
|
dmUserStatus = dmUserStatus,
|
||||||
)
|
)
|
||||||
|
|||||||
+18
-3
@@ -9,21 +9,25 @@
|
|||||||
|
|
||||||
package io.element.android.features.linknewdevice.impl.screens.desktop
|
package io.element.android.features.linknewdevice.impl.screens.desktop
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
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.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.linknewdevice.impl.R
|
import io.element.android.features.linknewdevice.impl.R
|
||||||
import io.element.android.libraries.designsystem.atomic.organisms.NumberedListOrganism
|
import io.element.android.libraries.designsystem.atomic.organisms.NumberedListOrganism
|
||||||
@@ -34,6 +38,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|||||||
import io.element.android.libraries.designsystem.theme.LocalBuildMeta
|
import io.element.android.libraries.designsystem.theme.LocalBuildMeta
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
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.Text
|
||||||
import io.element.android.libraries.designsystem.utils.annotatedTextWithBold
|
import io.element.android.libraries.designsystem.utils.annotatedTextWithBold
|
||||||
import io.element.android.libraries.permissions.api.PermissionsView
|
import io.element.android.libraries.permissions.api.PermissionsView
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
@@ -71,11 +76,10 @@ fun DesktopNoticeView(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
Modifier.fillMaxWidth()
|
Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(40.dp))
|
Spacer(modifier = Modifier.height(40.dp))
|
||||||
NumberedListOrganism(
|
NumberedListOrganism(
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
items = persistentListOf(
|
items = persistentListOf(
|
||||||
AnnotatedString(stringResource(R.string.screen_link_new_device_desktop_step1, appName)),
|
AnnotatedString(stringResource(R.string.screen_link_new_device_desktop_step1, appName)),
|
||||||
annotatedTextWithBold(
|
annotatedTextWithBold(
|
||||||
@@ -88,6 +92,17 @@ fun DesktopNoticeView(
|
|||||||
AnnotatedString(stringResource(R.string.screen_link_new_device_desktop_step3)),
|
AnnotatedString(stringResource(R.string.screen_link_new_device_desktop_step3)),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(12.dp))
|
||||||
|
.background(ElementTheme.colors.bgCriticalSubtle)
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
text = stringResource(R.string.screen_link_new_device_desktop_warning),
|
||||||
|
style = ElementTheme.typography.fontBodyMdMedium,
|
||||||
|
color = ElementTheme.colors.textCriticalPrimary,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<string name="screen_link_new_device_desktop_step3">"Scan the QR code with this device"</string>
|
<string name="screen_link_new_device_desktop_step3">"Scan the QR code with this device"</string>
|
||||||
<string name="screen_link_new_device_desktop_submit">"Ready to scan"</string>
|
<string name="screen_link_new_device_desktop_submit">"Ready to scan"</string>
|
||||||
<string name="screen_link_new_device_desktop_title">"Open %1$s on a desktop computer to get the QR code"</string>
|
<string name="screen_link_new_device_desktop_title">"Open %1$s on a desktop computer to get the QR code"</string>
|
||||||
|
<string name="screen_link_new_device_desktop_warning">"Only scan a QR code after following the steps above on your own device. Never scan a code sent by another person."</string>
|
||||||
<string name="screen_link_new_device_enter_number_error_numbers_do_not_match">"The numbers don’t match"</string>
|
<string name="screen_link_new_device_enter_number_error_numbers_do_not_match">"The numbers don’t match"</string>
|
||||||
<string name="screen_link_new_device_enter_number_notice">"Enter 2-digit code"</string>
|
<string name="screen_link_new_device_enter_number_notice">"Enter 2-digit code"</string>
|
||||||
<string name="screen_link_new_device_enter_number_subtitle">"This will verify that the connection to your other device is secure."</string>
|
<string name="screen_link_new_device_enter_number_subtitle">"This will verify that the connection to your other device is secure."</string>
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
+3
-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
|
||||||
@@ -83,9 +84,9 @@ import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsSta
|
|||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||||
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.dmUserStatus
|
||||||
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
|
||||||
@@ -319,6 +320,7 @@ class MessagesPresenter(
|
|||||||
appName = buildMeta.applicationName,
|
appName = buildMeta.applicationName,
|
||||||
pinnedMessagesBannerState = pinnedMessagesBannerState,
|
pinnedMessagesBannerState = pinnedMessagesBannerState,
|
||||||
dmUserVerificationState = dmUserVerificationState,
|
dmUserVerificationState = dmUserVerificationState,
|
||||||
|
dmUserStatus = roomInfo.dmUserStatus(),
|
||||||
roomMemberModerationState = roomMemberModerationState,
|
roomMemberModerationState = roomMemberModerationState,
|
||||||
topBarSharedHistoryIcon = topBarSharedHistoryIcon,
|
topBarSharedHistoryIcon = topBarSharedHistoryIcon,
|
||||||
successorRoom = roomInfo.successorRoom,
|
successorRoom = roomInfo.successorRoom,
|
||||||
|
|||||||
+2
@@ -27,6 +27,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
|||||||
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.encryption.identity.IdentityState
|
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||||
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
data class MessagesState(
|
data class MessagesState(
|
||||||
@@ -53,6 +54,7 @@ data class MessagesState(
|
|||||||
val appName: String,
|
val appName: String,
|
||||||
val pinnedMessagesBannerState: PinnedMessagesBannerState,
|
val pinnedMessagesBannerState: PinnedMessagesBannerState,
|
||||||
val dmUserVerificationState: IdentityState?,
|
val dmUserVerificationState: IdentityState?,
|
||||||
|
val dmUserStatus: DisplayedStatus?,
|
||||||
val roomMemberModerationState: RoomMemberModerationState,
|
val roomMemberModerationState: RoomMemberModerationState,
|
||||||
/** Type of "shared history" icon to show in the top bar. */
|
/** Type of "shared history" icon to show in the top bar. */
|
||||||
val topBarSharedHistoryIcon: SharedHistoryIcon,
|
val topBarSharedHistoryIcon: SharedHistoryIcon,
|
||||||
|
|||||||
+3
-3
@@ -50,10 +50,10 @@ import io.element.android.libraries.matrix.api.core.ThreadId
|
|||||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||||
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
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
|
||||||
|
|
||||||
@@ -129,6 +129,7 @@ fun aMessagesState(
|
|||||||
hasUnreadThreads = false,
|
hasUnreadThreads = false,
|
||||||
),
|
),
|
||||||
isCurrentlySharingLiveLocationInRoom: Boolean = false,
|
isCurrentlySharingLiveLocationInRoom: Boolean = false,
|
||||||
|
dmUserStatus: DisplayedStatus? = null,
|
||||||
eventSink: (MessagesEvent) -> Unit = {},
|
eventSink: (MessagesEvent) -> Unit = {},
|
||||||
) = MessagesState(
|
) = MessagesState(
|
||||||
roomId = RoomId("!id:domain"),
|
roomId = RoomId("!id:domain"),
|
||||||
@@ -159,6 +160,7 @@ fun aMessagesState(
|
|||||||
successorRoom = successorRoom,
|
successorRoom = successorRoom,
|
||||||
threads = threads,
|
threads = threads,
|
||||||
showLiveLocationShareBanner = isCurrentlySharingLiveLocationInRoom,
|
showLiveLocationShareBanner = isCurrentlySharingLiveLocationInRoom,
|
||||||
|
dmUserStatus = dmUserStatus,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -193,11 +195,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,
|
||||||
)
|
)
|
||||||
|
|||||||
+6
-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
|
||||||
|
|
||||||
@@ -244,6 +244,7 @@ fun MessagesView(
|
|||||||
heroes = state.heroes,
|
heroes = state.heroes,
|
||||||
dmUserIdentityState = state.dmUserVerificationState,
|
dmUserIdentityState = state.dmUserVerificationState,
|
||||||
sharedHistoryIcon = state.topBarSharedHistoryIcon,
|
sharedHistoryIcon = state.topBarSharedHistoryIcon,
|
||||||
|
dmUserStatus = state.dmUserStatus,
|
||||||
onBackClick = { hidingKeyboard { onBackClick() } },
|
onBackClick = { hidingKeyboard { onBackClick() } },
|
||||||
onRoomDetailsClick = { hidingKeyboard { onRoomDetailsClick() } },
|
onRoomDetailsClick = { hidingKeyboard { onRoomDetailsClick() } },
|
||||||
menuActions = {
|
menuActions = {
|
||||||
@@ -410,12 +411,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(
|
||||||
@@ -672,6 +668,7 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
|
|||||||
onViewAllPinnedMessagesClick = { },
|
onViewAllPinnedMessagesClick = { },
|
||||||
forceJumpToBottomVisibility = true,
|
forceJumpToBottomVisibility = true,
|
||||||
knockRequestsBannerView = {},
|
knockRequestsBannerView = {},
|
||||||
|
customReactionBottomSheet = {},
|
||||||
onThreadsListClick = {},
|
onThreadsListClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -729,5 +726,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 = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-6
@@ -41,6 +41,8 @@ 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.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
|
import io.element.android.libraries.matrix.api.room.getBestName
|
||||||
|
import io.element.android.libraries.matrix.ui.components.DisplayNameWithStatus
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
import io.element.android.libraries.slashcommands.api.SlashCommandSuggestion
|
import io.element.android.libraries.slashcommands.api.SlashCommandSuggestion
|
||||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||||
@@ -112,12 +114,16 @@ private fun SuggestionItemView(
|
|||||||
is ResolvedSuggestion.Member -> AvatarType.User
|
is ResolvedSuggestion.Member -> AvatarType.User
|
||||||
is ResolvedSuggestion.Command -> null
|
is ResolvedSuggestion.Command -> null
|
||||||
}
|
}
|
||||||
val title = when (suggestion) {
|
val title: String? = when (suggestion) {
|
||||||
is ResolvedSuggestion.AtRoom -> stringResource(R.string.screen_room_mentions_at_room_title)
|
is ResolvedSuggestion.AtRoom -> stringResource(R.string.screen_room_mentions_at_room_title)
|
||||||
is ResolvedSuggestion.Member -> suggestion.roomMember.displayName
|
is ResolvedSuggestion.Member -> suggestion.roomMember.getBestName()
|
||||||
is ResolvedSuggestion.Alias -> suggestion.roomName
|
is ResolvedSuggestion.Alias -> suggestion.roomName
|
||||||
is ResolvedSuggestion.Command -> suggestion.command.command
|
is ResolvedSuggestion.Command -> suggestion.command.command
|
||||||
}
|
}
|
||||||
|
val status = when (suggestion) {
|
||||||
|
is ResolvedSuggestion.Member -> suggestion.roomMember.displayedStatus
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
val details = when (suggestion) {
|
val details = when (suggestion) {
|
||||||
is ResolvedSuggestion.AtRoom,
|
is ResolvedSuggestion.AtRoom,
|
||||||
is ResolvedSuggestion.Member,
|
is ResolvedSuggestion.Member,
|
||||||
@@ -148,11 +154,11 @@ private fun SuggestionItemView(
|
|||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
title?.let {
|
title?.let {
|
||||||
Text(
|
DisplayNameWithStatus(
|
||||||
text = it,
|
name = title,
|
||||||
|
status = status,
|
||||||
style = ElementTheme.typography.fontBodyLgRegular,
|
style = ElementTheme.typography.fontBodyLgRegular,
|
||||||
maxLines = 1,
|
nameColor = ElementTheme.colors.textPrimary,
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
details?.let {
|
details?.let {
|
||||||
@@ -191,6 +197,7 @@ internal fun SuggestionsPickerViewPreview() {
|
|||||||
role = RoomMember.Role.User,
|
role = RoomMember.Role.User,
|
||||||
membershipChangeReason = null,
|
membershipChangeReason = null,
|
||||||
isServiceMember = false,
|
isServiceMember = false,
|
||||||
|
displayedStatus = null,
|
||||||
)
|
)
|
||||||
val anAlias = remember { RoomAlias("#room:domain.org") }
|
val anAlias = remember { RoomAlias("#room:domain.org") }
|
||||||
SuggestionsPickerView(
|
SuggestionsPickerView(
|
||||||
|
|||||||
+1
@@ -322,6 +322,7 @@ class ThreadedMessagesNode(
|
|||||||
onViewAllPinnedMessagesClick = {},
|
onViewAllPinnedMessagesClick = {},
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
knockRequestsBannerView = {},
|
knockRequestsBannerView = {},
|
||||||
|
customReactionBottomSheet = {},
|
||||||
onThreadsListClick = {},
|
onThreadsListClick = {},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -383,7 +383,12 @@ fun aThreadListItem(
|
|||||||
fun aThreadListItemEvent(
|
fun aThreadListItemEvent(
|
||||||
threadId: ThreadId = ThreadId("\$a-thread-id"),
|
threadId: ThreadId = ThreadId("\$a-thread-id"),
|
||||||
senderId: UserId = UserId("@a-user-id:server"),
|
senderId: UserId = UserId("@a-user-id:server"),
|
||||||
senderProfile: ProfileDetails = ProfileDetails.Ready(displayName = USER_NAME_ALICE, displayNameAmbiguous = false, avatarUrl = null),
|
senderProfile: ProfileDetails = ProfileDetails.Ready(
|
||||||
|
displayName = USER_NAME_ALICE,
|
||||||
|
displayNameAmbiguous = false,
|
||||||
|
avatarUrl = null,
|
||||||
|
displayedStatus = null,
|
||||||
|
),
|
||||||
isOwn: Boolean = false,
|
isOwn: Boolean = false,
|
||||||
content: EventContent = MessageContent(
|
content: EventContent = MessageContent(
|
||||||
body = "Hello world!",
|
body = "Hello world!",
|
||||||
|
|||||||
+1
@@ -166,6 +166,7 @@ internal fun MessageShieldViewPreview() {
|
|||||||
displayName = USER_NAME_ALICE,
|
displayName = USER_NAME_ALICE,
|
||||||
displayNameAmbiguous = false,
|
displayNameAmbiguous = false,
|
||||||
avatarUrl = null,
|
avatarUrl = null,
|
||||||
|
displayedStatus = null,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
+12
-2
@@ -315,7 +315,12 @@ private fun inReplyToInvalidContent(): InReplyToDetails.Ready = InReplyToDetails
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
textContent = "A text content",
|
textContent = "A text content",
|
||||||
senderProfile = ProfileDetails.Ready(displayName = "Sender", displayNameAmbiguous = false, avatarUrl = null)
|
senderProfile = ProfileDetails.Ready(
|
||||||
|
displayName = "Sender",
|
||||||
|
displayNameAmbiguous = false,
|
||||||
|
avatarUrl = null,
|
||||||
|
displayedStatus = null
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun inReplyToTextContent(): InReplyToDetails.Ready = InReplyToDetails.Ready(
|
private fun inReplyToTextContent(): InReplyToDetails.Ready = InReplyToDetails.Ready(
|
||||||
@@ -332,5 +337,10 @@ private fun inReplyToTextContent(): InReplyToDetails.Ready = InReplyToDetails.Re
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
textContent = "A text content",
|
textContent = "A text content",
|
||||||
senderProfile = ProfileDetails.Ready(displayName = "Sender", displayNameAmbiguous = false, avatarUrl = null)
|
senderProfile = ProfileDetails.Ready(
|
||||||
|
displayName = "Sender",
|
||||||
|
displayNameAmbiguous = false,
|
||||||
|
avatarUrl = null,
|
||||||
|
displayedStatus = null
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
+2
@@ -962,6 +962,7 @@ internal fun TimelineItemEventRowWithThreadSummaryPreview() = ElementPreview {
|
|||||||
displayName = USER_NAME_ALICE,
|
displayName = USER_NAME_ALICE,
|
||||||
avatarUrl = null,
|
avatarUrl = null,
|
||||||
displayNameAmbiguous = false,
|
displayNameAmbiguous = false,
|
||||||
|
displayedStatus = null,
|
||||||
),
|
),
|
||||||
timestamp = 0L,
|
timestamp = 0L,
|
||||||
)
|
)
|
||||||
@@ -1140,6 +1141,7 @@ internal fun ThreadSummaryViewPreview() {
|
|||||||
displayName = USER_NAME_ALICE,
|
displayName = USER_NAME_ALICE,
|
||||||
avatarUrl = null,
|
avatarUrl = null,
|
||||||
displayNameAmbiguous = true,
|
displayNameAmbiguous = true,
|
||||||
|
displayedStatus = null,
|
||||||
),
|
),
|
||||||
timestamp = 0L,
|
timestamp = 0L,
|
||||||
)
|
)
|
||||||
|
|||||||
+21
-26
@@ -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
|
|
||||||
|
when (state.target) {
|
||||||
|
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 onDismiss() {
|
fun onDismiss() {
|
||||||
state.eventSink(CustomReactionEvent.DismissCustomReactionSheet)
|
state.eventSink(CustomReactionEvent.DismissCustomReactionSheet)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onEmojiSelectedDismiss(emoji: Emoji) {
|
|
||||||
if (target?.event == null) return
|
|
||||||
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,
|
||||||
) {
|
) {
|
||||||
val presenter = remember {
|
emojiPickerRenderer.Render(
|
||||||
EmojiPickerPresenter(
|
state = state.target.emojiPickerState,
|
||||||
emojibaseStore = target.emojibaseStore,
|
|
||||||
recentEmojis = state.recentEmojis,
|
|
||||||
coroutineDispatchers = CoroutineDispatchers.Default,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
EmojiPicker(
|
|
||||||
onSelectEmoji = ::onEmojiSelectedDismiss,
|
onSelectEmoji = ::onEmojiSelectedDismiss,
|
||||||
state = presenter.present(),
|
|
||||||
selectedEmojis = state.selectedEmoji,
|
selectedEmojis = state.selectedEmoji,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentDescription = { emoji, isSelected ->
|
||||||
|
a11yReactionAction(emoji = emoji.unicode, userAlreadyReacted = isSelected)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+19
-9
@@ -26,7 +26,6 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.semantics.heading
|
import androidx.compose.ui.semantics.heading
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
@@ -46,9 +45,10 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|||||||
import io.element.android.libraries.designsystem.preview.ROOM_NAME
|
import io.element.android.libraries.designsystem.preview.ROOM_NAME
|
||||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||||
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.Text
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
|
import io.element.android.libraries.matrix.ui.components.DisplayNameWithStatus
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
@@ -65,6 +65,7 @@ internal fun MessagesViewTopBar(
|
|||||||
heroes: ImmutableList<AvatarData>,
|
heroes: ImmutableList<AvatarData>,
|
||||||
dmUserIdentityState: IdentityState?,
|
dmUserIdentityState: IdentityState?,
|
||||||
sharedHistoryIcon: SharedHistoryIcon,
|
sharedHistoryIcon: SharedHistoryIcon,
|
||||||
|
dmUserStatus: DisplayedStatus?,
|
||||||
onRoomDetailsClick: () -> Unit,
|
onRoomDetailsClick: () -> Unit,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@@ -91,6 +92,7 @@ internal fun MessagesViewTopBar(
|
|||||||
roomAvatar = roomAvatar,
|
roomAvatar = roomAvatar,
|
||||||
isTombstoned = isTombstoned,
|
isTombstoned = isTombstoned,
|
||||||
heroes = heroes,
|
heroes = heroes,
|
||||||
|
dmUserStatus = dmUserStatus,
|
||||||
modifier = titleModifier
|
modifier = titleModifier
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -144,6 +146,7 @@ private fun RoomAvatarAndNameRow(
|
|||||||
roomAvatar: AvatarData,
|
roomAvatar: AvatarData,
|
||||||
heroes: ImmutableList<AvatarData>,
|
heroes: ImmutableList<AvatarData>,
|
||||||
isTombstoned: Boolean,
|
isTombstoned: Boolean,
|
||||||
|
dmUserStatus: DisplayedStatus?,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
@@ -157,14 +160,13 @@ private fun RoomAvatarAndNameRow(
|
|||||||
isTombstoned = isTombstoned,
|
isTombstoned = isTombstoned,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
Text(
|
DisplayNameWithStatus(
|
||||||
modifier = Modifier
|
name = roomName ?: stringResource(CommonStrings.common_no_room_name),
|
||||||
.padding(start = 8.dp),
|
status = dmUserStatus,
|
||||||
text = roomName ?: stringResource(CommonStrings.common_no_room_name),
|
modifier = Modifier.padding(start = 8.dp),
|
||||||
style = ElementTheme.typography.fontBodyLgMedium,
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
fontStyle = FontStyle.Italic.takeIf { roomName == null },
|
nameColor = ElementTheme.colors.textPrimary,
|
||||||
maxLines = 1,
|
nameFontStyle = FontStyle.Italic.takeIf { roomName == null },
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,6 +186,7 @@ internal fun MessagesViewTopBarPreview() = ElementPreview {
|
|||||||
roomCallState: RoomCallState = RoomCallState.Unavailable,
|
roomCallState: RoomCallState = RoomCallState.Unavailable,
|
||||||
dmUserIdentityState: IdentityState? = null,
|
dmUserIdentityState: IdentityState? = null,
|
||||||
sharedHistoryIcon: SharedHistoryIcon = SharedHistoryIcon.NONE,
|
sharedHistoryIcon: SharedHistoryIcon = SharedHistoryIcon.NONE,
|
||||||
|
dmUserStatus: DisplayedStatus? = null,
|
||||||
displayThreads: Boolean = false,
|
displayThreads: Boolean = false,
|
||||||
) = MessagesViewTopBar(
|
) = MessagesViewTopBar(
|
||||||
roomName = roomName,
|
roomName = roomName,
|
||||||
@@ -192,6 +195,7 @@ internal fun MessagesViewTopBarPreview() = ElementPreview {
|
|||||||
heroes = heroes,
|
heroes = heroes,
|
||||||
dmUserIdentityState = dmUserIdentityState,
|
dmUserIdentityState = dmUserIdentityState,
|
||||||
sharedHistoryIcon = sharedHistoryIcon,
|
sharedHistoryIcon = sharedHistoryIcon,
|
||||||
|
dmUserStatus = dmUserStatus,
|
||||||
onRoomDetailsClick = {},
|
onRoomDetailsClick = {},
|
||||||
onBackClick = {},
|
onBackClick = {},
|
||||||
menuActions = {
|
menuActions = {
|
||||||
@@ -246,5 +250,11 @@ internal fun MessagesViewTopBarPreview() = ElementPreview {
|
|||||||
AMessagesViewTopBar(
|
AMessagesViewTopBar(
|
||||||
displayThreads = true,
|
displayThreads = true,
|
||||||
)
|
)
|
||||||
|
HorizontalDivider()
|
||||||
|
AMessagesViewTopBar(
|
||||||
|
roomName = "A DM with a very very very long name",
|
||||||
|
dmUserIdentityState = IdentityState.Verified,
|
||||||
|
dmUserStatus = DisplayedStatus.InCall(0L)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+19
-8
@@ -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,12 +540,7 @@ 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 stateWithCustomReactionState = state.copy(
|
val emojisByCategory = persistentMapOf(
|
||||||
customReactionState = aCustomReactionState(
|
|
||||||
target = CustomReactionState.Target.Success(
|
|
||||||
event = timelineItem,
|
|
||||||
emojibaseStore = EmojibaseStore(
|
|
||||||
categories = persistentMapOf(
|
|
||||||
EmojibaseCategory.People to persistentListOf(
|
EmojibaseCategory.People to persistentListOf(
|
||||||
Emoji(
|
Emoji(
|
||||||
hexcode = "",
|
hexcode = "",
|
||||||
@@ -555,7 +552,11 @@ class MessagesViewTest : RobolectricTest() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
val stateWithCustomReactionState = state.copy(
|
||||||
|
customReactionState = aCustomReactionState(
|
||||||
|
target = CustomReactionState.Target.Success(
|
||||||
|
event = timelineItem,
|
||||||
|
emojiPickerState = anEmojiPickerState(emojis = emojisByCategory),
|
||||||
),
|
),
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-6
@@ -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,13 +29,21 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class PredefinedUserStatus(val emoji: String, @StringRes val labelRes: Int) {
|
@Immutable
|
||||||
IN_A_MEETING("💬", R.string.common_user_status_in_a_meeting),
|
sealed interface EmojiPickerSheetState {
|
||||||
FOCUS_TIME("💡", R.string.common_user_status_focus_time),
|
data object Hidden : EmojiPickerSheetState
|
||||||
ON_THE_ROAD("🚙", R.string.common_user_status_on_the_road),
|
data object Loading : EmojiPickerSheetState
|
||||||
BE_RIGHT_BACK("☕", R.string.common_user_status_be_right_back),
|
data class Shown(val state: EmojiPickerState) : EmojiPickerSheetState
|
||||||
AWAY("🌴", R.string.common_user_status_away),
|
}
|
||||||
|
|
||||||
|
enum class PredefinedUserStatus(val emoji: String, @StringRes val labelRes: Int) {
|
||||||
|
IN_A_MEETING("💬", R.string.screen_settings_user_status_in_a_meeting),
|
||||||
|
FOCUS_TIME("💡", R.string.screen_settings_user_status_focus_time),
|
||||||
|
ON_THE_ROAD("🚙", R.string.screen_settings_user_status_on_the_road),
|
||||||
|
BE_RIGHT_BACK("☕", R.string.screen_settings_user_status_be_right_back),
|
||||||
|
AWAY("🌴", R.string.screen_settings_user_status_away),
|
||||||
}
|
}
|
||||||
|
|||||||
+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
|
||||||
|
)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+53
-6
@@ -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) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,7 +132,7 @@ private fun EmptyStatusRow(
|
|||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.screen_preferences_user_status_placeholder),
|
text = stringResource(R.string.screen_settings_user_status_placeholder),
|
||||||
modifier = Modifier.padding(vertical = 16.dp),
|
modifier = Modifier.padding(vertical = 16.dp),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -135,7 +150,7 @@ private fun CurrentStatusRow(
|
|||||||
) {
|
) {
|
||||||
val (emoji, text) = when (displayedStatus) {
|
val (emoji, text) = when (displayedStatus) {
|
||||||
is DisplayedStatus.UserSet -> displayedStatus.status.emoji to displayedStatus.status.text
|
is DisplayedStatus.UserSet -> displayedStatus.status.emoji to displayedStatus.status.text
|
||||||
is DisplayedStatus.InCall -> "🎧" to stringResource(R.string.common_user_status_on_a_call)
|
is DisplayedStatus.InCall -> "🎧" to stringResource(CommonStrings.common_on_a_call)
|
||||||
}
|
}
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text(text = text, modifier = Modifier.padding(vertical = 16.dp)) },
|
headlineContent = { Text(text = text, modifier = Modifier.padding(vertical = 16.dp)) },
|
||||||
@@ -210,7 +225,7 @@ private fun UserStatusPickerBottomSheet(
|
|||||||
}
|
}
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Text(text = stringResource(R.string.common_user_status_custom))
|
Text(text = stringResource(R.string.screen_settings_user_status_custom))
|
||||||
},
|
},
|
||||||
leadingContent = ListItemContent.Custom { EmojiText("✏️") },
|
leadingContent = ListItemContent.Custom { EmojiText("✏️") },
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -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() } }
|
||||||
@@ -249,7 +265,7 @@ private fun CustomStatusInputRow(
|
|||||||
TextField(
|
TextField(
|
||||||
modifier = Modifier.focusRequester(focusRequester),
|
modifier = Modifier.focusRequester(focusRequester),
|
||||||
state = textFieldState,
|
state = textFieldState,
|
||||||
placeholder = stringResource(R.string.screen_preferences_user_status_custom_hint),
|
placeholder = stringResource(R.string.screen_settings_user_status_custom_hint),
|
||||||
inputTransformation = InputTransformation.maxLength(CUSTOM_STATUS_TEXT_MAX_LENGTH),
|
inputTransformation = InputTransformation.maxLength(CUSTOM_STATUS_TEXT_MAX_LENGTH),
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
||||||
onKeyboardAction = { if (hasChanges) onConfirm() },
|
onKeyboardAction = { if (hasChanges) onConfirm() },
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,14 @@ If you proceed, some of your settings may change."</string>
|
|||||||
<string name="screen_notification_settings_system_notifications_action_required_content_link">"system settings"</string>
|
<string name="screen_notification_settings_system_notifications_action_required_content_link">"system settings"</string>
|
||||||
<string name="screen_notification_settings_system_notifications_turned_off">"System notifications turned off"</string>
|
<string name="screen_notification_settings_system_notifications_turned_off">"System notifications turned off"</string>
|
||||||
<string name="screen_notification_settings_title">"Notifications"</string>
|
<string name="screen_notification_settings_title">"Notifications"</string>
|
||||||
|
<string name="screen_settings_user_status_away">"Away"</string>
|
||||||
|
<string name="screen_settings_user_status_be_right_back">"Be right back"</string>
|
||||||
|
<string name="screen_settings_user_status_custom">"Custom…"</string>
|
||||||
|
<string name="screen_settings_user_status_custom_hint">"Custom status…"</string>
|
||||||
|
<string name="screen_settings_user_status_focus_time">"Focus time"</string>
|
||||||
|
<string name="screen_settings_user_status_in_a_meeting">"In a meeting"</string>
|
||||||
|
<string name="screen_settings_user_status_on_the_road">"On the road"</string>
|
||||||
|
<string name="screen_settings_user_status_placeholder">"What\'s your status?"</string>
|
||||||
<string name="theme_black">"Black"</string>
|
<string name="theme_black">"Black"</string>
|
||||||
<string name="theme_dark">"Dark"</string>
|
<string name="theme_dark">"Dark"</string>
|
||||||
<string name="theme_light">"Light"</string>
|
<string name="theme_light">"Light"</string>
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
~ 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.
|
|
||||||
-->
|
|
||||||
<resources>
|
|
||||||
<string name="common_user_status_in_a_meeting">In a meeting</string>
|
|
||||||
<string name="common_user_status_focus_time">Focus time</string>
|
|
||||||
<string name="common_user_status_on_the_road">On the road</string>
|
|
||||||
<string name="common_user_status_be_right_back">Be right back</string>
|
|
||||||
<string name="common_user_status_away">Away</string>
|
|
||||||
<string name="common_user_status_on_a_call">On a call</string>
|
|
||||||
<string name="common_user_status_custom">Custom…</string>
|
|
||||||
<string name="screen_preferences_user_status_placeholder">What\'s your status?</string>
|
|
||||||
<string name="screen_preferences_user_status_custom_hint">Custom status…</string>
|
|
||||||
</resources>
|
|
||||||
+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 {
|
||||||
|
|||||||
+5
-1
@@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
|||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
||||||
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
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.aMatrixUserList
|
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
@@ -75,6 +76,7 @@ fun aDmRoomMember(
|
|||||||
role: RoomMember.Role = RoomMember.Role.User,
|
role: RoomMember.Role = RoomMember.Role.User,
|
||||||
membershipChangeReason: String? = null,
|
membershipChangeReason: String? = null,
|
||||||
isServiceMember: Boolean = false,
|
isServiceMember: Boolean = false,
|
||||||
|
displayedStatus: DisplayedStatus? = null,
|
||||||
) = RoomMember(
|
) = RoomMember(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
displayName = displayName,
|
displayName = displayName,
|
||||||
@@ -86,6 +88,7 @@ fun aDmRoomMember(
|
|||||||
role = role,
|
role = role,
|
||||||
membershipChangeReason = membershipChangeReason,
|
membershipChangeReason = membershipChangeReason,
|
||||||
isServiceMember = isServiceMember,
|
isServiceMember = isServiceMember,
|
||||||
|
displayedStatus = displayedStatus,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aRoomDetailsState(
|
fun aRoomDetailsState(
|
||||||
@@ -178,12 +181,13 @@ fun aDmRoomDetailsState(
|
|||||||
roomName: String = "Daniel",
|
roomName: String = "Daniel",
|
||||||
isEncrypted: Boolean = true,
|
isEncrypted: Boolean = true,
|
||||||
dmRoomMemberVerificationState: UserProfileVerificationState = UserProfileVerificationState.UNKNOWN,
|
dmRoomMemberVerificationState: UserProfileVerificationState = UserProfileVerificationState.UNKNOWN,
|
||||||
|
displayedStatus: DisplayedStatus? = null,
|
||||||
) = aRoomDetailsState(
|
) = aRoomDetailsState(
|
||||||
roomName = roomName,
|
roomName = roomName,
|
||||||
isPublic = false,
|
isPublic = false,
|
||||||
isEncrypted = isEncrypted,
|
isEncrypted = isEncrypted,
|
||||||
canInvite = true,
|
canInvite = true,
|
||||||
roomType = RoomDetailsType.Dm(otherMember = aDmRoomMember(isIgnored = isDmMemberIgnored)),
|
roomType = RoomDetailsType.Dm(otherMember = aDmRoomMember(isIgnored = isDmMemberIgnored, displayedStatus = displayedStatus)),
|
||||||
dmOtherMemberDetailsState = aUserProfileState(
|
dmOtherMemberDetailsState = aUserProfileState(
|
||||||
isBlocked = AsyncData.Success(isDmMemberIgnored),
|
isBlocked = AsyncData.Success(isDmMemberIgnored),
|
||||||
verificationState = dmRoomMemberVerificationState,
|
verificationState = dmRoomMemberVerificationState,
|
||||||
|
|||||||
+36
-25
@@ -37,6 +37,7 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
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 im.vector.app.features.analytics.plan.Interaction
|
import im.vector.app.features.analytics.plan.Interaction
|
||||||
@@ -88,6 +89,7 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
|||||||
import io.element.android.libraries.matrix.api.room.getBestName
|
import io.element.android.libraries.matrix.api.room.getBestName
|
||||||
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.model.getAvatarData
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
|
import io.element.android.libraries.matrix.ui.model.toText
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.libraries.testtags.testTag
|
import io.element.android.libraries.testtags.testTag
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
@@ -489,11 +491,23 @@ private fun RoomHeaderSection(
|
|||||||
}
|
}
|
||||||
.testTag(TestTags.roomDetailAvatar)
|
.testTag(TestTags.roomDetailAvatar)
|
||||||
)
|
)
|
||||||
TitleAndSubtitle(
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
title = roomName,
|
Text(
|
||||||
subtitle = roomAlias?.value,
|
text = roomName,
|
||||||
onSubtitleClick = onSubtitleClick,
|
style = ElementTheme.typography.fontHeadingLgBold,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
|
if (roomAlias != null) {
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.niceClickable { onSubtitleClick(roomAlias.value) },
|
||||||
|
text = roomAlias.value,
|
||||||
|
style = ElementTheme.typography.fontBodyLgRegular,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,37 +545,34 @@ private fun DmHeaderSection(
|
|||||||
}
|
}
|
||||||
.testTag(TestTags.roomDetailAvatar)
|
.testTag(TestTags.roomDetailAvatar)
|
||||||
)
|
)
|
||||||
TitleAndSubtitle(
|
|
||||||
title = roomName,
|
|
||||||
subtitle = otherMember.userId.value,
|
|
||||||
onSubtitleClick = onSubtitleClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun TitleAndSubtitle(
|
|
||||||
title: String,
|
|
||||||
subtitle: String?,
|
|
||||||
onSubtitleClick: (String) -> Unit,
|
|
||||||
) {
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = roomName,
|
||||||
style = ElementTheme.typography.fontHeadingLgBold,
|
style = ElementTheme.typography.fontHeadingLgBold,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
if (subtitle != null) {
|
val userStatus = otherMember.displayedStatus?.toText()
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
if (userStatus != null) {
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.niceClickable { onSubtitleClick(subtitle) },
|
text = userStatus,
|
||||||
text = subtitle,
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
maxLines = 1,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.niceClickable { onSubtitleClick(otherMember.userId.value) },
|
||||||
|
text = otherMember.userId.value,
|
||||||
style = ElementTheme.typography.fontBodyLgRegular,
|
style = ElementTheme.typography.fontBodyLgRegular,
|
||||||
color = ElementTheme.colors.textSecondary,
|
color = ElementTheme.colors.textSecondary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
}
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
@@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.core.UserId
|
|||||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMemberListState> {
|
internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMemberListState> {
|
||||||
@@ -129,6 +130,7 @@ fun aRoomMember(
|
|||||||
role: RoomMember.Role = RoomMember.Role.User,
|
role: RoomMember.Role = RoomMember.Role.User,
|
||||||
membershipChangeReason: String? = null,
|
membershipChangeReason: String? = null,
|
||||||
isServiceMember: Boolean = false,
|
isServiceMember: Boolean = false,
|
||||||
|
displayedStatus: DisplayedStatus? = null,
|
||||||
) = RoomMember(
|
) = RoomMember(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
displayName = displayName,
|
displayName = displayName,
|
||||||
@@ -140,6 +142,7 @@ fun aRoomMember(
|
|||||||
role = role,
|
role = role,
|
||||||
membershipChangeReason = membershipChangeReason,
|
membershipChangeReason = membershipChangeReason,
|
||||||
isServiceMember = isServiceMember,
|
isServiceMember = isServiceMember,
|
||||||
|
displayedStatus = displayedStatus,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aRoomMemberList() = persistentListOf(
|
fun aRoomMemberList() = persistentListOf(
|
||||||
|
|||||||
+1
@@ -128,6 +128,7 @@ class RoomMemberDetailsPresenter(
|
|||||||
return userProfileState.copy(
|
return userProfileState.copy(
|
||||||
userName = roomUserName ?: userProfileState.userName,
|
userName = roomUserName ?: userProfileState.userName,
|
||||||
avatarUrl = roomUserAvatar ?: userProfileState.avatarUrl,
|
avatarUrl = roomUserAvatar ?: userProfileState.avatarUrl,
|
||||||
|
displayedStatus = roomMember?.displayedStatus ?: userProfileState.displayedStatus,
|
||||||
verificationState = verificationState,
|
verificationState = verificationState,
|
||||||
snackbarMessage = snackbarMessage,
|
snackbarMessage = snackbarMessage,
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
|
|||||||
+2
@@ -16,6 +16,7 @@ import io.element.android.features.roommembermoderation.api.RoomMemberModeration
|
|||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
|
import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
|
||||||
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.user.DisplayedStatus
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
@@ -82,6 +83,7 @@ fun anAlice() = MatrixUser(
|
|||||||
userId = UserId(value = "@alice:server.org"),
|
userId = UserId(value = "@alice:server.org"),
|
||||||
displayName = USER_NAME_ALICE,
|
displayName = USER_NAME_ALICE,
|
||||||
avatarUrl = null,
|
avatarUrl = null,
|
||||||
|
displayedStatus = DisplayedStatus.InCall(0L),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aRoomMembersModerationState(
|
fun aRoomMembersModerationState(
|
||||||
|
|||||||
+16
-1
@@ -57,6 +57,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
|||||||
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.model.getAvatarData
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
import io.element.android.libraries.matrix.ui.model.getBestName
|
import io.element.android.libraries.matrix.ui.model.getBestName
|
||||||
|
import io.element.android.libraries.matrix.ui.model.toText
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -264,9 +265,23 @@ private fun RoomMemberActionsBottomSheet(
|
|||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 16.dp, end = 16.dp, bottom = 8.dp)
|
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
val userStatus = user.displayedStatus?.toText()
|
||||||
|
if (userStatus != null) {
|
||||||
|
Text(
|
||||||
|
text = userStatus,
|
||||||
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
maxLines = 1,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
// Show user ID only if it's different from the display name
|
// Show user ID only if it's different from the display name
|
||||||
if (bestName != user.userId.value) {
|
if (bestName != user.userId.value) {
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
+2
@@ -13,6 +13,7 @@ import io.element.android.libraries.architecture.AsyncData
|
|||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||||
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.user.DisplayedStatus
|
||||||
|
|
||||||
data class UserProfileState(
|
data class UserProfileState(
|
||||||
val userId: UserId,
|
val userId: UserId,
|
||||||
@@ -26,6 +27,7 @@ data class UserProfileState(
|
|||||||
val dmRoomId: RoomId?,
|
val dmRoomId: RoomId?,
|
||||||
val canCall: Boolean,
|
val canCall: Boolean,
|
||||||
val snackbarMessage: SnackbarMessage?,
|
val snackbarMessage: SnackbarMessage?,
|
||||||
|
val displayedStatus: DisplayedStatus?,
|
||||||
val eventSink: (UserProfileEvents) -> Unit
|
val eventSink: (UserProfileEvents) -> Unit
|
||||||
) {
|
) {
|
||||||
enum class ConfirmationDialog {
|
enum class ConfirmationDialog {
|
||||||
|
|||||||
+1
@@ -145,6 +145,7 @@ class UserProfilePresenter(
|
|||||||
userId = userId,
|
userId = userId,
|
||||||
userName = userProfile?.displayName,
|
userName = userProfile?.displayName,
|
||||||
avatarUrl = userProfile?.avatarUrl,
|
avatarUrl = userProfile?.avatarUrl,
|
||||||
|
displayedStatus = userProfile?.displayedStatus,
|
||||||
isBlocked = isBlocked.value,
|
isBlocked = isBlocked.value,
|
||||||
verificationState = UserProfileVerificationState.UNKNOWN,
|
verificationState = UserProfileVerificationState.UNKNOWN,
|
||||||
startDmActionState = startDmActionState.value,
|
startDmActionState = startDmActionState.value,
|
||||||
|
|||||||
+16
-1
@@ -42,6 +42,8 @@ import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
|||||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
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.user.DisplayedStatus
|
||||||
|
import io.element.android.libraries.matrix.ui.model.toText
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.libraries.testtags.testTag
|
import io.element.android.libraries.testtags.testTag
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
@@ -56,6 +58,7 @@ fun UserProfileHeaderSection(
|
|||||||
openAvatarPreview: (url: String) -> Unit,
|
openAvatarPreview: (url: String) -> Unit,
|
||||||
onUserIdClick: () -> Unit,
|
onUserIdClick: () -> Unit,
|
||||||
withdrawVerificationClick: () -> Unit,
|
withdrawVerificationClick: () -> Unit,
|
||||||
|
displayedStatus: DisplayedStatus?,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
@@ -90,7 +93,16 @@ fun UserProfileHeaderSection(
|
|||||||
style = ElementTheme.typography.fontHeadingLgBold,
|
style = ElementTheme.typography.fontHeadingLgBold,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
}
|
||||||
|
displayedStatus?.let { status ->
|
||||||
|
Text(
|
||||||
|
text = status.toText(),
|
||||||
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.niceClickable { onUserIdClick() },
|
modifier = Modifier.niceClickable { onUserIdClick() },
|
||||||
@@ -99,6 +111,7 @@ fun UserProfileHeaderSection(
|
|||||||
color = ElementTheme.colors.textSecondary,
|
color = ElementTheme.colors.textSecondary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
when (verificationState) {
|
when (verificationState) {
|
||||||
UserProfileVerificationState.UNKNOWN, UserProfileVerificationState.UNVERIFIED -> Unit
|
UserProfileVerificationState.UNKNOWN, UserProfileVerificationState.UNVERIFIED -> Unit
|
||||||
UserProfileVerificationState.VERIFIED -> {
|
UserProfileVerificationState.VERIFIED -> {
|
||||||
@@ -143,6 +156,7 @@ internal fun UserProfileHeaderSectionPreview() = ElementPreview {
|
|||||||
userId = UserId("@alice:example.com"),
|
userId = UserId("@alice:example.com"),
|
||||||
userName = USER_NAME_ALICE,
|
userName = USER_NAME_ALICE,
|
||||||
verificationState = UserProfileVerificationState.VERIFIED,
|
verificationState = UserProfileVerificationState.VERIFIED,
|
||||||
|
displayedStatus = DisplayedStatus.InCall(0L),
|
||||||
openAvatarPreview = {},
|
openAvatarPreview = {},
|
||||||
onUserIdClick = {},
|
onUserIdClick = {},
|
||||||
withdrawVerificationClick = {},
|
withdrawVerificationClick = {},
|
||||||
@@ -157,6 +171,7 @@ internal fun UserProfileHeaderSectionWithVerificationViolationPreview() = Elemen
|
|||||||
userId = UserId("@alice:example.com"),
|
userId = UserId("@alice:example.com"),
|
||||||
userName = USER_NAME_ALICE,
|
userName = USER_NAME_ALICE,
|
||||||
verificationState = UserProfileVerificationState.VERIFICATION_VIOLATION,
|
verificationState = UserProfileVerificationState.VERIFICATION_VIOLATION,
|
||||||
|
displayedStatus = DisplayedStatus.InCall(0L),
|
||||||
openAvatarPreview = {},
|
openAvatarPreview = {},
|
||||||
onUserIdClick = {},
|
onUserIdClick = {},
|
||||||
withdrawVerificationClick = {},
|
withdrawVerificationClick = {},
|
||||||
|
|||||||
+3
@@ -18,6 +18,7 @@ import io.element.android.libraries.architecture.AsyncData
|
|||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||||
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.user.DisplayedStatus
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||||
|
|
||||||
open class UserProfileStateProvider : PreviewParameterProvider<UserProfileState> {
|
open class UserProfileStateProvider : PreviewParameterProvider<UserProfileState> {
|
||||||
@@ -48,6 +49,7 @@ fun aUserProfileState(
|
|||||||
dmRoomId: RoomId? = null,
|
dmRoomId: RoomId? = null,
|
||||||
canCall: Boolean = false,
|
canCall: Boolean = false,
|
||||||
snackbarMessage: SnackbarMessage? = null,
|
snackbarMessage: SnackbarMessage? = null,
|
||||||
|
displayedStatus: DisplayedStatus? = null,
|
||||||
eventSink: (UserProfileEvents) -> Unit = {},
|
eventSink: (UserProfileEvents) -> Unit = {},
|
||||||
) = UserProfileState(
|
) = UserProfileState(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
@@ -61,5 +63,6 @@ fun aUserProfileState(
|
|||||||
dmRoomId = dmRoomId,
|
dmRoomId = dmRoomId,
|
||||||
canCall = canCall,
|
canCall = canCall,
|
||||||
snackbarMessage = snackbarMessage,
|
snackbarMessage = snackbarMessage,
|
||||||
|
displayedStatus = displayedStatus,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|||||||
+1
@@ -78,6 +78,7 @@ fun UserProfileView(
|
|||||||
userId = state.userId,
|
userId = state.userId,
|
||||||
userName = state.userName,
|
userName = state.userName,
|
||||||
verificationState = state.verificationState,
|
verificationState = state.verificationState,
|
||||||
|
displayedStatus = state.displayedStatus,
|
||||||
openAvatarPreview = { avatarUrl ->
|
openAvatarPreview = { avatarUrl ->
|
||||||
openAvatarPreview(state.userName ?: state.userId.value, avatarUrl)
|
openAvatarPreview(state.userName ?: state.userId.value, avatarUrl)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version
|
|||||||
# https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt
|
# https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt
|
||||||
# All new features should not be implemented in the pull request that upgrades the version, developers should
|
# All new features should not be implemented in the pull request that upgrades the version, developers should
|
||||||
# only fix API breaks and may add some TODOs.
|
# only fix API breaks and may add some TODOs.
|
||||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.07.22"
|
matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.07.23"
|
||||||
|
|
||||||
# Others
|
# Others
|
||||||
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
|
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
|
||||||
@@ -230,7 +230,7 @@ color_picker = "io.mhssn:colorpicker:1.0.0"
|
|||||||
|
|
||||||
# Analytics
|
# Analytics
|
||||||
posthog = "com.posthog:posthog-android:3.51.1"
|
posthog = "com.posthog:posthog-android:3.51.1"
|
||||||
sentry = "io.sentry:sentry-android:8.48.0"
|
sentry = "io.sentry:sentry-android:8.49.0"
|
||||||
# main branch can be tested replacing the version with main-SNAPSHOT
|
# main branch can be tested replacing the version with main-SNAPSHOT
|
||||||
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.37.0"
|
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.37.0"
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ fun MatrixBadgeRowMolecule(
|
|||||||
) {
|
) {
|
||||||
FlowRow(
|
FlowRow(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(start = 16.dp, end = 16.dp, top = 8.dp),
|
.padding(start = 16.dp, end = 16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
|
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user