diff --git a/.github/ISSUE_TEMPLATE/story.yml b/.github/ISSUE_TEMPLATE/story.yml deleted file mode 100644 index 436b92b507..0000000000 --- a/.github/ISSUE_TEMPLATE/story.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: User story issue -description: Second-level planning issue template. A story should take about a week or a sprint to finish. -title: "[Story] " -labels: [T-Story] - -body: -- type: textarea - attributes: - label: Story - description: A story should take roughly a week or a sprint to finish. Each story is usually made up of a number of tasks that take half to a full day. - value: | - As a user… - I want to… - so that I can… - - ## Scope - <!--These should be a list of technical tasks which take ½-1 day to complete--> - ```[tasklist] - ### Tasklist - - [ ] Task 1 - ``` - - - [ ] QA signoff on completion - - [ ] Design signoff on completion - - [ ] Product signoff on completion - - - ## Stretch goals - None at this time - <!--or add a tasklist--> - - ## Out of scope - - - validations: - required: false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 724060aa40..b3408f138b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,7 +38,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.8.0 + uses: gradle/gradle-build-action@v2.8.1 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Assemble debug APK @@ -55,7 +55,7 @@ jobs: name: elementx-debug path: | app/build/outputs/apk/debug/*.apk - - uses: rnkdsh/action-upload-diawi@v1.5.1 + - uses: rnkdsh/action-upload-diawi@v1.5.2 id: diawi # Do not fail the whole build if Diawi upload fails continue-on-error: true diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index f05db3c791..772df369b8 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -11,7 +11,7 @@ jobs: - run: | npm install --save-dev @babel/plugin-transform-flow-strip-types - name: Danger - uses: danger/danger-js@11.2.8 + uses: danger/danger-js@11.3.0 with: args: "--dangerfile ./tools/danger/dangerfile.js" env: diff --git a/.github/workflows/nightlyReports.yml b/.github/workflows/nightlyReports.yml index 124afbc98f..295cd9f1c6 100644 --- a/.github/workflows/nightlyReports.yml +++ b/.github/workflows/nightlyReports.yml @@ -62,7 +62,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.8.0 + uses: gradle/gradle-build-action@v2.8.1 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Dependency analysis diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 417acc9341..6e3efd2ee7 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -40,7 +40,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.8.0 + uses: gradle/gradle-build-action@v2.8.1 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Run code quality check suite @@ -60,7 +60,7 @@ jobs: yarn add danger-plugin-lint-report --dev - name: Danger lint if: always() - uses: danger/danger-js@11.2.8 + uses: danger/danger-js@11.3.0 with: args: "--dangerfile ./tools/danger/dangerfile-lint.js" env: diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml index e9ea93619c..8fadf6b352 100644 --- a/.github/workflows/recordScreenshots.yml +++ b/.github/workflows/recordScreenshots.yml @@ -24,7 +24,7 @@ jobs: java-version: '17' # Add gradle cache, this should speed up the process - name: Configure gradle - uses: gradle/gradle-build-action@v2.8.0 + uses: gradle/gradle-build-action@v2.8.1 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Record screenshots diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cc8fc9055c..bce39b7962 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.8.0 + uses: gradle/gradle-build-action@v2.8.1 - name: Create app bundle env: ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }} diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index b511835a60..1619e8524b 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -32,7 +32,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.8.0 + uses: gradle/gradle-build-action@v2.8.1 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: 🔊 Publish results to Sonar diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f662e5352d..c07ceedf00 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,7 +44,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.8.0 + uses: gradle/gradle-build-action@v2.8.1 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} diff --git a/CHANGES.md b/CHANGES.md index beb480dd45..96fc743ee3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,16 @@ +Changes in Element X v0.2.3 (2023-09-27) +======================================== + +Features ✨ +---------- + - Handle installation of Apks from the media viewer. ([#1432](https://github.com/vector-im/element-x-android/pull/1432)) + - Integrate SDK 0.1.58 ([#1437](https://github.com/vector-im/element-x-android/pull/1437)) + +Other changes +------------- + - Element call: add custom parameters to Element Call urls. ([#1434](https://github.com/vector-im/element-x-android/issues/1434)) + + Changes in Element X v0.2.2 (2023-09-21) ======================================== diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 164c2ae2e4..d1a953ba09 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -54,6 +54,7 @@ import io.element.android.features.verifysession.api.VerifySessionEntryPoint import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.architecture.waitForChildAttached import io.element.android.libraries.deeplink.DeeplinkData import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.di.SessionScope @@ -68,6 +69,7 @@ import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize import timber.log.Timber @@ -304,6 +306,15 @@ class LoggedInFlowNode @AssistedInject constructor( } } + internal suspend fun attachInviteList(deeplinkData: DeeplinkData.InviteList) = withContext(lifecycleScope.coroutineContext) { + notificationDrawerManager.clearMembershipNotificationForSession(deeplinkData.sessionId) + backstack.singleTop(NavTarget.RoomList) + backstack.push(NavTarget.InviteList) + waitForChildAttached<Node, NavTarget> { navTarget -> + navTarget is NavTarget.InviteList + } + } + @Composable override fun View(modifier: Modifier) { Box(modifier = modifier) { @@ -321,13 +332,4 @@ class LoggedInFlowNode @AssistedInject constructor( } } } - - internal suspend fun attachRoom(deeplinkData: DeeplinkData.Room) { - backstack.push(NavTarget.Room(deeplinkData.roomId)) - } - - internal suspend fun attachInviteList(deeplinkData: DeeplinkData.InviteList) { - notificationDrawerManager.clearMembershipNotificationForSession(deeplinkData.sessionId) - backstack.push(NavTarget.InviteList) - } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 60095c17d0..94f344be7e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -234,7 +234,7 @@ class RootFlowNode @AssistedInject constructor( .apply { when (deeplinkData) { is DeeplinkData.Root -> attachRoot() - is DeeplinkData.Room -> attachRoom(deeplinkData) + is DeeplinkData.Room -> attachRoom(deeplinkData.roomId) is DeeplinkData.InviteList -> attachInviteList(deeplinkData) } } diff --git a/fastlane/metadata/android/en-US/changelogs/40002030.txt b/fastlane/metadata/android/en-US/changelogs/40002030.txt new file mode 100644 index 0000000000..ff4f86ce7e --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40002030.txt @@ -0,0 +1,2 @@ +Main changes in this version: bugfixes. +Full changelog: https://github.com/vector-im/element-x-android/releases diff --git a/features/analytics/impl/src/main/res/values-zh-rTW/translations.xml b/features/analytics/impl/src/main/res/values-zh-rTW/translations.xml index 81349674b7..996e5f5f3b 100644 --- a/features/analytics/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/analytics/impl/src/main/res/values-zh-rTW/translations.xml @@ -2,7 +2,7 @@ <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="screen_analytics_prompt_data_usage">"我們不會紀錄或剖繪您的個人資料"</string> <string name="screen_analytics_prompt_help_us_improve">"分享匿名的使用數據以協助我們釐清問題"</string> - <string name="screen_analytics_prompt_read_terms">"您可以到 %1$s 閱讀我們的條款。"</string> + <string name="screen_analytics_prompt_read_terms">"您可以到%1$s閱讀我們的條款。"</string> <string name="screen_analytics_prompt_read_terms_content_link">"這裡"</string> <string name="screen_analytics_prompt_settings">"您可以在任何時候關閉它"</string> <string name="screen_analytics_prompt_third_party_sharing">"我們不會和第三方分享您的資料"</string> diff --git a/features/call/src/main/AndroidManifest.xml b/features/call/src/main/AndroidManifest.xml index d106d4e7b8..877b7fb0a8 100644 --- a/features/call/src/main/AndroidManifest.xml +++ b/features/call/src/main/AndroidManifest.xml @@ -39,7 +39,6 @@ <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> - <data android:scheme="http" /> <data android:scheme="https" /> <data android:host="call.element.io" /> diff --git a/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt b/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt index 30e71d6201..b3e2f9227a 100644 --- a/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt +++ b/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt @@ -21,13 +21,13 @@ import javax.inject.Inject class CallIntentDataParser @Inject constructor() { - private val validHttpSchemes = sequenceOf("http", "https") + private val validHttpSchemes = sequenceOf("https") fun parse(data: String?): String? { val parsedUrl = data?.let { Uri.parse(data) } ?: return null val scheme = parsedUrl.scheme return when { - scheme in validHttpSchemes && parsedUrl.host == "call.element.io" -> data + scheme in validHttpSchemes && parsedUrl.host == "call.element.io" -> parsedUrl scheme == "element" && parsedUrl.host == "call" -> { // We use this custom scheme to load arbitrary URLs for other instances of Element Call, // so we can only verify it's an HTTP/HTTPs URL with a non-empty host @@ -40,14 +40,36 @@ class CallIntentDataParser @Inject constructor() { } // This should never be possible, but we still need to take into account the possibility else -> null - } + }?.withCustomParameters() } - private fun Uri.getUrlParameter(): String? { + private fun Uri.getUrlParameter(): Uri? { return getQueryParameter("url") - ?.takeIf { - val internalUri = Uri.parse(it) - internalUri.scheme in validHttpSchemes && !internalUri.host.isNullOrBlank() + ?.let { urlParameter -> + Uri.parse(urlParameter).takeIf { uri -> + uri.scheme in validHttpSchemes && !uri.host.isNullOrBlank() + } } } } + +/** + * Ensure the uri has the following parameters and value: + * - appPrompt=false + * - confineToRoom=true + * to ensure that the rendering will bo correct on the embedded Webview. + */ +private fun Uri.withCustomParameters(): String { + val builder = buildUpon() + builder.clearQuery() + queryParameterNames.forEach { + if (it == APP_PROMPT_PARAMETER || it == CONFINE_TO_ROOM_PARAMETER) return@forEach + builder.appendQueryParameter(it, getQueryParameter(it)) + } + builder.appendQueryParameter(APP_PROMPT_PARAMETER, "false") + builder.appendQueryParameter(CONFINE_TO_ROOM_PARAMETER, "true") + return builder.build().toString() +} + +private const val APP_PROMPT_PARAMETER = "appPrompt" +private const val CONFINE_TO_ROOM_PARAMETER = "confineToRoom" diff --git a/features/call/src/main/res/values-cs/translations.xml b/features/call/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..dfe6327828 --- /dev/null +++ b/features/call/src/main/res/values-cs/translations.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="call_foreground_service_channel_title_android">"Probíhající hovor"</string> + <string name="call_foreground_service_message_android">"Klepněte pro návrat k hovoru"</string> + <string name="call_foreground_service_title_android">"☎️ Probíhá hovor"</string> +</resources> diff --git a/features/call/src/main/res/values-zh-rTW/translations.xml b/features/call/src/main/res/values-zh-rTW/translations.xml new file mode 100644 index 0000000000..9a6be228ce --- /dev/null +++ b/features/call/src/main/res/values-zh-rTW/translations.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="call_foreground_service_message_android">"點擊以返回到通話頁面"</string> + <string name="call_foreground_service_title_android">"☎️ 通話中"</string> +</resources> diff --git a/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt b/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt index 71290f15b7..aee97ed982 100644 --- a/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt +++ b/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt @@ -52,15 +52,19 @@ class CallIntentDataParserTests { } @Test - fun `Element Call urls will be returned as is`() { + fun `Element Call http urls returns null`() { val httpBaseUrl = "http://call.element.io" val httpCallUrl = "http://call.element.io/some-actual-call?with=parameters" + assertThat(callIntentDataParser.parse(httpBaseUrl)).isNull() + assertThat(callIntentDataParser.parse(httpCallUrl)).isNull() + } + + @Test + fun `Element Call urls will be returned as is`() { val httpsBaseUrl = "https://call.element.io" - val httpsCallUrl = "https://call.element.io/some-actual-call?with=parameters" - assertThat(callIntentDataParser.parse(httpBaseUrl)).isEqualTo(httpBaseUrl) - assertThat(callIntentDataParser.parse(httpCallUrl)).isEqualTo(httpCallUrl) - assertThat(callIntentDataParser.parse(httpsBaseUrl)).isEqualTo(httpsBaseUrl) - assertThat(callIntentDataParser.parse(httpsCallUrl)).isEqualTo(httpsCallUrl) + val httpsCallUrl = VALID_CALL_URL_WITH_PARAM + assertThat(callIntentDataParser.parse(httpsBaseUrl)).isEqualTo("$httpsBaseUrl?$EXTRA_PARAMS") + assertThat(callIntentDataParser.parse(httpsCallUrl)).isEqualTo("$httpsCallUrl&$EXTRA_PARAMS") } @Test @@ -76,19 +80,35 @@ class CallIntentDataParserTests { } @Test - fun `element scheme with call host and url param gets url extracted`() { + fun `element scheme with call host and url with http will returns null`() { val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters" val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "element://call?url=$encodedUrl" - assertThat(callIntentDataParser.parse(url)).isEqualTo(embeddedUrl) + assertThat(callIntentDataParser.parse(url)).isNull() } @Test - fun `element scheme 2 with url param gets url extracted`() { + fun `element scheme with call host and url param gets url extracted`() { + val embeddedUrl = VALID_CALL_URL_WITH_PARAM + val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") + val url = "element://call?url=$encodedUrl" + assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS") + } + + @Test + fun `element scheme 2 with url param with http returns null`() { val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters" val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "io.element.call:/?url=$encodedUrl" - assertThat(callIntentDataParser.parse(url)).isEqualTo(embeddedUrl) + assertThat(callIntentDataParser.parse(url)).isNull() + } + + @Test + fun `element scheme 2 with url param gets url extracted`() { + val embeddedUrl = VALID_CALL_URL_WITH_PARAM + val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") + val url = "io.element.call:/?url=$encodedUrl" + assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS") } @Test @@ -101,7 +121,7 @@ class CallIntentDataParserTests { @Test fun `element scheme 2 with no url returns null`() { - val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = VALID_CALL_URL_WITH_PARAM val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "io.element.call:/?no_url=$encodedUrl" assertThat(callIntentDataParser.parse(url)).isNull() @@ -109,7 +129,7 @@ class CallIntentDataParserTests { @Test fun `element scheme with no call host returns null`() { - val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = VALID_CALL_URL_WITH_PARAM val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "element://no-call?url=$encodedUrl" assertThat(callIntentDataParser.parse(url)).isNull() @@ -129,9 +149,39 @@ class CallIntentDataParserTests { @Test fun `element invalid scheme returns null`() { - val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = VALID_CALL_URL_WITH_PARAM val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "bad.scheme:/?url=$encodedUrl" assertThat(callIntentDataParser.parse(url)).isNull() } + + @Test + fun `element scheme 2 with url extra param appPrompt gets url extracted`() { + val embeddedUrl = "${VALID_CALL_URL_WITH_PARAM}&appPrompt=true" + val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") + val url = "io.element.call:/?url=$encodedUrl" + assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS") + } + + @Test + fun `element scheme 2 with url extra param confineToRoom gets url extracted`() { + val embeddedUrl = "${VALID_CALL_URL_WITH_PARAM}&confineToRoom=false" + val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") + val url = "io.element.call:/?url=$encodedUrl" + assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS") + } + + @Test + fun `element scheme 2 with url fragment gets url extracted`() { + val embeddedUrl = "${VALID_CALL_URL_WITH_PARAM}#fragment" + val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") + val url = "io.element.call:/?url=$encodedUrl" + assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS#fragment") + } + + + companion object { + const val VALID_CALL_URL_WITH_PARAM = "https://call.element.io/some-actual-call?with=parameters" + const val EXTRA_PARAMS = "appPrompt=false&confineToRoom=true" + } } diff --git a/features/ftue/impl/src/main/res/values-cs/translations.xml b/features/ftue/impl/src/main/res/values-cs/translations.xml index 724f3793cd..9363c704ad 100644 --- a/features/ftue/impl/src/main/res/values-cs/translations.xml +++ b/features/ftue/impl/src/main/res/values-cs/translations.xml @@ -2,6 +2,8 @@ <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="screen_migration_message">"Jedná se o jednorázový proces, prosíme o strpení."</string> <string name="screen_migration_title">"Nastavení vašeho účtu"</string> + <string name="screen_notification_optin_subtitle">"Nastavení můžete později změnit."</string> + <string name="screen_notification_optin_title">"Povolte oznámení a nezmeškejte žádnou zprávu"</string> <string name="screen_welcome_bullet_1">"Hovory, hlasování, vyhledávání a další budou přidány koncem tohoto roku."</string> <string name="screen_welcome_bullet_2">"Historie zpráv šifrovaných místností nebude v této aktualizaci k dispozici."</string> <string name="screen_welcome_bullet_3">"Rádi bychom se od vás dozvěděli, co si o tom myslíte, dejte nám vědět prostřednictvím stránky s nastavením."</string> diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt index ead0b98228..f4443e92f4 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt @@ -21,7 +21,7 @@ import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -64,7 +64,7 @@ fun StaticMapView( contentAlignment = Alignment.Center ) { val context = LocalContext.current - var retryHash by remember { mutableStateOf(0) } + var retryHash by remember { mutableIntStateOf(0) } val builder = remember { StaticMapUrlBuilder(context) } val painter = rememberAsyncImagePainter( model = if (constraints.isZero) { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt index 9c07204ab2..039986343b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt @@ -18,6 +18,7 @@ package io.element.android.features.login.impl.screens.waitlistscreen import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -58,14 +59,14 @@ class WaitListPresenter @AssistedInject constructor( mutableStateOf(Async.Uninitialized) } - val attemptNumber: MutableState<Int> = remember { mutableStateOf(0) } + val attemptNumber = remember { mutableIntStateOf(0) } fun handleEvents(event: WaitListEvents) { when (event) { WaitListEvents.AttemptLogin -> { // Do not attempt to login on first resume of the View. - attemptNumber.value++ - if (attemptNumber.value > 1) { + attemptNumber.intValue++ + if (attemptNumber.intValue > 1) { coroutineScope.loginAttempt(formState, loginAction) } } diff --git a/features/login/impl/src/main/res/values-ru/translations.xml b/features/login/impl/src/main/res/values-ru/translations.xml index 3ecb8a52d0..641db1e0d4 100644 --- a/features/login/impl/src/main/res/values-ru/translations.xml +++ b/features/login/impl/src/main/res/values-ru/translations.xml @@ -14,9 +14,9 @@ <string name="screen_change_account_provider_subtitle">"Используйте другого поставщика учетных записей, например, собственный частный сервер или рабочую учетную запись."</string> <string name="screen_change_account_provider_title">"Сменить поставщика учетной записи"</string> <string name="screen_change_server_error_invalid_homeserver">"Нам не удалось связаться с этим домашним сервером. Убедитесь, что вы правильно ввели URL-адрес домашнего сервера. Если URL-адрес указан правильно, обратитесь к администратору домашнего сервера за дополнительной помощью."</string> - <string name="screen_change_server_error_no_sliding_sync_message">"В настоящее время этот сервер не поддерживает скользящую синхронизацию."</string> + <string name="screen_change_server_error_no_sliding_sync_message">"К сожалению данный сервер не поддерживает sliding sync."</string> <string name="screen_change_server_form_header">"URL-адрес домашнего сервера"</string> - <string name="screen_change_server_form_notice">"Вы можете подключиться только к существующему серверу, поддерживающему скользящую синхронизацию. Администратору домашнего сервера потребуется настроить его. %1$s"</string> + <string name="screen_change_server_form_notice">"Вы можете подключиться только к существующему серверу, поддерживающему sliding sync. Администратору домашнего сервера потребуется настроить его. %1$s"</string> <string name="screen_change_server_subtitle">"Какой адрес у вашего сервера?"</string> <string name="screen_login_error_deactivated_account">"Данная учетная запись была деактивирована."</string> <string name="screen_login_error_invalid_credentials">"Неверное имя пользователя и/или пароль"</string> diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 1dc46aa2bd..c99c0372a8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -30,7 +30,6 @@ import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import im.vector.app.features.analytics.plan.PollEnd import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction @@ -39,6 +38,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer import io.element.android.features.messages.impl.messagecomposer.MessageComposerState import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelinePresenter +import io.element.android.features.messages.impl.timeline.TimelineState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryPresenter import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuPresenter @@ -76,7 +76,6 @@ import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType import io.element.android.libraries.matrix.ui.room.canRedactAsState import io.element.android.libraries.matrix.ui.room.canSendMessageAsState import io.element.android.libraries.textcomposer.MessageComposerMode -import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -95,7 +94,6 @@ class MessagesPresenter @AssistedInject constructor( private val messageSummaryFormatter: MessageSummaryFormatter, private val dispatchers: CoroutineDispatchers, private val clipboardHelper: ClipboardHelper, - private val analyticsService: AnalyticsService, private val preferencesStore: PreferencesStore, @Assisted private val navigator: MessagesNavigator, ) : Presenter<MessagesState> { @@ -155,6 +153,7 @@ class MessagesPresenter @AssistedInject constructor( targetEvent = event.event, composerState = composerState, enableTextFormatting = enableTextFormatting, + timelineState = timelineState, ) } is MessagesEvents.ToggleReaction -> { @@ -206,6 +205,7 @@ class MessagesPresenter @AssistedInject constructor( targetEvent: TimelineItem.Event, composerState: MessageComposerState, enableTextFormatting: Boolean, + timelineState: TimelineState, ) = launch { when (action) { TimelineItemAction.Copy -> handleCopyContents(targetEvent) @@ -216,7 +216,7 @@ class MessagesPresenter @AssistedInject constructor( TimelineItemAction.ViewSource -> handleShowDebugInfoAction(targetEvent) TimelineItemAction.Forward -> handleForwardAction(targetEvent) TimelineItemAction.ReportContent -> handleReportAction(targetEvent) - TimelineItemAction.EndPoll -> handleEndPollAction(targetEvent) + TimelineItemAction.EndPoll -> handleEndPollAction(targetEvent, timelineState) } } @@ -266,7 +266,7 @@ class MessagesPresenter @AssistedInject constructor( targetEvent: TimelineItem.Event, composerState: MessageComposerState, enableTextFormatting: Boolean, - ) { + ) { val composerMode = MessageComposerMode.Edit( targetEvent.eventId, (targetEvent.content as? TimelineItemTextBasedContent)?.let { @@ -344,11 +344,11 @@ class MessagesPresenter @AssistedInject constructor( navigator.onReportContentClicked(event.eventId, event.senderId) } - private suspend fun handleEndPollAction(event: TimelineItem.Event) { - event.eventId?.let { - room.endPoll(it, "The poll with event id: $it has ended.") - analyticsService.capture(PollEnd()) - } + private fun handleEndPollAction( + event: TimelineItem.Event, + timelineState: TimelineState, + ) { + event.eventId?.let { timelineState.eventSink(TimelineEvents.PollEndClicked(it)) } } private suspend fun handleCopyContents(event: TimelineItem.Event) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt index 588adb1020..c9e485b88a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt @@ -108,7 +108,7 @@ class ActionListPresenter @Inject constructor( buildList { val isMineOrCanRedact = timelineItem.isMine || userCanRedact - // TODO Poll: Reply to poll. Ensure to update `fun TimelineItemEventContent.canBeReplied()` + // TODO Polls: Reply to poll. Ensure to update `fun TimelineItemEventContent.canBeReplied()` // when touching this // if (timelineItem.isRemote) { // // Can only reply or forward messages already uploaded to the server diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt index 44bff4f5ab..ef93574134 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt @@ -16,6 +16,7 @@ package io.element.android.features.messages.impl.media.local +import android.app.Activity import android.content.ContentResolver import android.content.ContentValues import android.content.Context @@ -24,17 +25,25 @@ import android.net.Uri import android.os.Build import android.os.Environment import android.provider.MediaStore +import androidx.activity.compose.ManagedActivityResultLauncher +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext import androidx.core.content.FileProvider import androidx.core.net.toFile import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.androidutils.system.startInstallFromSourceIntent import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber import java.io.File @@ -50,10 +59,27 @@ class AndroidLocalMediaActions @Inject constructor( ) : LocalMediaActions { private var activityContext: Context? = null + private var apkInstallLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>? = null + private var pendingMedia: LocalMedia? = null @Composable override fun Configure() { val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() + apkInstallLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult(), + ) { activityResult -> + if (activityResult.resultCode == Activity.RESULT_OK) { + pendingMedia?.let { + coroutineScope.launch { + openFile(it) + } + } + } else { + // User cancelled + } + pendingMedia = null + } return DisposableEffect(Unit) { activityContext = context onDispose { @@ -99,11 +125,20 @@ class AndroidLocalMediaActions @Inject constructor( override suspend fun open(localMedia: LocalMedia): Result<Unit> = withContext(coroutineDispatchers.io) { require(localMedia.uri.scheme == ContentResolver.SCHEME_FILE) runCatching { - val openMediaIntent = Intent(Intent.ACTION_VIEW) - .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - .setDataAndType(localMedia.toShareableUri(), localMedia.info.mimeType) - withContext(coroutineDispatchers.main) { - activityContext!!.startActivity(openMediaIntent) + when (localMedia.info.mimeType) { + MimeTypes.Apk -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (activityContext?.packageManager?.canRequestPackageInstalls() == false) { + pendingMedia = localMedia + activityContext?.startInstallFromSourceIntent(apkInstallLauncher!!).let { } + } else { + openFile(localMedia) + } + } else { + openFile(localMedia) + } + } + else -> openFile(localMedia) } }.onSuccess { Timber.v("Open media succeed") @@ -112,6 +147,15 @@ class AndroidLocalMediaActions @Inject constructor( } } + private suspend fun openFile(localMedia: LocalMedia) { + val openMediaIntent = Intent(Intent.ACTION_VIEW) + .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + .setDataAndType(localMedia.toShareableUri(), localMedia.info.mimeType) + withContext(coroutineDispatchers.main) { + activityContext?.startActivity(openMediaIntent) + } + } + private fun LocalMedia.toShareableUri(): Uri { val mediaAsFile = this.toFile() val authority = "${buildMeta.applicationId}.fileprovider" diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt index 13a9ff3bee..b295f68e11 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -59,7 +60,7 @@ class MediaViewerPresenter @AssistedInject constructor( @Composable override fun present(): MediaViewerState { val coroutineScope = rememberCoroutineScope() - var loadMediaTrigger by remember { mutableStateOf(0) } + var loadMediaTrigger by remember { mutableIntStateOf(0) } val mediaFile: MutableState<MediaFile?> = remember { mutableStateOf(null) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt index 7ab52216fe..b0570a6f2a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt @@ -47,11 +47,13 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import coil.compose.AsyncImage +import io.element.android.features.messages.impl.R import io.element.android.features.messages.impl.media.local.LocalMedia import io.element.android.features.messages.impl.media.local.LocalMediaView import io.element.android.features.messages.impl.media.local.MediaInfo import io.element.android.features.messages.impl.media.local.rememberLocalMediaViewState import io.element.android.libraries.architecture.Async +import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.dialogs.RetryDialog import io.element.android.libraries.designsystem.preview.ElementPreviewDark @@ -92,6 +94,7 @@ fun MediaViewerView( topBar = { MediaViewerTopBar( actionsEnabled = state.downloadedMedia is Async.Success, + mimeType = state.mediaInfo.mimeType, onBackPressed = onBackPressed, eventSink = state.eventSink ) @@ -162,6 +165,7 @@ private fun rememberShowProgress(downloadedMedia: Async<LocalMedia>): Boolean { @Composable private fun MediaViewerTopBar( actionsEnabled: Boolean, + mimeType: String, onBackPressed: () -> Unit, eventSink: (MediaViewerEvents) -> Unit, ) { @@ -175,10 +179,16 @@ private fun MediaViewerTopBar( eventSink(MediaViewerEvents.OpenWith) }, ) { - Icon( - imageVector = Icons.Default.OpenInNew, - contentDescription = stringResource(id = CommonStrings.action_open_with) - ) + when (mimeType) { + MimeTypes.Apk -> Icon( + resourceId = R.drawable.ic_apk_install, + contentDescription = stringResource(id = CommonStrings.common_install_apk_android) + ) + else -> Icon( + imageVector = Icons.Default.OpenInNew, + contentDescription = stringResource(id = CommonStrings.action_open_with) + ) + } } IconButton( enabled = actionsEnabled, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 6898d7796f..d6e74a0df0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -154,12 +154,10 @@ class MessageComposerPresenter @Inject constructor( fun handleEvents(event: MessageComposerEvents) { when (event) { MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value - MessageComposerEvents.CloseSpecialMode -> { richTextEditorState.setHtml("") messageComposerContext.composerMode = MessageComposerMode.Normal("") } - is MessageComposerEvents.SendMessage -> appCoroutineScope.sendMessage( message = event.message, updateComposerMode = { messageComposerContext.composerMode = it }, @@ -167,6 +165,11 @@ class MessageComposerPresenter @Inject constructor( ) is MessageComposerEvents.SetMode -> { messageComposerContext.composerMode = event.composerMode + if (event.composerMode is MessageComposerMode.Reply) { + appCoroutineScope.launch { + room.enterReplyMode(event.composerMode.eventId) + } + } } MessageComposerEvents.AddAttachment -> localCoroutineScope.launch { showAttachmentSourcePicker = true diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt index e427646a71..ca23904583 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt @@ -26,4 +26,8 @@ sealed interface TimelineEvents { val pollStartId: EventId, val answerId: String ) : TimelineEvents + + data class PollEndClicked( + val pollStartId: EventId, + ) : TimelineEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index a7e86d341f..0d0fafd17d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable +import im.vector.app.features.analytics.plan.PollEnd import im.vector.app.features.analytics.plan.PollVote import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.model.TimelineItem @@ -98,11 +99,18 @@ class TimelinePresenter @Inject constructor( ) analyticsService.capture(PollVote()) } + is TimelineEvents.PollEndClicked -> appScope.launch { + room.endPoll( + pollStartId = event.pollStartId, + text = "The poll with event id: ${event.pollStartId} has ended." + ) + analyticsService.capture(PollEnd()) + } } } LaunchedEffect(timelineItems.size) { - computeHasNewItems(timelineItems, prevMostRecentItemId, hasNewItems) + computeHasNewItems(timelineItems, prevMostRecentItemId, hasNewItems) } LaunchedEffect(Unit) { @@ -123,7 +131,7 @@ class TimelinePresenter @Inject constructor( paginationState = paginationState, timelineItems = timelineItems, hasNewItems = hasNewItems.value, - eventSink = { handleEvents(it) } + eventSink = ::handleEvents ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index 42f529a899..ba02b8eb95 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -103,10 +103,6 @@ fun TimelineView( // TODO implement this logic once we have support to 'jump to event X' in sliding sync } - fun onPollAnswerSelected(pollStartId: EventId, answerId: String) { - state.eventSink(TimelineEvents.PollAnswerSelected(pollStartId, answerId)) - } - // Animate alpha when timeline is first displayed, to avoid flashes or glitching when viewing rooms val alpha by alphaAnimation(label = "alpha for timeline") @@ -134,7 +130,7 @@ fun TimelineView( onReactionLongClick = onReactionLongClicked, onMoreReactionsClick = onMoreReactionsClicked, onTimestampClicked = onTimestampClicked, - onPollAnswerSelected = ::onPollAnswerSelected, + eventSink = state.eventSink, onSwipeToReply = onSwipeToReply, ) } @@ -172,7 +168,7 @@ fun TimelineItemRow( onMoreReactionsClick: (TimelineItem.Event) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, onSwipeToReply: (TimelineItem.Event) -> Unit, - onPollAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, + eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier ) { when (timelineItem) { @@ -189,6 +185,7 @@ fun TimelineItemRow( isHighlighted = highlightedItem == timelineItem.identifier(), onClick = { onClick(timelineItem) }, onLongClick = { onLongClick(timelineItem) }, + eventSink = eventSink, modifier = modifier, ) } else { @@ -205,7 +202,7 @@ fun TimelineItemRow( onMoreReactionsClick = onMoreReactionsClick, onTimestampClicked = onTimestampClicked, onSwipeToReply = { onSwipeToReply(timelineItem) }, - onPollAnswerSelected = onPollAnswerSelected, + eventSink = eventSink, modifier = modifier, ) } @@ -243,7 +240,7 @@ fun TimelineItemRow( onReactionClick = onReactionClick, onReactionLongClick = onReactionLongClick, onMoreReactionsClick = onMoreReactionsClick, - onPollAnswerSelected = onPollAnswerSelected, + eventSink = eventSink, onSwipeToReply = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 44c5d23600..4ab98ce953 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -61,6 +61,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.constraintlayout.compose.ConstrainScope import androidx.constraintlayout.compose.ConstraintLayout +import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView @@ -80,9 +81,9 @@ import io.element.android.libraries.designsystem.colors.AvatarColorsProvider import io.element.android.libraries.designsystem.components.EqualWidthColumn import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.swipe.SwipeableActionsState import io.element.android.libraries.designsystem.swipe.rememberSwipeableActionsState import io.element.android.libraries.designsystem.text.toPx @@ -123,7 +124,7 @@ fun TimelineItemEventRow( onReactionLongClick: (emoji: String, eventId: TimelineItem.Event) -> Unit, onMoreReactionsClick: (eventId: TimelineItem.Event) -> Unit, onSwipeToReply: () -> Unit, - onPollAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, + eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier ) { val coroutineScope = rememberCoroutineScope() @@ -146,7 +147,7 @@ fun TimelineItemEventRow( } if (canReply) { val state: SwipeableActionsState = rememberSwipeableActionsState() - val offset = state.offset.value + val offset = state.offset.floatValue val swipeThresholdPx = 40.dp.toPx() val thresholdCrossed = abs(offset) > swipeThresholdPx SwipeSensitivity(3f) { @@ -181,7 +182,7 @@ fun TimelineItemEventRow( onReactionClicked = { emoji -> onReactionClick(emoji, event) }, onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) }, onMoreReactionsClicked = { onMoreReactionsClick(event) }, - onPollAnswerSelected = onPollAnswerSelected, + eventSink = eventSink, ) } } @@ -198,7 +199,7 @@ fun TimelineItemEventRow( onReactionClicked = { emoji -> onReactionClick(emoji, event) }, onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) }, onMoreReactionsClicked = { onMoreReactionsClick(event) }, - onPollAnswerSelected = onPollAnswerSelected, + eventSink = eventSink, ) } } @@ -240,7 +241,7 @@ private fun TimelineItemEventRowContent( onReactionClicked: (emoji: String) -> Unit, onReactionLongClicked: (emoji: String) -> Unit, onMoreReactionsClicked: (event: TimelineItem.Event) -> Unit, - onPollAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, + eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier, ) { fun ConstrainScope.linkStartOrEnd(event: TimelineItem.Event) = if (event.isMine) { @@ -299,7 +300,7 @@ private fun TimelineItemEventRowContent( onTimestampClicked = { onTimestampClicked(event) }, - onPollAnswerSelected = onPollAnswerSelected, + eventSink = eventSink, ) } @@ -371,7 +372,7 @@ private fun MessageEventBubbleContent( onMessageLongClick: () -> Unit, inReplyToClick: () -> Unit, onTimestampClicked: () -> Unit, - onPollAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, + eventSink: (TimelineEvents) -> Unit, @SuppressLint("ModifierParameter") bubbleModifier: Modifier = Modifier, // need to rename this modifier to distinguish it from the following ones ) { @@ -385,11 +386,12 @@ private fun MessageEventBubbleContent( ) { TimelineItemEventContentView( content = event.content, + isMine = event.isMine, interactionSource = interactionSource, onClick = onMessageClick, onLongClick = onMessageLongClick, extraPadding = event.toExtraPadding(), - onPollAnswerSelected = onPollAnswerSelected, + eventSink = eventSink, modifier = modifier, ) } @@ -652,7 +654,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview { onMoreReactionsClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) TimelineItemEventRow( event = aTimelineItemEvent( @@ -673,7 +675,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview { onMoreReactionsClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) } } @@ -712,7 +714,7 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview { onMoreReactionsClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) TimelineItemEventRow( event = aTimelineItemEvent( @@ -735,7 +737,7 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview { onMoreReactionsClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) } } @@ -786,7 +788,7 @@ internal fun TimelineItemEventRowTimestampPreview( onMoreReactionsClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) } } @@ -818,7 +820,7 @@ internal fun TimelineItemEventRowWithManyReactionsPreview() = ElementPreview { onMoreReactionsClick = {}, onSwipeToReply = {}, onTimestampClicked = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) } } @@ -843,7 +845,7 @@ internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight { onMoreReactionsClick = {}, onSwipeToReply = {}, onTimestampClicked = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) } @@ -864,6 +866,6 @@ internal fun TimelineItemEventTimestampBelowPreview() = ElementPreviewLight { onMoreReactionsClick = {}, onSwipeToReply = {}, onTimestampClicked = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt index d782a36dcf..ccffcc16ca 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex +import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.event.noExtraPadding @@ -35,8 +36,8 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.util.defaultTimelineContentPadding -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight @Composable fun TimelineItemStateEventRow( @@ -44,6 +45,7 @@ fun TimelineItemStateEventRow( isHighlighted: Boolean, onClick: () -> Unit, onLongClick: () -> Unit, + eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier ) { val interactionSource = remember { MutableInteractionSource() } @@ -65,11 +67,12 @@ fun TimelineItemStateEventRow( ) { TimelineItemEventContentView( content = event.content, + isMine = event.isMine, interactionSource = interactionSource, onClick = onClick, onLongClick = onLongClick, extraPadding = noExtraPadding, - onPollAnswerSelected = { _, _ -> error("Polls are not supported in state events") }, + eventSink = eventSink, modifier = Modifier.defaultTimelineContentPadding() ) } @@ -88,5 +91,6 @@ internal fun TimelineItemStateEventRowPreview() = ElementPreview { isHighlighted = false, onClick = {}, onLongClick = {}, + eventSink = {} ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt index e882950d6c..dccc020e49 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt @@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent @@ -31,16 +32,16 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent -import io.element.android.libraries.matrix.api.core.EventId @Composable fun TimelineItemEventContentView( content: TimelineItemEventContent, + isMine: Boolean, interactionSource: MutableInteractionSource, extraPadding: ExtraPadding, onClick: () -> Unit, onLongClick: () -> Unit, - onPollAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, + eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier ) { when (content) { @@ -95,7 +96,8 @@ fun TimelineItemEventContentView( ) is TimelineItemPollContent -> TimelineItemPollView( content = content, - onAnswerSelected = onPollAnswerSelected, + isMine = isMine, + eventSink = eventSink, modifier = modifier, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt index 2943bfc1d5..958845f98c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt @@ -19,27 +19,40 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContentProvider import io.element.android.features.poll.api.PollContentView -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.api.core.EventId import kotlinx.collections.immutable.toImmutableList @Composable fun TimelineItemPollView( content: TimelineItemPollContent, - onAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, + isMine: Boolean, + eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier, ) { + fun onAnswerSelected(pollStartId: EventId, answerId: String) { + eventSink(TimelineEvents.PollAnswerSelected(pollStartId, answerId)) + } + + fun onPollEnd(pollStartId: EventId) { + eventSink(TimelineEvents.PollEndClicked(pollStartId)) + } + PollContentView( eventId = content.eventId, question = content.question, answerItems = content.answerItems.toImmutableList(), pollKind = content.pollKind, isPollEnded = content.isEnded, - onAnswerSelected = onAnswerSelected, + isMine = isMine, + onAnswerSelected = ::onAnswerSelected, + onPollEdit = {}, // TODO Polls: Wire up this callback once poll edit screen is done. + onPollEnd = ::onPollEnd, modifier = modifier, ) } @@ -50,6 +63,18 @@ internal fun TimelineItemPollViewPreview(@PreviewParameter(TimelineItemPollConte ElementPreview { TimelineItemPollView( content = content, - onAnswerSelected = { _, _ -> }, + isMine = false, + eventSink = {}, + ) + } + +@PreviewsDayNight +@Composable +internal fun TimelineItemPollCreatorViewPreview(@PreviewParameter(TimelineItemPollContentProvider::class) content: TimelineItemPollContent) = + ElementPreview { + TimelineItemPollView( + content = content, + isMine = true, + eventSink = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index 040969e092..3d0dc4d80c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -109,14 +109,17 @@ class TimelineItemContentMessageFactory @Inject constructor( formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0), fileExtension = fileExtensionExtractor.extractFromName(messageType.body) ) - is FileMessageType -> TimelineItemFileContent( - body = messageType.body, - thumbnailSource = messageType.info?.thumbnailSource, - fileSource = messageType.source, - mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, - formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0), - fileExtension = fileExtensionExtractor.extractFromName(messageType.body) - ) + is FileMessageType -> { + val fileExtension = fileExtensionExtractor.extractFromName(messageType.body) + TimelineItemFileContent( + body = messageType.body, + thumbnailSource = messageType.info?.thumbnailSource, + fileSource = messageType.source, + mimeType = messageType.info?.mimetype ?: MimeTypes.fromFileExtension(fileExtension), + formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0), + fileExtension = fileExtension + ) + } is NoticeMessageType -> TimelineItemNoticeContent( body = messageType.body, htmlDocument = messageType.formatted?.toHtmlDocument(), diff --git a/features/messages/impl/src/main/res/drawable/ic_apk_install.xml b/features/messages/impl/src/main/res/drawable/ic_apk_install.xml new file mode 100644 index 0000000000..b39fc4c5d5 --- /dev/null +++ b/features/messages/impl/src/main/res/drawable/ic_apk_install.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M160,880Q127,880 103.5,856.5Q80,833 80,800L80,160Q80,127 103.5,103.5Q127,80 160,80L480,80L720,320L720,490L640,490L640,360L440,360L440,160L160,160Q160,160 160,160Q160,160 160,160L160,800Q160,800 160,800Q160,800 160,800L600,800L600,880L160,880ZM160,800L160,490L160,490L160,360L160,160L160,160Q160,160 160,160Q160,160 160,160L160,800Q160,800 160,800Q160,800 160,800L160,800ZM200,760Q204,711 230,670Q256,629 298,605L260,537Q260,536 264,522Q269,520 273.5,520Q278,520 280,525L319,595Q339,587 359,582.5Q379,578 400,578Q421,578 441,582.5Q461,587 481,595L520,525Q520,525 535,521Q540,523 541,528Q542,533 540,537L502,605Q544,629 570,670Q596,711 600,760L200,760ZM310,700Q318,700 324,694Q330,688 330,680Q330,672 324,666Q318,660 310,660Q302,660 296,666Q290,672 290,680Q290,688 296,694Q302,700 310,700ZM490,700Q498,700 504,694Q510,688 510,680Q510,672 504,666Q498,660 490,660Q482,660 476,666Q470,672 470,680Q470,688 476,694Q482,700 490,700ZM800,880L640,720L696,663L760,726L760,560L840,560L840,726L904,663L960,720L800,880Z"/> +</vector> diff --git a/features/messages/impl/src/main/res/values-cs/translations.xml b/features/messages/impl/src/main/res/values-cs/translations.xml index 1543101f6d..906ef6b987 100644 --- a/features/messages/impl/src/main/res/values-cs/translations.xml +++ b/features/messages/impl/src/main/res/values-cs/translations.xml @@ -12,6 +12,7 @@ <string name="screen_room_attachment_source_gallery">"Knihovna fotografií a videí"</string> <string name="screen_room_attachment_source_location">"Poloha"</string> <string name="screen_room_attachment_source_poll">"Hlasování"</string> + <string name="screen_room_attachment_text_formatting">"Formátování textu"</string> <string name="screen_room_encrypted_history_banner">"Historie zpráv je momentálně v této místnosti nedostupná"</string> <string name="screen_room_error_failed_retrieving_user_details">"Nepodařilo se načíst údaje o uživateli"</string> <string name="screen_room_invite_again_alert_message">"Chtěli byste je pozvat zpět?"</string> diff --git a/features/messages/impl/src/main/res/values-zh-rTW/translations.xml b/features/messages/impl/src/main/res/values-zh-rTW/translations.xml index ac4725896e..1038508ac6 100644 --- a/features/messages/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/messages/impl/src/main/res/values-zh-rTW/translations.xml @@ -9,6 +9,7 @@ <string name="screen_room_attachment_source_files">"附件"</string> <string name="screen_room_attachment_source_location">"位置"</string> <string name="screen_room_attachment_source_poll">"投票"</string> + <string name="screen_room_attachment_text_formatting">"格式化文字"</string> <string name="screen_room_invite_again_alert_title">"此聊天室只有您一個人"</string> <string name="screen_room_message_copied">"訊息已複製"</string> <string name="screen_room_no_permission_to_post">"您沒有權限在此聊天室傳送訊息"</string> @@ -17,8 +18,11 @@ <string name="screen_room_notification_settings_error_restoring_default">"無法重設為預設模式,請再試一次。"</string> <string name="screen_room_notification_settings_error_setting_mode">"無法設定模式,請再試一次。"</string> <string name="screen_room_notification_settings_mode_all_messages">"所有訊息"</string> - <string name="screen_room_notification_settings_mode_mentions_and_keywords">"只限提及與關鍵字"</string> + <string name="screen_room_notification_settings_mode_mentions_and_keywords">"僅限提及與關鍵字"</string> + <string name="screen_room_reactions_show_less">"較少"</string> + <string name="screen_room_reactions_show_more">"更多"</string> <string name="screen_room_retry_send_menu_send_again_action">"重傳"</string> <string name="screen_room_retry_send_menu_title">"無法傳送您的訊息"</string> + <string name="screen_room_timeline_less_reactions">"較少"</string> <string name="screen_room_retry_send_menu_remove_action">"移除"</string> </resources> diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt index 07662e4bcc..9bbb514117 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt @@ -644,7 +644,6 @@ class MessagesPresenterTest { messageSummaryFormatter = FakeMessageSummaryFormatter(), navigator = navigator, clipboardHelper = clipboardHelper, - analyticsService = analyticsService, preferencesStore = preferencesStore, dispatchers = coroutineDispatchers, ) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt index 0435be3cb1..ec83b86cd9 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt @@ -20,7 +20,9 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.plan.PollEnd import im.vector.app.features.analytics.plan.PollVote +import io.element.android.features.messages.fixtures.aMessageEvent import io.element.android.features.messages.fixtures.aTimelineItemsFactory import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelinePresenter @@ -42,6 +44,7 @@ import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.awaitWithLatch import io.element.android.tests.testutils.testCoroutineDispatchers +import io.element.android.tests.testutils.waitForPredicate import kotlinx.coroutines.delay import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -280,6 +283,29 @@ class TimelinePresenterTest { assertThat(analyticsService.capturedEvents.last()).isEqualTo(PollVote()) } + @Test + fun `present - PollEndClicked event calls into rust room api and analytics`() = runTest { + val room = FakeMatrixRoom() + val analyticsService = FakeAnalyticsService() + val presenter = createTimelinePresenter( + room = room, + analyticsService = analyticsService, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(TimelineEvents.PollEndClicked(aMessageEvent().eventId!!)) + waitForPredicate { room.endPollInvocations.size == 1 } + cancelAndIgnoreRemainingEvents() + assertThat(room.endPollInvocations.size).isEqualTo(1) + assertThat(room.endPollInvocations.first().pollStartId).isEqualTo(AN_EVENT_ID) + assertThat(room.endPollInvocations.first().text).isEqualTo("The poll with event id: \$anEventId has ended.") + assertThat(analyticsService.capturedEvents.size).isEqualTo(1) + assertThat(analyticsService.capturedEvents.last()).isEqualTo(PollEnd()) + } + } + private fun TestScope.createTimelinePresenter( timeline: MatrixTimeline = FakeMatrixTimeline(), timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory() diff --git a/features/onboarding/impl/src/main/res/values-ru/translations.xml b/features/onboarding/impl/src/main/res/values-ru/translations.xml index 5c8c12c2b0..21bc36c78a 100644 --- a/features/onboarding/impl/src/main/res/values-ru/translations.xml +++ b/features/onboarding/impl/src/main/res/values-ru/translations.xml @@ -6,5 +6,5 @@ <string name="screen_onboarding_subtitle">"Безопасное общение и совместная работа"</string> <string name="screen_onboarding_welcome_message">"Добро пожаловать в самый быстрый Element. Преимущество в скорости и простоте."</string> <string name="screen_onboarding_welcome_subtitle">"Добро пожаловать в %1$s. Supercharged — это скорость и простота."</string> - <string name="screen_onboarding_welcome_title">"Будь в своей стихии"</string> + <string name="screen_onboarding_welcome_title">"Будь c element"</string> </resources> diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt index e94b5adeeb..42f0c4f527 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt @@ -19,13 +19,17 @@ package io.element.android.features.poll.api import io.element.android.libraries.matrix.api.poll.PollAnswer import kotlinx.collections.immutable.persistentListOf -fun aPollAnswerItemList(isEnded: Boolean = false, isDisclosed: Boolean = true) = persistentListOf( +fun aPollAnswerItemList( + hasVotes: Boolean = true, + isEnded: Boolean = false, + isDisclosed: Boolean = true, +) = persistentListOf( aPollAnswerItem( answer = PollAnswer("option_1", "Italian \uD83C\uDDEE\uD83C\uDDF9"), isDisclosed = isDisclosed, isEnabled = !isEnded, isWinner = isEnded, - votesCount = 5, + votesCount = if (hasVotes) 5 else 0, percentage = 0.5f ), aPollAnswerItem( @@ -42,7 +46,7 @@ fun aPollAnswerItemList(isEnded: Boolean = false, isDisclosed: Boolean = true) = isEnabled = !isEnded, isWinner = false, isSelected = true, - votesCount = 1, + votesCount = if (hasVotes) 1 else 0, percentage = 0.1f ), aPollAnswerItem(isDisclosed = isDisclosed, isEnabled = !isEnded), diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt index 9307c20e77..bfcccf3b89 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt @@ -26,14 +26,19 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectableGroup import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +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.Text import io.element.android.libraries.designsystem.utils.CommonDrawables @@ -51,13 +56,37 @@ fun PollContentView( answerItems: ImmutableList<PollAnswerItem>, pollKind: PollKind, isPollEnded: Boolean, + isMine: Boolean, onAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, + onPollEdit: (pollStartId: EventId) -> Unit, + onPollEnd: (pollStartId: EventId) -> Unit, modifier: Modifier = Modifier, ) { + val votesCount = remember(answerItems) { answerItems.sumOf { it.votesCount } } + fun onAnswerSelected(pollAnswer: PollAnswer) { eventId?.let { onAnswerSelected(it, pollAnswer.id) } } + fun onPollEdit() { + eventId?.let { onPollEdit(it) } + } + + fun onPollEnd() { + eventId?.let { onPollEnd(it) } + } + + var showConfirmation: Boolean by remember { mutableStateOf(false) } + + if (showConfirmation) ConfirmationDialog( + content = stringResource(id = CommonStrings.common_poll_end_confirmation), + onSubmitClicked = { + onPollEnd() + showConfirmation = false + }, + onDismiss = { showConfirmation = false }, + ) + Column( modifier = modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp), @@ -67,11 +96,20 @@ fun PollContentView( PollAnswers(answerItems = answerItems, onAnswerSelected = ::onAnswerSelected) if (isPollEnded || pollKind == PollKind.Disclosed) { - val votesCount = remember(answerItems) { answerItems.sumOf { it.votesCount } } DisclosedPollBottomNotice(votesCount = votesCount) } else { UndisclosedPollBottomNotice() } + + if (isMine) { + CreatorView( + votesCount = 1, // TODO Polls: set to `votesCount` when edit poll screen is implemented. + isPollEnded = isPollEnded, + onPollEdit = ::onPollEdit, + onPollEnd = { showConfirmation = true }, + modifier = Modifier.fillMaxWidth(), + ) + } } } @@ -157,6 +195,31 @@ private fun ColumnScope.UndisclosedPollBottomNotice( ) } +@Composable +private fun CreatorView( + @Suppress("SameParameterValue") votesCount: Int, // TODO Polls: remove @Suppress when edit poll screen is implemented. + isPollEnded: Boolean, + onPollEdit: () -> Unit, + onPollEnd: () -> Unit, + modifier: Modifier = Modifier +) { + if (!isPollEnded) { + if (votesCount == 0) { + Button( + text = stringResource(id = CommonStrings.action_edit_poll), + onClick = onPollEdit, + modifier = modifier, + ) + } else { + Button( + text = stringResource(id = CommonStrings.action_end_poll), + onClick = onPollEnd, + modifier = modifier, + ) + } + } +} + @PreviewsDayNight @Composable internal fun PollContentUndisclosedPreview() = ElementPreview { @@ -166,7 +229,10 @@ internal fun PollContentUndisclosedPreview() = ElementPreview { answerItems = aPollAnswerItemList(isDisclosed = false), pollKind = PollKind.Undisclosed, isPollEnded = false, + isMine = false, onAnswerSelected = { _, _ -> }, + onPollEdit = {}, + onPollEnd = {}, ) } @@ -179,7 +245,10 @@ internal fun PollContentDisclosedPreview() = ElementPreview { answerItems = aPollAnswerItemList(), pollKind = PollKind.Disclosed, isPollEnded = false, + isMine = false, onAnswerSelected = { _, _ -> }, + onPollEdit = {}, + onPollEnd = {}, ) } @@ -192,6 +261,57 @@ internal fun PollContentEndedPreview() = ElementPreview { answerItems = aPollAnswerItemList(isEnded = true), pollKind = PollKind.Disclosed, isPollEnded = true, + isMine = false, + onAnswerSelected = { _, _ -> }, + onPollEdit = {}, + onPollEnd = {}, + ) +} + +@PreviewsDayNight +@Composable +internal fun PollContentCreatorNoVotesPreview() = ElementPreview { + PollContentView( + eventId = EventId("\$anEventId"), + question = "What type of food should we have at the party?", + answerItems = aPollAnswerItemList(hasVotes = false, isEnded = false), + pollKind = PollKind.Disclosed, + isPollEnded = false, + isMine = true, + onAnswerSelected = { _, _ -> }, + onPollEdit = {}, + onPollEnd = {}, + ) +} + +@PreviewsDayNight +@Composable +internal fun PollContentCreatorPreview() = ElementPreview { + PollContentView( + eventId = EventId("\$anEventId"), + question = "What type of food should we have at the party?", + answerItems = aPollAnswerItemList(isEnded = false), + pollKind = PollKind.Disclosed, + isPollEnded = false, + isMine = true, + onAnswerSelected = { _, _ -> }, + onPollEdit = {}, + onPollEnd = {}, + ) +} + +@PreviewsDayNight +@Composable +internal fun PollContentCreatorEndedPreview() = ElementPreview { + PollContentView( + eventId = EventId("\$anEventId"), + question = "What type of food should we have at the party?", + answerItems = aPollAnswerItemList(isEnded = true), + pollKind = PollKind.Disclosed, + isPollEnded = true, + isMine = true, onAnswerSelected = { _, _ -> }, + onPollEdit = {}, + onPollEnd = {}, ) } diff --git a/features/poll/impl/src/main/res/values-cs/translations.xml b/features/poll/impl/src/main/res/values-cs/translations.xml index d199df993f..fa722e9616 100644 --- a/features/poll/impl/src/main/res/values-cs/translations.xml +++ b/features/poll/impl/src/main/res/values-cs/translations.xml @@ -4,6 +4,8 @@ <string name="screen_create_poll_anonymous_desc">"Zobrazit výsledky až po skončení hlasování"</string> <string name="screen_create_poll_anonymous_headline">"Anonymní hlasování"</string> <string name="screen_create_poll_answer_hint">"Volba %1$d"</string> + <string name="screen_create_poll_discard_confirmation">"Opravdu chcete zrušit toto hlasování?"</string> + <string name="screen_create_poll_discard_confirmation_title">"Zrušit hlasování"</string> <string name="screen_create_poll_question_desc">"Otázka nebo téma"</string> <string name="screen_create_poll_question_hint">"Čeho se hlasování týká?"</string> <string name="screen_create_poll_title">"Vytvořit hlasování"</string> diff --git a/features/poll/impl/src/main/res/values-zh-rTW/translations.xml b/features/poll/impl/src/main/res/values-zh-rTW/translations.xml new file mode 100644 index 0000000000..298224c8ad --- /dev/null +++ b/features/poll/impl/src/main/res/values-zh-rTW/translations.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="screen_create_poll_add_option_btn">"新增選項"</string> + <string name="screen_create_poll_anonymous_desc">"只在投票結束後顯示結果"</string> + <string name="screen_create_poll_anonymous_headline">"隱藏票數"</string> + <string name="screen_create_poll_answer_hint">"選項 %1$d"</string> + <string name="screen_create_poll_discard_confirmation">"您確定要捨棄這項投票嗎?"</string> + <string name="screen_create_poll_discard_confirmation_title">"捨棄投票"</string> + <string name="screen_create_poll_question_desc">"問題或主題"</string> + <string name="screen_create_poll_question_hint">"投什麼?"</string> + <string name="screen_create_poll_title">"建立投票"</string> +</resources> diff --git a/features/preferences/impl/src/main/res/values-cs/translations.xml b/features/preferences/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..26b082e386 --- /dev/null +++ b/features/preferences/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="screen_edit_profile_display_name">"Zobrazované jméno"</string> + <string name="screen_edit_profile_display_name_placeholder">"Vaše zobrazované jméno"</string> + <string name="screen_edit_profile_error">"Došlo k neznámé chybě a informace nelze změnit."</string> + <string name="screen_edit_profile_error_title">"Nelze aktualizovat profil"</string> + <string name="screen_edit_profile_title">"Upravit profil"</string> + <string name="screen_edit_profile_updating_details">"Aktualizace profilu…"</string> +</resources> diff --git a/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml b/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml new file mode 100644 index 0000000000..f1cb358027 --- /dev/null +++ b/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="screen_edit_profile_display_name">"顯示名稱"</string> + <string name="screen_edit_profile_display_name_placeholder">"您的顯示名稱"</string> + <string name="screen_edit_profile_error_title">"無法更新個人檔案"</string> + <string name="screen_edit_profile_title">"編輯個人檔案"</string> + <string name="screen_edit_profile_updating_details">"正在更新個人檔案…"</string> +</resources> diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt index 5fd95e79bd..9258909201 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt @@ -17,9 +17,11 @@ package io.element.android.features.rageshake.impl.bugreport import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableFloatState import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable @@ -43,27 +45,27 @@ class BugReportPresenter @Inject constructor( ) : Presenter<BugReportState> { private class BugReporterUploadListener( - private val sendingProgress: MutableState<Float>, + private val sendingProgress: MutableFloatState, private val sendingAction: MutableState<Async<Unit>> ) : BugReporterListener { override fun onUploadCancelled() { - sendingProgress.value = 0f + sendingProgress.floatValue = 0f sendingAction.value = Async.Uninitialized } override fun onUploadFailed(reason: String?) { - sendingProgress.value = 0f + sendingProgress.floatValue = 0f sendingAction.value = Async.Failure(Exception(reason)) } override fun onProgress(progress: Int) { - sendingProgress.value = progress.toFloat() / 100 + sendingProgress.floatValue = progress.toFloat() / 100 sendingAction.value = Async.Loading() } override fun onUploadSucceed(reportUrl: String?) { - sendingProgress.value = 0f + sendingProgress.floatValue = 0f sendingAction.value = Async.Success(Unit) } } @@ -80,7 +82,7 @@ class BugReportPresenter @Inject constructor( .collectAsState(initial = "") val sendingProgress = remember { - mutableStateOf(0f) + mutableFloatStateOf(0f) } val sendingAction: MutableState<Async<Unit>> = remember { mutableStateOf(Async.Uninitialized) @@ -107,7 +109,7 @@ class BugReportPresenter @Inject constructor( copy(sendScreenshot = event.sendScreenshot) } BugReportEvents.ClearError -> { - sendingProgress.value = 0f + sendingProgress.floatValue = 0f sendingAction.value = Async.Uninitialized } } @@ -115,7 +117,7 @@ class BugReportPresenter @Inject constructor( return BugReportState( hasCrashLogs = crashInfo.isNotEmpty(), - sendingProgress = sendingProgress.value, + sendingProgress = sendingProgress.floatValue, sending = sendingAction.value, formState = formState.value, screenshotUri = screenshotUri.value, diff --git a/features/rageshake/impl/src/main/res/values-zh-rTW/translations.xml b/features/rageshake/impl/src/main/res/values-zh-rTW/translations.xml index 6e9eaabed3..bd4d1cb665 100644 --- a/features/rageshake/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/rageshake/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="screen_bug_report_attach_screenshot">"附上螢幕截圖"</string> + <string name="screen_bug_report_contact_me">"如果有其他問題,你可以聯絡我。"</string> <string name="screen_bug_report_contact_me_title">"聯絡我"</string> <string name="screen_bug_report_edit_screenshot">"編輯螢幕截圖"</string> <string name="screen_bug_report_include_screenshot">"傳送螢幕截圖"</string> diff --git a/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml b/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml index 7b4eb895ea..14db6a6d91 100644 --- a/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml @@ -23,12 +23,13 @@ <string name="screen_room_notification_settings_error_restoring_default">"無法重設為預設模式,請再試一次。"</string> <string name="screen_room_notification_settings_error_setting_mode">"無法設定模式,請再試一次。"</string> <string name="screen_room_notification_settings_mode_all_messages">"所有訊息"</string> - <string name="screen_room_notification_settings_mode_mentions_and_keywords">"只限提及與關鍵字"</string> + <string name="screen_room_notification_settings_mode_mentions_and_keywords">"僅限提及與關鍵字"</string> <string name="screen_dm_details_block_alert_action">"封鎖"</string> <string name="screen_dm_details_block_user">"封鎖使用者"</string> <string name="screen_dm_details_unblock_alert_action">"解除封鎖"</string> <string name="screen_dm_details_unblock_user">"解除封鎖使用者"</string> <string name="screen_room_details_leave_room_title">"離開聊天室"</string> <string name="screen_room_details_people_title">"夥伴"</string> + <string name="screen_room_details_security_title">"安全性"</string> <string name="screen_room_details_topic_title">"主題"</string> </resources> diff --git a/features/roomlist/impl/src/main/res/values-zh-rTW/translations.xml b/features/roomlist/impl/src/main/res/values-zh-rTW/translations.xml index 795a4e46d0..4ad446a0bf 100644 --- a/features/roomlist/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/roomlist/impl/src/main/res/values-zh-rTW/translations.xml @@ -2,4 +2,6 @@ <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="screen_roomlist_a11y_create_message">"建立新的對話或聊天室"</string> <string name="screen_roomlist_main_space_title">"所有聊天室"</string> + <string name="session_verification_banner_message">"您似乎正在使用新的裝置。請使用另一個裝置進行驗證,以存取您的加密訊息。"</string> + <string name="session_verification_banner_title">"驗證這是您本人"</string> </resources> diff --git a/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml b/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml index fc59911a93..8b054ac408 100644 --- a/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,9 +1,19 @@ <?xml version="1.0" encoding="utf-8"?> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="screen_session_verification_cancelled_subtitle">"似乎出了一點問題。有可能是因為等候逾時,或是請求被拒絕。"</string> + <string name="screen_session_verification_compare_emojis_subtitle">"確認顯示在其他工作階段上的表情符號是否和下方的相同。"</string> + <string name="screen_session_verification_compare_emojis_title">"比對表情符號"</string> + <string name="screen_session_verification_complete_subtitle">"新的工作階段已完成驗證。它能夠存取您的加密訊息,而其他使用者會將它視為可信任的。"</string> + <string name="screen_session_verification_open_existing_session_subtitle">"為了存取被加密的歷史訊息,請證明這是您本人。"</string> + <string name="screen_session_verification_open_existing_session_title">"開啟一個現存的工作階段"</string> + <string name="screen_session_verification_positive_button_canceled">"重新嘗試驗證"</string> <string name="screen_session_verification_positive_button_initial">"我準備好了"</string> <string name="screen_session_verification_positive_button_verifying_ongoing">"等待比對"</string> - <string name="screen_session_verification_they_dont_match">"不相符"</string> - <string name="screen_session_verification_they_match">"相符"</string> + <string name="screen_session_verification_request_accepted_subtitle">"表情符號是唯一的,請相互比對,確認它們的排列順序是否相同。"</string> + <string name="screen_session_verification_they_dont_match">"不一樣"</string> + <string name="screen_session_verification_they_match">"一樣"</string> + <string name="screen_session_verification_waiting_to_accept_subtitle">"準備開始驗證,請到您的其他工作階段接受請求。"</string> + <string name="screen_session_verification_waiting_to_accept_title">"等待接受請求"</string> <string name="screen_session_verification_cancelled_title">"驗證已取消"</string> <string name="screen_session_verification_positive_button_ready">"開始"</string> </resources> diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6f648c1773..9f8af860fb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,7 +45,7 @@ dependencycheck = "8.4.0" dependencyanalysis = "1.22.0" stem = "2.3.0" sqldelight = "1.5.5" -telephoto = "0.6.1" +telephoto = "0.6.2" wysiwyg = "2.12.0" # DI @@ -128,7 +128,7 @@ test_junit = "junit:junit:4.13.2" test_runner = "androidx.test:runner:1.5.2" test_uiautomator = "androidx.test.uiautomator:uiautomator:2.2.0" test_junitext = "androidx.test.ext:junit:1.1.5" -test_mockk = "io.mockk:mockk:1.13.7" +test_mockk = "io.mockk:mockk:1.13.8" test_barista = "com.adevinta.android:barista:4.3.0" test_hamcrest = "org.hamcrest:hamcrest:2.2" test_orchestrator = "androidx.test:orchestrator:1.4.2" @@ -150,7 +150,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" } timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.57" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.58" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt index c6373fbbf6..0506126568 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt @@ -51,4 +51,12 @@ object MimeTypes { fun String?.isMimeTypeFile() = this?.startsWith("file/").orFalse() fun String?.isMimeTypeText() = this?.startsWith("text/").orFalse() fun String?.isMimeTypeAny() = this?.startsWith("*/").orFalse() + + fun fromFileExtension(fileExtension: String): String { + return when (fileExtension.lowercase()) { + "apk" -> Apk + "pdf" -> Pdf + else -> OctetStream + } + } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/swipe/SwipeableActionsState.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/swipe/SwipeableActionsState.kt index 77d80cdde5..542c46fae1 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/swipe/SwipeableActionsState.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/swipe/SwipeableActionsState.kt @@ -21,9 +21,10 @@ import androidx.compose.animation.core.tween import androidx.compose.foundation.MutatePriority import androidx.compose.foundation.gestures.DraggableState import androidx.compose.runtime.Composable +import androidx.compose.runtime.FloatState import androidx.compose.runtime.Stable -import androidx.compose.runtime.State import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -41,8 +42,8 @@ class SwipeableActionsState { /** * The current position (in pixels) of the content. */ - val offset: State<Float> get() = offsetState - private var offsetState = mutableStateOf(0f) + val offset: FloatState get() = offsetState + private var offsetState = mutableFloatStateOf(0f) /** * Whether the content is currently animating to reset its offset after it was swiped. @@ -51,21 +52,21 @@ class SwipeableActionsState { private set val draggableState = DraggableState { delta -> - val targetOffset = offsetState.value + delta + val targetOffset = offsetState.floatValue + delta val isAllowed = isResettingOnRelease || targetOffset > 0f - offsetState.value += if (isAllowed) delta else 0f + offsetState.floatValue += if (isAllowed) delta else 0f } suspend fun resetOffset() { draggableState.drag(MutatePriority.PreventUserInput) { isResettingOnRelease = true try { - Animatable(offsetState.value).animateTo( + Animatable(offsetState.floatValue).animateTo( targetValue = 0f, animationSpec = tween(durationMillis = 300), ) { - dragBy(value - offsetState.value) + dragBy(value - offsetState.floatValue) } } finally { isResettingOnRelease = false diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt index 872376583d..2041d7998d 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt @@ -22,7 +22,7 @@ import androidx.compose.material3.SliderColors import androidx.compose.material3.SliderDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier @@ -62,7 +62,7 @@ internal fun SlidersPreview() = ElementThemedPreview { ContentToPreview() } @Composable private fun ContentToPreview() { - var value by remember { mutableStateOf(0.33f) } + var value by remember { mutableFloatStateOf(0.33f) } Column { Slider(onValueChange = { value = it }, value = value, enabled = true) Slider(steps = 10, onValueChange = { value = it }, value = value, enabled = true) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt index 8565e4c747..290a54c502 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt @@ -61,6 +61,7 @@ sealed interface NotificationContent { ) : MessageLike data object RoomRedaction : MessageLike data object Sticker : MessageLike + data class Poll(val question: String) : MessageLike } sealed interface StateEvent : NotificationContent { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 1dd6101354..17cb637d80 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -89,6 +89,8 @@ interface MatrixRoom : Closeable { suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String?): Result<Unit> + suspend fun enterReplyMode(eventId: EventId): Result<Unit> + suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result<Unit> suspend fun redactEvent(eventId: EventId, reason: String? = null): Result<Unit> @@ -184,7 +186,4 @@ interface MatrixRoom : Closeable { suspend fun endPoll(pollStartId: EventId, text: String): Result<Unit> override fun close() = destroy() - } - - diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt index c958810f5e..97148f7dd4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt @@ -17,6 +17,7 @@ package io.element.android.libraries.matrix.impl.media import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.media.MediaFile import io.element.android.libraries.matrix.api.media.MediaSource @@ -77,7 +78,7 @@ class RustMediaLoader( val mediaFile = innerClient.getMediaFile( mediaSource = mediaSource, body = body, - mimeType = mimeType ?: "application/octet-stream", + mimeType = mimeType ?: MimeTypes.OctetStream, tempDir = cacheDirectory.path, ) RustMediaFile(mediaFile) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt index e30e57113d..b82716cc2d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt @@ -94,6 +94,7 @@ private fun MessageLikeEventContent.toContent(senderId: UserId): NotificationCon } MessageLikeEventContent.RoomRedaction -> NotificationContent.MessageLike.RoomRedaction MessageLikeEventContent.Sticker -> NotificationContent.MessageLike.Sticker + is MessageLikeEventContent.Poll -> NotificationContent.MessageLike.Poll(question) } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt index 8ee0361ace..d539ec6a53 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt @@ -27,7 +27,6 @@ import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomListService import org.matrix.rustcomponents.sdk.TimelineDiff import org.matrix.rustcomponents.sdk.TimelineListener -import org.matrix.rustcomponents.sdk.genTransactionId import kotlin.time.Duration.Companion.milliseconds /** @@ -61,7 +60,7 @@ class RoomContentForwarder( // Sending a message requires a registered timeline listener targetRoom.addTimelineListener(NoOpTimelineListener) withTimeout(timeoutMs.milliseconds) { - targetRoom.send(content, genTransactionId()) + targetRoom.send(content) } } // After sending, we remove the timeline diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 4a8a3f9bee..4c20dd4d0c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -60,6 +60,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.withContext +import org.matrix.rustcomponents.sdk.EventTimelineItem import org.matrix.rustcomponents.sdk.RequiredState import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomListItem @@ -67,7 +68,6 @@ import org.matrix.rustcomponents.sdk.RoomMember import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation import org.matrix.rustcomponents.sdk.RoomSubscription import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle -import org.matrix.rustcomponents.sdk.genTransactionId import org.matrix.rustcomponents.sdk.messageEventContentFromHtml import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown import timber.log.Timber @@ -139,6 +139,7 @@ class RustMatrixRoom( roomCoroutineScope.cancel() innerRoom.destroy() roomListItem.destroy() + inReplyToEventTimelineItem?.destroy() } override val name: String? @@ -241,10 +242,9 @@ class RustMatrixRoom( } override suspend fun sendMessage(body: String, htmlBody: String?): Result<Unit> = withContext(roomDispatcher) { - val transactionId = genTransactionId() messageEventContentFromParts(body, htmlBody).use { content -> runCatching { - innerRoom.send(content, transactionId) + innerRoom.send(content) } } } @@ -253,26 +253,39 @@ class RustMatrixRoom( withContext(roomDispatcher) { if (originalEventId != null) { runCatching { - innerRoom.edit(messageEventContentFromParts(body, htmlBody), originalEventId.value, transactionId?.value) + innerRoom.edit(messageEventContentFromParts(body, htmlBody), originalEventId.value) } } else { runCatching { transactionId?.let { cancelSend(it) } - innerRoom.send(messageEventContentFromParts(body, htmlBody), genTransactionId()) + innerRoom.send(messageEventContentFromParts(body, htmlBody)) } } } + private var inReplyToEventTimelineItem: EventTimelineItem? = null + + override suspend fun enterReplyMode(eventId: EventId): Result<Unit> = withContext(roomDispatcher) { + runCatching { + inReplyToEventTimelineItem?.destroy() + inReplyToEventTimelineItem = null + inReplyToEventTimelineItem = innerRoom.getEventTimelineItemByEventId(eventId.value) + } + } + override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result<Unit> = withContext(roomDispatcher) { runCatching { - innerRoom.sendReply(messageEventContentFromParts(body, htmlBody), eventId.value, genTransactionId()) + val inReplyTo = inReplyToEventTimelineItem ?: innerRoom.getEventTimelineItemByEventId(eventId.value) + inReplyTo.use { eventTimelineItem -> + innerRoom.sendReply(messageEventContentFromParts(body, htmlBody), eventTimelineItem) + } + inReplyToEventTimelineItem = null } } override suspend fun redactEvent(eventId: EventId, reason: String?) = withContext(roomDispatcher) { - val transactionId = genTransactionId() runCatching { - innerRoom.redact(eventId.value, reason, transactionId) + innerRoom.redact(eventId.value, reason) } } @@ -416,7 +429,6 @@ class RustMatrixRoom( description = description, zoomLevel = zoomLevel?.toUByte(), assetType = assetType?.toInner(), - txnId = genTransactionId(), ) } } @@ -433,7 +445,6 @@ class RustMatrixRoom( answers = answers, maxSelections = maxSelections.toUByte(), pollKind = pollKind.toInner(), - txnId = genTransactionId(), ) } } @@ -446,7 +457,6 @@ class RustMatrixRoom( innerRoom.sendPollResponse( pollStartId = pollStartId.value, answers = answers, - txnId = genTransactionId(), ) } } @@ -459,7 +469,6 @@ class RustMatrixRoom( innerRoom.endPoll( pollStartId = pollStartId.value, text = text, - txnId = genTransactionId(), ) } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 0e8916e87e..e8abdb62df 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -31,8 +31,8 @@ import io.element.android.libraries.matrix.api.notificationsettings.Notification import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState -import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState +import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.timeline.MatrixTimeline @@ -208,6 +208,10 @@ class FakeMatrixRoom( var replyMessageParameter: Pair<String, String?>? = null private set + override suspend fun enterReplyMode(eventId: EventId): Result<Unit> { + return Result.success(Unit) + } + override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result<Unit> { replyMessageParameter = body to htmlBody return Result.success(Unit) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt index 36eb445ae6..884f45de02 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt @@ -29,7 +29,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -55,7 +55,7 @@ fun SelectedUsersList( ) { val lazyListState = rememberLazyListState() if (autoScroll) { - var currentSize by rememberSaveable { mutableStateOf(selectedUsers.size) } + var currentSize by rememberSaveable { mutableIntStateOf(selectedUsers.size) } LaunchedEffect(selectedUsers.size) { val isItemAdded = selectedUsers.size > currentSize if (isItemAdded) { diff --git a/libraries/permissions/api/src/main/res/values-cs/translations.xml b/libraries/permissions/api/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..967924280c --- /dev/null +++ b/libraries/permissions/api/src/main/res/values-cs/translations.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="dialog_permission_camera">"Aby mohla aplikace používat fotoaparát, udělte prosím oprávnění v nastavení systému."</string> + <string name="dialog_permission_generic">"Udělte prosím oprávnění v nastavení systému."</string> + <string name="dialog_permission_microphone">"Aby aplikace mohla používat mikrofon, udělte prosím oprávnění v nastavení systému."</string> + <string name="dialog_permission_notification">"Aby aplikace mohla zobrazovat upozornění, udělte prosím oprávnění v nastavení systému."</string> +</resources> diff --git a/libraries/permissions/api/src/main/res/values-de/translations.xml b/libraries/permissions/api/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..5a16d89b4f --- /dev/null +++ b/libraries/permissions/api/src/main/res/values-de/translations.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="dialog_permission_camera">"Damit die Anwendung die Kamera verwenden kann, erteilen Sie bitte die Erlaubnis in den Systemeinstellungen."</string> + <string name="dialog_permission_generic">"Bitte erteilen Sie die Erlaubnis in den Systemeinstellungen."</string> + <string name="dialog_permission_microphone">"Damit die Anwendung das Mikrofon verwenden kann, erteilen Sie bitte die Erlaubnis in den Systemeinstellungen."</string> + <string name="dialog_permission_notification">"Damit die Anwendung Benachrichtigungen anzeigen kann, erteilen Sie bitte die Erlaubnis in den Systemeinstellungen."</string> +</resources> diff --git a/libraries/permissions/api/src/main/res/values-fr/translations.xml b/libraries/permissions/api/src/main/res/values-fr/translations.xml index 104425aca9..e0fac38135 100644 --- a/libraries/permissions/api/src/main/res/values-fr/translations.xml +++ b/libraries/permissions/api/src/main/res/values-fr/translations.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="dialog_permission_camera">"Pour permettre à l\'application d\'utiliser l\'appareil photo, veuillez accorder l\'autorisation dans les paramètres du système."</string> - <string name="dialog_permission_generic">"Veuillez accorder l\'autorisation dans les paramètres du système."</string> - <string name="dialog_permission_microphone">"Pour permettre à l\'application d\'utiliser le microphone, veuillez accorder l\'autorisation dans les paramètres du système."</string> - <string name="dialog_permission_notification">"Pour permettre à l\'application d\'afficher les notifications, veuillez accorder l\'autorisation dans les paramètres du système."</string> + <string name="dialog_permission_camera">"Pour permettre à l’application d’utiliser l’appareil photo, veuillez accorder l’autorisation dans les paramètres du système."</string> + <string name="dialog_permission_generic">"Veuillez accorder l’autorisation dans les paramètres du système."</string> + <string name="dialog_permission_microphone">"Pour permettre à l\'application d’utiliser le microphone, veuillez accorder l’autorisation dans les paramètres du système."</string> + <string name="dialog_permission_notification">"Pour permettre à l’application d’afficher les notifications, veuillez accorder l’autorisation dans les paramètres du système."</string> </resources> diff --git a/libraries/permissions/api/src/main/res/values-ru/translations.xml b/libraries/permissions/api/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..5c7a1b25f1 --- /dev/null +++ b/libraries/permissions/api/src/main/res/values-ru/translations.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="dialog_permission_camera">"Чтобы приложение могло использовать камеру, предоставьте разрешение в системных настройках."</string> + <string name="dialog_permission_generic">"Пожалуйста, предоставьте разрешение в системных настройках."</string> + <string name="dialog_permission_microphone">"Чтобы приложение могло использовать микрофон, предоставьте разрешение в системных настройках."</string> + <string name="dialog_permission_notification">"Чтобы приложение отображало уведомления, предоставьте разрешение в системных настройках."</string> +</resources> diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt index c93d517e89..9951698b88 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt @@ -114,10 +114,63 @@ class NotifiableEventResolver @Inject constructor( title = null, // TODO check if title is needed anymore ) } else { - fallbackNotifiableEvent(userId, roomId, eventId) + Timber.tag(loggerTag.value).d("Ignoring notification state event for membership ${content.membershipState}") + null } } - else -> fallbackNotifiableEvent(userId, roomId, eventId) + NotificationContent.MessageLike.CallAnswer, + NotificationContent.MessageLike.CallCandidates, + NotificationContent.MessageLike.CallHangup, + NotificationContent.MessageLike.CallInvite -> null.also { + Timber.tag(loggerTag.value).d("Ignoring notification for call ${content.javaClass.simpleName}") + } + NotificationContent.MessageLike.KeyVerificationAccept, + NotificationContent.MessageLike.KeyVerificationCancel, + NotificationContent.MessageLike.KeyVerificationDone, + NotificationContent.MessageLike.KeyVerificationKey, + NotificationContent.MessageLike.KeyVerificationMac, + NotificationContent.MessageLike.KeyVerificationReady, + NotificationContent.MessageLike.KeyVerificationStart -> null.also { + Timber.tag(loggerTag.value).d("Ignoring notification for verification ${content.javaClass.simpleName}") + } + is NotificationContent.MessageLike.Poll -> null.also { + // TODO Polls: handle notification rendering + Timber.tag(loggerTag.value).d("Ignoring notification for poll") + } + is NotificationContent.MessageLike.ReactionContent -> null.also { + Timber.tag(loggerTag.value).d("Ignoring notification for reaction") + } + NotificationContent.MessageLike.RoomEncrypted -> fallbackNotifiableEvent(userId, roomId, eventId).also { + Timber.tag(loggerTag.value).w("Notification with encrypted content -> fallback") + } + NotificationContent.MessageLike.RoomRedaction -> null.also { + Timber.tag(loggerTag.value).d("Ignoring notification for redaction") + } + NotificationContent.MessageLike.Sticker -> null.also { + Timber.tag(loggerTag.value).d("Ignoring notification for sticker") + } + NotificationContent.StateEvent.PolicyRuleRoom, + NotificationContent.StateEvent.PolicyRuleServer, + NotificationContent.StateEvent.PolicyRuleUser, + NotificationContent.StateEvent.RoomAliases, + NotificationContent.StateEvent.RoomAvatar, + NotificationContent.StateEvent.RoomCanonicalAlias, + NotificationContent.StateEvent.RoomCreate, + NotificationContent.StateEvent.RoomEncryption, + NotificationContent.StateEvent.RoomGuestAccess, + NotificationContent.StateEvent.RoomHistoryVisibility, + NotificationContent.StateEvent.RoomJoinRules, + NotificationContent.StateEvent.RoomName, + NotificationContent.StateEvent.RoomPinnedEvents, + NotificationContent.StateEvent.RoomPowerLevels, + NotificationContent.StateEvent.RoomServerAcl, + NotificationContent.StateEvent.RoomThirdPartyInvite, + NotificationContent.StateEvent.RoomTombstone, + NotificationContent.StateEvent.RoomTopic, + NotificationContent.StateEvent.SpaceChild, + NotificationContent.StateEvent.SpaceParent -> null.also { + Timber.tag(loggerTag.value).d("Ignoring notification for state event ${content.javaClass.simpleName}") + } } } diff --git a/libraries/textcomposer/impl/src/main/res/values-cs/translations.xml b/libraries/textcomposer/impl/src/main/res/values-cs/translations.xml index cae45c8cd2..ea3bda3bae 100644 --- a/libraries/textcomposer/impl/src/main/res/values-cs/translations.xml +++ b/libraries/textcomposer/impl/src/main/res/values-cs/translations.xml @@ -1,8 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="rich_text_editor_bullet_list">"Přepnout seznam s odrážkami"</string> + <string name="rich_text_editor_close_formatting_options">"Zavřít možnosti formátování"</string> <string name="rich_text_editor_code_block">"Přepnout blok kódu"</string> <string name="rich_text_editor_composer_placeholder">"Zpráva…"</string> + <string name="rich_text_editor_create_link">"Vytvořit odkaz"</string> + <string name="rich_text_editor_edit_link">"Upravit odkaz"</string> <string name="rich_text_editor_format_bold">"Použít tučný text"</string> <string name="rich_text_editor_format_italic">"Použít kurzívu"</string> <string name="rich_text_editor_format_strikethrough">"Použít přeškrtnutí"</string> @@ -12,7 +15,10 @@ <string name="rich_text_editor_inline_code">"Použít formát inline kódu"</string> <string name="rich_text_editor_link">"Nastavit odkaz"</string> <string name="rich_text_editor_numbered_list">"Přepnout číslovaný seznam"</string> + <string name="rich_text_editor_open_compose_options">"Otevřít možnosti psaní"</string> <string name="rich_text_editor_quote">"Přepnout citaci"</string> + <string name="rich_text_editor_remove_link">"Odstranit odkaz"</string> <string name="rich_text_editor_unindent">"Zrušit odsazení"</string> + <string name="rich_text_editor_url_placeholder">"Odkaz"</string> <string name="rich_text_editor_a11y_add_attachment">"Přidat přílohu"</string> </resources> diff --git a/libraries/textcomposer/impl/src/main/res/values-zh-rTW/translations.xml b/libraries/textcomposer/impl/src/main/res/values-zh-rTW/translations.xml index 4342c3478e..c299975f82 100644 --- a/libraries/textcomposer/impl/src/main/res/values-zh-rTW/translations.xml +++ b/libraries/textcomposer/impl/src/main/res/values-zh-rTW/translations.xml @@ -14,6 +14,7 @@ <string name="rich_text_editor_link">"設定連結"</string> <string name="rich_text_editor_numbered_list">"切換數字編號"</string> <string name="rich_text_editor_quote">"切換引用"</string> + <string name="rich_text_editor_remove_link">"移除連結"</string> <string name="rich_text_editor_unindent">"減少縮排"</string> <string name="rich_text_editor_url_placeholder">"連結"</string> <string name="rich_text_editor_a11y_add_attachment">"新增附件"</string> diff --git a/libraries/ui-strings/src/main/res/values-cs/translations.xml b/libraries/ui-strings/src/main/res/values-cs/translations.xml index 683a1e6abd..75a35d3190 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -1,10 +1,15 @@ <?xml version="1.0" encoding="utf-8"?> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="a11y_hide_password">"Skrýt heslo"</string> + <string name="a11y_notifications_mentions_only">"Pouze zmínky"</string> + <string name="a11y_notifications_muted">"Ztišeno"</string> + <string name="a11y_poll">"Hlasování"</string> + <string name="a11y_poll_end">"Hlasování ukončeno"</string> <string name="a11y_send_files">"Odeslat soubory"</string> <string name="a11y_show_password">"Zobrazit heslo"</string> <string name="a11y_user_menu">"Uživatelské menu"</string> <string name="action_accept">"Přijmout"</string> + <string name="action_add_to_timeline">"Přidat na časovou osu"</string> <string name="action_back">"Zpět"</string> <string name="action_cancel">"Zrušit"</string> <string name="action_choose_photo">"Vybrat fotku"</string> @@ -34,16 +39,20 @@ <string name="action_learn_more">"Zjistit více"</string> <string name="action_leave">"Odejít"</string> <string name="action_leave_room">"Opustit místnost"</string> + <string name="action_manage_account">"Spravovat účet"</string> + <string name="action_manage_devices">"Spravovat zařízení"</string> <string name="action_next">"Další"</string> <string name="action_no">"Ne"</string> <string name="action_not_now">"Teď ne"</string> <string name="action_ok">"OK"</string> + <string name="action_open_settings">"Otevřít nastavení"</string> <string name="action_open_with">"Otevřít v aplikaci"</string> <string name="action_quick_reply">"Rychlá odpověď"</string> <string name="action_quote">"Citovat"</string> <string name="action_react">"Reagovat"</string> <string name="action_remove">"Odstranit"</string> <string name="action_reply">"Odpovědět"</string> + <string name="action_reply_in_thread">"Odpovědět ve vlákně"</string> <string name="action_report_bug">"Nahlásit chybu"</string> <string name="action_report_content">"Nahlásit obsah"</string> <string name="action_retry">"Zkusit znovu"</string> @@ -64,6 +73,7 @@ <string name="action_yes">"Ano"</string> <string name="common_about">"O aplikaci"</string> <string name="common_acceptable_use_policy">"Zásady používání"</string> + <string name="common_advanced_settings">"Pokročilá nastavení"</string> <string name="common_analytics">"Analytika"</string> <string name="common_audio">"Zvuk"</string> <string name="common_bubbles">"Bubliny"</string> @@ -82,6 +92,7 @@ <string name="common_forward_message">"Přeposlat zprávu"</string> <string name="common_gif">"GIF"</string> <string name="common_image">"Obrázek"</string> + <string name="common_in_reply_to">"V odpovědi na %1$s"</string> <string name="common_invite_unknown_profile">"Tento Matrix identifikátor nelze najít, takže pozvánka nemusí být přijata."</string> <string name="common_leaving_room">"Opuštění místnosti"</string> <string name="common_link_copied_to_clipboard">"Odkaz zkopírován do schránky"</string> @@ -96,14 +107,17 @@ <string name="common_password">"Heslo"</string> <string name="common_people">"Lidé"</string> <string name="common_permalink">"Trvalý odkaz"</string> + <string name="common_permission">"Oprávnění"</string> <string name="common_poll_total_votes">"Celkový počet hlasů: %1$s"</string> <string name="common_poll_undisclosed_text">"Výsledky se zobrazí po skončení hlasování"</string> <string name="common_privacy_policy">"Zásady ochrany osobních údajů"</string> + <string name="common_reaction">"Reakce"</string> <string name="common_reactions">"Reakce"</string> <string name="common_refreshing">"Obnovování…"</string> <string name="common_replying_to">"Odpověď na %1$s"</string> <string name="common_report_a_bug">"Nahlásit chybu"</string> <string name="common_report_submitted">"Zpráva odeslána"</string> + <string name="common_rich_text_editor">"Editor formátovaného textu"</string> <string name="common_room_name">"Název místnosti"</string> <string name="common_room_name_placeholder">"např. název vašeho projektu"</string> <string name="common_search_for_someone">"Hledat někoho"</string> @@ -120,7 +134,9 @@ <string name="common_success">"Úspěch"</string> <string name="common_suggestions">"Návrhy"</string> <string name="common_syncing">"Synchronizace"</string> + <string name="common_text">"Text"</string> <string name="common_third_party_notices">"Oznámení třetích stran"</string> + <string name="common_thread">"Vlákno"</string> <string name="common_topic">"Téma"</string> <string name="common_topic_placeholder">"O čem je tato místnost?"</string> <string name="common_unable_to_decrypt">"Nelze dešifrovat"</string> @@ -133,6 +149,7 @@ <string name="common_verification_complete">"Ověření dokončeno"</string> <string name="common_video">"Video"</string> <string name="common_waiting">"Čekání…"</string> + <string name="common_poll_summary">"Hlasování: %1$s"</string> <string name="dialog_title_confirmation">"Potvrzení"</string> <string name="dialog_title_warning">"Upozornění"</string> <string name="emoji_picker_category_activity">"Aktivity"</string> diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index 39a495c33f..afdcc0e79c 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -45,6 +45,7 @@ <string name="action_no">"Nein"</string> <string name="action_not_now">"Nicht jetzt"</string> <string name="action_ok">"OK"</string> + <string name="action_open_settings">"Einstellungen öffnen"</string> <string name="action_open_with">"Öffnen mit"</string> <string name="action_quick_reply">"Schnelle Antwort"</string> <string name="action_quote">"Zitat"</string> @@ -106,6 +107,7 @@ <string name="common_password">"Passwort"</string> <string name="common_people">"Personen"</string> <string name="common_permalink">"Permalink"</string> + <string name="common_permission">"Erlaubnis"</string> <string name="common_poll_total_votes">"Stimmen insgesamt: %1$s"</string> <string name="common_poll_undisclosed_text">"Die Ergebnisse werden nach Ende der Umfrage angezeigt"</string> <string name="common_privacy_policy">"Datenschutz­erklärung"</string> @@ -147,6 +149,7 @@ <string name="common_verification_complete">"Verifizierung abgeschlossen"</string> <string name="common_video">"Video"</string> <string name="common_waiting">"Warten…"</string> + <string name="common_poll_summary">"Umfrage: %1$s"</string> <string name="dialog_title_confirmation">"Bestätigung"</string> <string name="dialog_title_warning">"Warnung"</string> <string name="emoji_picker_category_activity">"Aktivitäten"</string> diff --git a/libraries/ui-strings/src/main/res/values-ru/translations.xml b/libraries/ui-strings/src/main/res/values-ru/translations.xml index a3b6a264df..ab7cdc7e85 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -9,6 +9,7 @@ <string name="a11y_show_password">"Показать пароль"</string> <string name="a11y_user_menu">"Меню пользователя"</string> <string name="action_accept">"Разрешить"</string> + <string name="action_add_to_timeline">"Добавить в хронологию"</string> <string name="action_back">"Назад"</string> <string name="action_cancel">"Отмена"</string> <string name="action_choose_photo">"Выбрать фото"</string> @@ -44,6 +45,7 @@ <string name="action_no">"Нет"</string> <string name="action_not_now">"Не сейчас"</string> <string name="action_ok">"Ок"</string> + <string name="action_open_settings">"Открыть настройки"</string> <string name="action_open_with">"Открыть с помощью"</string> <string name="action_quick_reply">"Быстрый ответ"</string> <string name="action_quote">"Цитата"</string> @@ -105,6 +107,7 @@ <string name="common_password">"Пароль"</string> <string name="common_people">"Пользователи"</string> <string name="common_permalink">"Постоянная ссылка"</string> + <string name="common_permission">"Разрешение"</string> <string name="common_poll_total_votes">"Всего голосов: %1$s"</string> <string name="common_poll_undisclosed_text">"Результаты будут показаны после завершения опроса"</string> <string name="common_privacy_policy">"Политика конфиденциальности"</string> @@ -146,6 +149,7 @@ <string name="common_verification_complete">"Проверка завершена"</string> <string name="common_video">"Видео"</string> <string name="common_waiting">"Ожидание…"</string> + <string name="common_poll_summary">"Опрос: %1$s"</string> <string name="dialog_title_confirmation">"Подтверждение"</string> <string name="dialog_title_warning">"Предупреждение"</string> <string name="emoji_picker_category_activity">"Деятельность"</string> @@ -223,7 +227,7 @@ <string name="screen_share_location_title">"Поделиться местоположением"</string> <string name="screen_share_my_location_action">"Поделиться моим местоположением"</string> <string name="screen_share_open_apple_maps">"Открыть в Apple Maps"</string> - <string name="screen_share_open_google_maps">"Открыть в Google Картах"</string> + <string name="screen_share_open_google_maps">"Открыть в Google Maps"</string> <string name="screen_share_open_osm_maps">"Открыть в OpenStreetMap"</string> <string name="screen_share_this_location_action">"Поделиться этим местоположением"</string> <string name="screen_view_location_title">"Местоположение"</string> diff --git a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml index 0ace8beb22..2059c6f138 100644 --- a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml +++ b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml @@ -1,6 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="a11y_hide_password">"隱藏密碼"</string> + <string name="a11y_notifications_mentions_only">"僅限提及"</string> + <string name="a11y_notifications_muted">"已關閉通知"</string> + <string name="a11y_poll">"投票"</string> + <string name="a11y_poll_end">"投票已結束"</string> <string name="a11y_send_files">"傳送檔案"</string> <string name="a11y_show_password">"顯示密碼"</string> <string name="a11y_user_menu">"使用者選單"</string> @@ -21,6 +25,7 @@ <string name="action_done">"完成"</string> <string name="action_edit">"編輯"</string> <string name="action_enable">"啟用"</string> + <string name="action_end_poll">"結束投票"</string> <string name="action_forgot_password">"忘記密碼?"</string> <string name="action_forward">"轉寄"</string> <string name="action_invite">"邀請"</string> @@ -33,16 +38,19 @@ <string name="action_leave_room">"離開聊天室"</string> <string name="action_manage_account">"管理帳號"</string> <string name="action_manage_devices">"管理裝置"</string> - <string name="action_next">"下一個"</string> + <string name="action_next">"下一步"</string> <string name="action_no">"否"</string> <string name="action_not_now">"以後再說"</string> <string name="action_ok">"OK"</string> + <string name="action_open_settings">"開啟設定"</string> <string name="action_open_with">"用其他方式開啟"</string> <string name="action_quick_reply">"快速回覆"</string> <string name="action_quote">"引用"</string> <string name="action_react">"回應"</string> <string name="action_remove">"移除"</string> <string name="action_reply">"回覆"</string> + <string name="action_reply_in_thread">"在討論串中回覆"</string> + <string name="action_report_bug">"回報程式錯誤"</string> <string name="action_report_content">"檢舉內容"</string> <string name="action_retry">"再試一次"</string> <string name="action_retry_decryption">"再次嘗試解密"</string> @@ -52,7 +60,7 @@ <string name="action_send_message">"傳送訊息"</string> <string name="action_share">"分享"</string> <string name="action_share_link">"分享連結"</string> - <string name="action_skip">"跳過"</string> + <string name="action_skip">"略過"</string> <string name="action_start">"開始"</string> <string name="action_start_chat">"開始聊天"</string> <string name="action_start_verification">"開始驗證"</string> @@ -61,6 +69,8 @@ <string name="action_view_source">"檢視原始碼"</string> <string name="action_yes">"是"</string> <string name="common_about">"關於"</string> + <string name="common_acceptable_use_policy">"可接受使用政策"</string> + <string name="common_advanced_settings">"進階設定"</string> <string name="common_analytics">"分析"</string> <string name="common_audio">"音訊"</string> <string name="common_copyright">"著作權"</string> @@ -70,6 +80,7 @@ <string name="common_developer_options">"開發者選項"</string> <string name="common_edited_suffix">"(已編輯)"</string> <string name="common_editing">"編輯中"</string> + <string name="common_emote">"* %1$s %2$s"</string> <string name="common_encryption_enabled">"已啟用加密"</string> <string name="common_error">"錯誤"</string> <string name="common_file">"檔案"</string> @@ -77,6 +88,7 @@ <string name="common_forward_message">"訊息轉寄"</string> <string name="common_gif">"GIF"</string> <string name="common_image">"圖片"</string> + <string name="common_in_reply_to">"回覆 %1$s"</string> <string name="common_invite_unknown_profile">"找不到此 Matrix ID,因此可能沒有人會收到邀請。"</string> <string name="common_leaving_room">"正在離開聊天室"</string> <string name="common_link_copied_to_clipboard">"連結已複製到剪貼簿"</string> @@ -91,14 +103,20 @@ <string name="common_password">"密碼"</string> <string name="common_people">"夥伴"</string> <string name="common_permalink">"永久連結"</string> + <string name="common_permission">"權限"</string> + <string name="common_poll_total_votes">"總票數:%1$s"</string> <string name="common_poll_undisclosed_text">"結果將在投票結束後公佈"</string> <string name="common_privacy_policy">"隱私權政策"</string> + <string name="common_reaction">"回應"</string> <string name="common_reactions">"回應"</string> - <string name="common_refreshing">"重新整理…"</string> + <string name="common_refreshing">"重新整理中…"</string> <string name="common_replying_to">"正在回覆%1$s"</string> + <string name="common_report_a_bug">"回報程式錯誤"</string> + <string name="common_rich_text_editor">"格式化文字編輯器"</string> <string name="common_room_name">"聊天室名稱"</string> <string name="common_room_name_placeholder">"範例:您的計畫名稱"</string> <string name="common_search_results">"搜尋結果"</string> + <string name="common_security">"安全性"</string> <string name="common_select_your_server">"選擇您的伺服器"</string> <string name="common_sending">"傳送中…"</string> <string name="common_server_url">"伺服器 URL"</string> @@ -107,6 +125,8 @@ <string name="common_success">"成功"</string> <string name="common_suggestions">"建議"</string> <string name="common_syncing">"同步中"</string> + <string name="common_text">"文字"</string> + <string name="common_thread">"討論串"</string> <string name="common_topic">"主題"</string> <string name="common_unable_to_decrypt">"無法解密"</string> <string name="common_unable_to_invite_message">"無法發送邀請給一或多個使用者。"</string> @@ -117,6 +137,7 @@ <string name="common_verification_complete">"驗證完成"</string> <string name="common_video">"影片"</string> <string name="common_waiting">"等待中…"</string> + <string name="common_poll_summary">"投票:%1$s"</string> <string name="dialog_title_confirmation">"確認"</string> <string name="dialog_title_warning">"警告"</string> <string name="emoji_picker_category_activity">"活動"</string> @@ -148,6 +169,7 @@ <string name="screen_media_upload_preview_error_failed_sending">"無法上傳媒體檔案,請稍後再試。"</string> <string name="screen_notification_settings_additional_settings_section_title">"其他設定"</string> <string name="screen_notification_settings_direct_chats">"私訊"</string> + <string name="screen_notification_settings_edit_mode_mentions_and_keywords">"僅限提及與關鍵字"</string> <string name="screen_notification_settings_enable_notifications">"在這個裝置上開啟通知"</string> <string name="screen_notification_settings_group_chats">"群組聊天"</string> <string name="screen_notification_settings_mentions_section_title">"提及"</string> @@ -155,6 +177,7 @@ <string name="screen_notification_settings_system_notifications_action_required_content_link">"系統設定"</string> <string name="screen_notification_settings_system_notifications_turned_off">"已關閉系統通知"</string> <string name="screen_notification_settings_title">"通知"</string> + <string name="screen_settings_oidc_account">"帳號與裝置"</string> <string name="screen_share_location_title">"分享位置"</string> <string name="screen_share_my_location_action">"分享我的位置"</string> <string name="screen_share_open_apple_maps">"在 Apple Maps 中開啟"</string> @@ -168,7 +191,7 @@ <string name="dialog_title_error">"錯誤"</string> <string name="dialog_title_success">"成功"</string> <string name="screen_analytics_settings_help_us_improve">"分享匿名的使用數據以協助我們釐清問題"</string> - <string name="screen_analytics_settings_read_terms">"您可以到 %1$s 閱讀我們的條款。"</string> + <string name="screen_analytics_settings_read_terms">"您可以到%1$s閱讀我們的條款。"</string> <string name="screen_analytics_settings_read_terms_content_link">"這裡"</string> <string name="screen_report_content_block_user">"封鎖使用者"</string> </resources> diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 5d4d427af9..18f5e63078 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -71,6 +71,7 @@ <string name="action_take_photo">"Take photo"</string> <string name="action_view_source">"View Source"</string> <string name="action_yes">"Yes"</string> + <string name="action_edit_poll">"Edit poll"</string> <string name="common_about">"About"</string> <string name="common_acceptable_use_policy">"Acceptable use policy"</string> <string name="common_advanced_settings">"Advanced settings"</string> @@ -93,6 +94,7 @@ <string name="common_gif">"GIF"</string> <string name="common_image">"Image"</string> <string name="common_in_reply_to">"In reply to %1$s"</string> + <string name="common_install_apk_android">"Install APK"</string> <string name="common_invite_unknown_profile">"This Matrix ID can\'t be found, so the invite might not be received."</string> <string name="common_leaving_room">"Leaving room"</string> <string name="common_link_copied_to_clipboard">"Link copied to clipboard"</string> @@ -149,6 +151,7 @@ <string name="common_verification_complete">"Verification complete"</string> <string name="common_video">"Video"</string> <string name="common_waiting">"Waiting…"</string> + <string name="common_poll_end_confirmation">"Are you sure you want to end this poll?"</string> <string name="common_poll_summary">"Poll: %1$s"</string> <string name="dialog_title_confirmation">"Confirmation"</string> <string name="dialog_title_warning">"Warning"</string> diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index a609c9262d..f34cdfbdc0 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -56,7 +56,7 @@ private const val versionMinor = 2 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -private const val versionPatch = 2 +private const val versionPatch = 3 object Versions { val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch diff --git a/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt index 9360ce93ec..b39360c698 100644 --- a/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt +++ b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt @@ -25,9 +25,9 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SpaceId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.services.appnavstate.api.AppForegroundStateService -import io.element.android.services.appnavstate.api.NavigationState -import io.element.android.services.appnavstate.api.AppNavigationStateService import io.element.android.services.appnavstate.api.AppNavigationState +import io.element.android.services.appnavstate.api.AppNavigationStateService +import io.element.android.services.appnavstate.api.NavigationState import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -50,16 +50,15 @@ class DefaultAppNavigationStateService @Inject constructor( private val state = MutableStateFlow( AppNavigationState( - navigationState = NavigationState.Root, - isInForeground = true, - ) + navigationState = NavigationState.Root, + isInForeground = true, + ) ) override val appNavigationState: StateFlow<AppNavigationState> = state init { coroutineScope.launch { appForegroundStateService.start() - appForegroundStateService.isInForeground.collect { isInForeground -> state.getAndUpdate { it.copy(isInForeground = isInForeground) } } @@ -83,7 +82,7 @@ class DefaultAppNavigationStateService @Inject constructor( val currentValue = state.value.navigationState Timber.tag(loggerTag.value).d("Navigating to space $spaceId. Current state: $currentValue") val newValue: NavigationState.Space = when (currentValue) { - NavigationState.Root -> error("onNavigateToSession() must be called first") + NavigationState.Root -> return logError("onNavigateToSession()") is NavigationState.Session -> NavigationState.Space(owner, spaceId, currentValue) is NavigationState.Space -> NavigationState.Space(owner, spaceId, currentValue.parentSession) is NavigationState.Room -> NavigationState.Space(owner, spaceId, currentValue.parentSpace.parentSession) @@ -96,8 +95,8 @@ class DefaultAppNavigationStateService @Inject constructor( val currentValue = state.value.navigationState Timber.tag(loggerTag.value).d("Navigating to room $roomId. Current state: $currentValue") val newValue: NavigationState.Room = when (currentValue) { - NavigationState.Root -> error("onNavigateToSession() must be called first") - is NavigationState.Session -> error("onNavigateToSpace() must be called first") + NavigationState.Root -> return logError("onNavigateToSession()") + is NavigationState.Session -> return logError("onNavigateToSpace()") is NavigationState.Space -> NavigationState.Room(owner, roomId, currentValue) is NavigationState.Room -> NavigationState.Room(owner, roomId, currentValue.parentSpace) is NavigationState.Thread -> NavigationState.Room(owner, roomId, currentValue.parentRoom.parentSpace) @@ -109,9 +108,9 @@ class DefaultAppNavigationStateService @Inject constructor( val currentValue = state.value.navigationState Timber.tag(loggerTag.value).d("Navigating to thread $threadId. Current state: $currentValue") val newValue: NavigationState.Thread = when (currentValue) { - NavigationState.Root -> error("onNavigateToSession() must be called first") - is NavigationState.Session -> error("onNavigateToSpace() must be called first") - is NavigationState.Space -> error("onNavigateToRoom() must be called first") + NavigationState.Root -> return logError("onNavigateToSession()") + is NavigationState.Session -> return logError("onNavigateToSpace()") + is NavigationState.Space -> return logError("onNavigateToRoom()") is NavigationState.Room -> NavigationState.Thread(owner, threadId, currentValue) is NavigationState.Thread -> NavigationState.Thread(owner, threadId, currentValue.parentRoom) } @@ -123,10 +122,10 @@ class DefaultAppNavigationStateService @Inject constructor( Timber.tag(loggerTag.value).d("Leaving thread. Current state: $currentValue") if (!currentValue.assertOwner(owner)) return val newValue: NavigationState.Room = when (currentValue) { - NavigationState.Root -> error("onNavigateToSession() must be called first") - is NavigationState.Session -> error("onNavigateToSpace() must be called first") - is NavigationState.Space -> error("onNavigateToRoom() must be called first") - is NavigationState.Room -> error("onNavigateToThread() must be called first") + NavigationState.Root -> return logError("onNavigateToSession()") + is NavigationState.Session -> return logError("onNavigateToSpace()") + is NavigationState.Space -> return logError("onNavigateToRoom()") + is NavigationState.Room -> return logError("onNavigateToThread()") is NavigationState.Thread -> currentValue.parentRoom } state.getAndUpdate { it.copy(navigationState = newValue) } @@ -137,9 +136,9 @@ class DefaultAppNavigationStateService @Inject constructor( Timber.tag(loggerTag.value).d("Leaving room. Current state: $currentValue") if (!currentValue.assertOwner(owner)) return val newValue: NavigationState.Space = when (currentValue) { - NavigationState.Root -> error("onNavigateToSession() must be called first") - is NavigationState.Session -> error("onNavigateToSpace() must be called first") - is NavigationState.Space -> error("onNavigateToRoom() must be called first") + NavigationState.Root -> return logError("onNavigateToSession()") + is NavigationState.Session -> return logError("onNavigateToSpace()") + is NavigationState.Space -> return logError("onNavigateToRoom()") is NavigationState.Room -> currentValue.parentSpace is NavigationState.Thread -> currentValue.parentRoom.parentSpace } @@ -151,8 +150,8 @@ class DefaultAppNavigationStateService @Inject constructor( Timber.tag(loggerTag.value).d("Leaving space. Current state: $currentValue") if (!currentValue.assertOwner(owner)) return val newValue: NavigationState.Session = when (currentValue) { - NavigationState.Root -> error("onNavigateToSession() must be called first") - is NavigationState.Session -> error("onNavigateToSpace() must be called first") + NavigationState.Root -> return logError("onNavigateToSession()") + is NavigationState.Session -> return logError("onNavigateToSpace()") is NavigationState.Space -> currentValue.parentSession is NavigationState.Room -> currentValue.parentSpace.parentSession is NavigationState.Thread -> currentValue.parentRoom.parentSpace.parentSession @@ -167,6 +166,10 @@ class DefaultAppNavigationStateService @Inject constructor( state.getAndUpdate { it.copy(navigationState = NavigationState.Root) } } + private fun logError(logPrefix: String) { + Timber.tag(loggerTag.value).w("$logPrefix must be call first.") + } + private fun NavigationState.assertOwner(owner: String): Boolean { if (this.owner != owner) { Timber.tag(loggerTag.value).d("Can't leave current state as the owner is not the same (current = ${this.owner}, new = $owner)") diff --git a/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultNavigationStateServiceTest.kt b/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultNavigationStateServiceTest.kt index dd0e576c79..ab272478cc 100644 --- a/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultNavigationStateServiceTest.kt +++ b/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultNavigationStateServiceTest.kt @@ -29,7 +29,6 @@ import io.element.android.services.appnavstate.test.A_THREAD_OWNER import io.element.android.tests.testutils.runCancellableScopeTest import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first -import org.junit.Assert.assertThrows import org.junit.Test class DefaultNavigationStateServiceTest { @@ -63,8 +62,8 @@ class DefaultNavigationStateServiceTest { @Test fun testFailure() = runCancellableScopeTest { scope -> val service = createStateService(scope) - - assertThrows(IllegalStateException::class.java) { service.onNavigateToSpace(A_SPACE_OWNER, A_SPACE_ID) } + service.onNavigateToSpace(A_SPACE_OWNER, A_SPACE_ID) + assertThat(service.appNavigationState.value.navigationState).isEqualTo(NavigationState.Root) } private fun createStateService( diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_6,NEXUS_5,1.0,en].png index 6715960e25..a322a9a364 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15ec8227e84c01b8e9acd5271e379d989c8e4ed72b57c87d252a992a0d3f1a1a -size 15702 +oid sha256:bd2a831abc63de6366f2a4fbac653d551e29fbbbb698f5dc3ae51de04dbf6138 +size 15941 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_7,NEXUS_5,1.0,en].png index 2aca1f16a4..01188c15b3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08c11b272ebe9b4d8f9b76841b60a519d08a9e5f37970c66b78bd36319bcd53f -size 15782 +oid sha256:6b7dea2e1df15375ae07db4e6cf7b2a14a26c0ff1f2cbb11efdaea263d954550 +size 16067 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-D-32_32_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-D-32_32_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..1ac340b172 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-D-32_32_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09440b685e219b4a5f5dc78d2694707d7eeb1905d6fdea6395e3dcc941482ac5 +size 51846 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-D-32_32_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-D-32_32_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..540d962b0c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-D-32_32_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae8d6b561d358c7ebccae2da4281f3ea1b4d433f52de3c3b2008c1208c0c8bd7 +size 54037 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-N-32_33_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-N-32_33_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7c14f911dd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-N-32_33_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0cd0936ace9bac190f21680bf339cc23839e5f5d241522cb8f328b2db0887f6 +size 48293 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-N-32_33_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-N-32_33_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e14dee1d70 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-N-32_33_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc96f4b704396e0931a08a968139ee9d586cd1e14358b546361d11bc91e7bab5 +size 50373 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-D-32_32_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-D-33_33_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-D-32_32_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-D-33_33_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-N-32_33_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-N-33_34_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-N-32_33_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-N-33_34_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-D-33_33_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-D-34_34_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-D-33_33_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-D-34_34_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-N-33_34_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-N-34_35_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-N-33_34_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-N-34_35_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-D-35_35_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-D-36_36_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-D-35_35_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-D-36_36_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-N-35_36_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-N-36_37_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-N-35_36_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-N-36_37_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-36_36_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-37_37_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-36_36_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-37_37_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-36_36_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-37_37_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-36_36_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-37_37_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-36_36_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-37_37_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-36_36_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-37_37_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-36_37_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-37_38_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-36_37_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-37_38_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-36_37_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-37_38_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-36_37_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-37_38_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-36_37_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-37_38_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-36_37_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-37_38_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-D-37_37_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-D-38_38_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-D-37_37_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-D-38_38_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-N-37_38_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-N-38_39_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-N-37_38_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-N-38_39_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_10,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_10,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_10,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_11,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_11,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_11,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_12,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_12,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_12,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_13,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_13,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_13,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_14,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_14,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_14,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_14,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_15,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_15,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_15,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_15,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_16,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_16,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_16,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_16,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_17,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_17,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_17,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_17,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_18,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_18,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_18,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_18,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_19,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_19,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_19,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_19,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_20,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_20,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_20,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_20,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_21,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_21,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_21,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_21,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_6,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_6,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_6,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_7,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_7,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_7,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_8,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_8,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_8,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_9,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_9,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_9,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_10,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_10,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_10,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_11,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_11,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_11,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_12,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_12,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_12,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_13,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_13,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_13,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_14,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_14,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_14,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_14,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_15,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_15,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_15,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_15,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_16,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_16,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_16,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_16,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_17,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_17,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_17,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_17,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_18,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_18,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_18,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_18,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_19,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_19,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_19,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_19,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_20,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_20,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_20,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_20,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_21,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_21,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_21,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_21,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_6,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_6,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_6,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_7,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_7,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_7,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_8,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_8,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_8,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_9,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_9,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_9,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-D-39_39_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-D-40_40_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-D-39_39_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-D-40_40_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-N-39_40_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-N-40_41_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-N-39_40_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-N-40_41_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-40_40_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-41_41_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-40_40_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-41_41_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-40_40_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-41_41_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-40_40_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-41_41_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-40_41_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-41_42_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-40_41_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-41_42_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-40_41_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-41_42_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-40_41_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-41_42_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-D-41_41_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-D-42_42_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-D-41_41_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-D-42_42_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-N-41_42_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-N-42_43_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-N-41_42_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-N-42_43_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-42_42_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-43_43_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-42_42_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-43_43_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-42_42_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-43_43_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-42_42_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-43_43_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-42_43_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-43_44_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-42_43_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-43_44_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-42_43_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-43_44_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-42_43_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-43_44_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-D-43_43_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-D-44_44_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-D-43_43_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-D-44_44_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-N-43_44_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-N-44_45_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-N-43_44_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-N-44_45_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-D-44_44_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-D-45_45_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-D-44_44_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-D-45_45_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-N-44_45_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-N-45_46_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-N-44_45_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-N-45_46_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreator-D-11_11_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreator-D-11_11_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..1ac340b172 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreator-D-11_11_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09440b685e219b4a5f5dc78d2694707d7eeb1905d6fdea6395e3dcc941482ac5 +size 51846 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreator-N-11_12_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreator-N-11_12_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7c14f911dd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreator-N-11_12_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0cd0936ace9bac190f21680bf339cc23839e5f5d241522cb8f328b2db0887f6 +size 48293 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorEnded-D-12_12_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorEnded-D-12_12_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c8e001c9af --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorEnded-D-12_12_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88230f28f7761d8f76bc5a50dbc4ba08a694eaf0d64207257f5196c741fb7b52 +size 49078 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorEnded-N-12_13_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorEnded-N-12_13_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..495cb4c484 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorEnded-N-12_13_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6e31f773b884608499081ab3c7a27297edfbf8af76f502d9076b4753956e2db +size 45929 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorNoVotes-D-10_10_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorNoVotes-D-10_10_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2d9643e989 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorNoVotes-D-10_10_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fbe23f6b00973c497f791c37f9d884e5d6baa6e6f5376f10633be0cb3043d49 +size 52067 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorNoVotes-N-10_11_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorNoVotes-N-10_11_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..48329ff374 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorNoVotes-N-10_11_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a38081492746fdc6a4ae9f5408005456ab0f2143d4f937139d637aa39eabe77 +size 48435