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
-
- ```[tasklist]
- ### Tasklist
- - [ ] Task 1
- ```
-
- - [ ] QA signoff on completion
- - [ ] Design signoff on completion
- - [ ] Product signoff on completion
-
-
- ## Stretch goals
- None at this time
-
-
- ## 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 { 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 @@
"我們不會紀錄或剖繪您的個人資料"
"分享匿名的使用數據以協助我們釐清問題"
- "您可以到 %1$s 閱讀我們的條款。"
+ "您可以到%1$s閱讀我們的條款。"
"這裡"
"您可以在任何時候關閉它"
"我們不會和第三方分享您的資料"
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 @@
-
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 @@
+
+
+ "Probíhající hovor"
+ "Klepněte pro návrat k hovoru"
+ "☎️ Probíhá hovor"
+
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 @@
+
+
+ "點擊以返回到通話頁面"
+ "☎️ 通話中"
+
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 @@
"Jedná se o jednorázový proces, prosíme o strpení."
"Nastavení vašeho účtu"
+ "Nastavení můžete později změnit."
+ "Povolte oznámení a nezmeškejte žádnou zprávu"
"Hovory, hlasování, vyhledávání a další budou přidány koncem tohoto roku."
"Historie zpráv šifrovaných místností nebude v této aktualizaci k dispozici."
"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."
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 = 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 @@
"Используйте другого поставщика учетных записей, например, собственный частный сервер или рабочую учетную запись."
"Сменить поставщика учетной записи"
"Нам не удалось связаться с этим домашним сервером. Убедитесь, что вы правильно ввели URL-адрес домашнего сервера. Если URL-адрес указан правильно, обратитесь к администратору домашнего сервера за дополнительной помощью."
- "В настоящее время этот сервер не поддерживает скользящую синхронизацию."
+ "К сожалению данный сервер не поддерживает sliding sync."
"URL-адрес домашнего сервера"
- "Вы можете подключиться только к существующему серверу, поддерживающему скользящую синхронизацию. Администратору домашнего сервера потребуется настроить его. %1$s"
+ "Вы можете подключиться только к существующему серверу, поддерживающему sliding sync. Администратору домашнего сервера потребуется настроить его. %1$s"
"Какой адрес у вашего сервера?"
"Данная учетная запись была деактивирована."
"Неверное имя пользователя и/или пароль"
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 {
@@ -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? = 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 = 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 = 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): 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 @@
+
+
+
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 @@
"Knihovna fotografií a videí"
"Poloha"
"Hlasování"
+ "Formátování textu"
"Historie zpráv je momentálně v této místnosti nedostupná"
"Nepodařilo se načíst údaje o uživateli"
"Chtěli byste je pozvat zpět?"
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 @@
"附件"
"位置"
"投票"
+ "格式化文字"
"此聊天室只有您一個人"
"訊息已複製"
"您沒有權限在此聊天室傳送訊息"
@@ -17,8 +18,11 @@
"無法重設為預設模式,請再試一次。"
"無法設定模式,請再試一次。"
"所有訊息"
- "只限提及與關鍵字"
+ "僅限提及與關鍵字"
+ "較少"
+ "更多"
"重傳"
"無法傳送您的訊息"
+ "較少"
"移除"
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 @@
"Безопасное общение и совместная работа"
"Добро пожаловать в самый быстрый Element. Преимущество в скорости и простоте."
"Добро пожаловать в %1$s. Supercharged — это скорость и простота."
- "Будь в своей стихии"
+ "Будь c element"
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,
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 @@
"Zobrazit výsledky až po skončení hlasování"
"Anonymní hlasování"
"Volba %1$d"
+ "Opravdu chcete zrušit toto hlasování?"
+ "Zrušit hlasování"
"Otázka nebo téma"
"Čeho se hlasování týká?"
"Vytvořit hlasování"
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 @@
+
+
+ "新增選項"
+ "只在投票結束後顯示結果"
+ "隱藏票數"
+ "選項 %1$d"
+ "您確定要捨棄這項投票嗎?"
+ "捨棄投票"
+ "問題或主題"
+ "投什麼?"
+ "建立投票"
+
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 @@
+
+
+ "Zobrazované jméno"
+ "Vaše zobrazované jméno"
+ "Došlo k neznámé chybě a informace nelze změnit."
+ "Nelze aktualizovat profil"
+ "Upravit profil"
+ "Aktualizace profilu…"
+
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 @@
+
+
+ "顯示名稱"
+ "您的顯示名稱"
+ "無法更新個人檔案"
+ "編輯個人檔案"
+ "正在更新個人檔案…"
+
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 {
private class BugReporterUploadListener(
- private val sendingProgress: MutableState,
+ private val sendingProgress: MutableFloatState,
private val sendingAction: MutableState>
) : 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> = 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 @@
"附上螢幕截圖"
+ "如果有其他問題,你可以聯絡我。"
"聯絡我"
"編輯螢幕截圖"
"傳送螢幕截圖"
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 @@
"無法重設為預設模式,請再試一次。"
"無法設定模式,請再試一次。"
"所有訊息"
- "只限提及與關鍵字"
+ "僅限提及與關鍵字"
"封鎖"
"封鎖使用者"
"解除封鎖"
"解除封鎖使用者"
"離開聊天室"
"夥伴"
+ "安全性"
"主題"
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 @@
"建立新的對話或聊天室"
"所有聊天室"
+ "您似乎正在使用新的裝置。請使用另一個裝置進行驗證,以存取您的加密訊息。"
+ "驗證這是您本人"
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 @@
+ "似乎出了一點問題。有可能是因為等候逾時,或是請求被拒絕。"
+ "確認顯示在其他工作階段上的表情符號是否和下方的相同。"
+ "比對表情符號"
+ "新的工作階段已完成驗證。它能夠存取您的加密訊息,而其他使用者會將它視為可信任的。"
+ "為了存取被加密的歷史訊息,請證明這是您本人。"
+ "開啟一個現存的工作階段"
+ "重新嘗試驗證"
"我準備好了"
"等待比對"
- "不相符"
- "相符"
+ "表情符號是唯一的,請相互比對,確認它們的排列順序是否相同。"
+ "不一樣"
+ "一樣"
+ "準備開始驗證,請到您的其他工作階段接受請求。"
+ "等待接受請求"
"驗證已取消"
"開始"
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 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
+ suspend fun enterReplyMode(eventId: EventId): Result
+
suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result
suspend fun redactEvent(eventId: EventId, reason: String? = null): Result
@@ -184,7 +186,4 @@ interface MatrixRoom : Closeable {
suspend fun endPoll(pollStartId: EventId, text: String): Result
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 = 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 = withContext(roomDispatcher) {
+ runCatching {
+ inReplyToEventTimelineItem?.destroy()
+ inReplyToEventTimelineItem = null
+ inReplyToEventTimelineItem = innerRoom.getEventTimelineItemByEventId(eventId.value)
+ }
+ }
+
override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result = 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? = null
private set
+ override suspend fun enterReplyMode(eventId: EventId): Result {
+ return Result.success(Unit)
+ }
+
override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result {
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 @@
+
+
+ "Aby mohla aplikace používat fotoaparát, udělte prosím oprávnění v nastavení systému."
+ "Udělte prosím oprávnění v nastavení systému."
+ "Aby aplikace mohla používat mikrofon, udělte prosím oprávnění v nastavení systému."
+ "Aby aplikace mohla zobrazovat upozornění, udělte prosím oprávnění v nastavení systému."
+
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 @@
+
+
+ "Damit die Anwendung die Kamera verwenden kann, erteilen Sie bitte die Erlaubnis in den Systemeinstellungen."
+ "Bitte erteilen Sie die Erlaubnis in den Systemeinstellungen."
+ "Damit die Anwendung das Mikrofon verwenden kann, erteilen Sie bitte die Erlaubnis in den Systemeinstellungen."
+ "Damit die Anwendung Benachrichtigungen anzeigen kann, erteilen Sie bitte die Erlaubnis in den Systemeinstellungen."
+
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 @@
- "Pour permettre à l\'application d\'utiliser l\'appareil photo, veuillez accorder l\'autorisation dans les paramètres du système."
- "Veuillez accorder l\'autorisation dans les paramètres du système."
- "Pour permettre à l\'application d\'utiliser le microphone, veuillez accorder l\'autorisation dans les paramètres du système."
- "Pour permettre à l\'application d\'afficher les notifications, veuillez accorder l\'autorisation dans les paramètres du système."
+ "Pour permettre à l’application d’utiliser l’appareil photo, veuillez accorder l’autorisation dans les paramètres du système."
+ "Veuillez accorder l’autorisation dans les paramètres du système."
+ "Pour permettre à l\'application d’utiliser le microphone, veuillez accorder l’autorisation dans les paramètres du système."
+ "Pour permettre à l’application d’afficher les notifications, veuillez accorder l’autorisation dans les paramètres du système."
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 @@
+
+
+ "Чтобы приложение могло использовать камеру, предоставьте разрешение в системных настройках."
+ "Пожалуйста, предоставьте разрешение в системных настройках."
+ "Чтобы приложение могло использовать микрофон, предоставьте разрешение в системных настройках."
+ "Чтобы приложение отображало уведомления, предоставьте разрешение в системных настройках."
+
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 @@
"Přepnout seznam s odrážkami"
+ "Zavřít možnosti formátování"
"Přepnout blok kódu"
"Zpráva…"
+ "Vytvořit odkaz"
+ "Upravit odkaz"
"Použít tučný text"
"Použít kurzívu"
"Použít přeškrtnutí"
@@ -12,7 +15,10 @@
"Použít formát inline kódu"
"Nastavit odkaz"
"Přepnout číslovaný seznam"
+ "Otevřít možnosti psaní"
"Přepnout citaci"
+ "Odstranit odkaz"
"Zrušit odsazení"
+ "Odkaz"
"Přidat přílohu"
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 @@
"設定連結"
"切換數字編號"
"切換引用"
+ "移除連結"
"減少縮排"
"連結"
"新增附件"
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 @@
"Skrýt heslo"
+ "Pouze zmínky"
+ "Ztišeno"
+ "Hlasování"
+ "Hlasování ukončeno"
"Odeslat soubory"
"Zobrazit heslo"
"Uživatelské menu"
"Přijmout"
+ "Přidat na časovou osu"
"Zpět"
"Zrušit"
"Vybrat fotku"
@@ -34,16 +39,20 @@
"Zjistit více"
"Odejít"
"Opustit místnost"
+ "Spravovat účet"
+ "Spravovat zařízení"
"Další"
"Ne"
"Teď ne"
"OK"
+ "Otevřít nastavení"
"Otevřít v aplikaci"
"Rychlá odpověď"
"Citovat"
"Reagovat"
"Odstranit"
"Odpovědět"
+ "Odpovědět ve vlákně"
"Nahlásit chybu"
"Nahlásit obsah"
"Zkusit znovu"
@@ -64,6 +73,7 @@
"Ano"
"O aplikaci"
"Zásady používání"
+ "Pokročilá nastavení"
"Analytika"
"Zvuk"
"Bubliny"
@@ -82,6 +92,7 @@
"Přeposlat zprávu"
"GIF"
"Obrázek"
+ "V odpovědi na %1$s"
"Tento Matrix identifikátor nelze najít, takže pozvánka nemusí být přijata."
"Opuštění místnosti"
"Odkaz zkopírován do schránky"
@@ -96,14 +107,17 @@
"Heslo"
"Lidé"
"Trvalý odkaz"
+ "Oprávnění"
"Celkový počet hlasů: %1$s"
"Výsledky se zobrazí po skončení hlasování"
"Zásady ochrany osobních údajů"
+ "Reakce"
"Reakce"
"Obnovování…"
"Odpověď na %1$s"
"Nahlásit chybu"
"Zpráva odeslána"
+ "Editor formátovaného textu"
"Název místnosti"
"např. název vašeho projektu"
"Hledat někoho"
@@ -120,7 +134,9 @@
"Úspěch"
"Návrhy"
"Synchronizace"
+ "Text"
"Oznámení třetích stran"
+ "Vlákno"
"Téma"
"O čem je tato místnost?"
"Nelze dešifrovat"
@@ -133,6 +149,7 @@
"Ověření dokončeno"
"Video"
"Čekání…"
+ "Hlasování: %1$s"
"Potvrzení"
"Upozornění"
"Aktivity"
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 @@
"Nein"
"Nicht jetzt"
"OK"
+ "Einstellungen öffnen"
"Öffnen mit"
"Schnelle Antwort"
"Zitat"
@@ -106,6 +107,7 @@
"Passwort"
"Personen"
"Permalink"
+ "Erlaubnis"
"Stimmen insgesamt: %1$s"
"Die Ergebnisse werden nach Ende der Umfrage angezeigt"
"Datenschutzerklärung"
@@ -147,6 +149,7 @@
"Verifizierung abgeschlossen"
"Video"
"Warten…"
+ "Umfrage: %1$s"
"Bestätigung"
"Warnung"
"Aktivitäten"
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 @@
"Показать пароль"
"Меню пользователя"
"Разрешить"
+ "Добавить в хронологию"
"Назад"
"Отмена"
"Выбрать фото"
@@ -44,6 +45,7 @@
"Нет"
"Не сейчас"
"Ок"
+ "Открыть настройки"
"Открыть с помощью"
"Быстрый ответ"
"Цитата"
@@ -105,6 +107,7 @@
"Пароль"
"Пользователи"
"Постоянная ссылка"
+ "Разрешение"
"Всего голосов: %1$s"
"Результаты будут показаны после завершения опроса"
"Политика конфиденциальности"
@@ -146,6 +149,7 @@
"Проверка завершена"
"Видео"
"Ожидание…"
+ "Опрос: %1$s"
"Подтверждение"
"Предупреждение"
"Деятельность"
@@ -223,7 +227,7 @@
"Поделиться местоположением"
"Поделиться моим местоположением"
"Открыть в Apple Maps"
- "Открыть в Google Картах"
+ "Открыть в Google Maps"
"Открыть в OpenStreetMap"
"Поделиться этим местоположением"
"Местоположение"
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 @@
"隱藏密碼"
+ "僅限提及"
+ "已關閉通知"
+ "投票"
+ "投票已結束"
"傳送檔案"
"顯示密碼"
"使用者選單"
@@ -21,6 +25,7 @@
"完成"
"編輯"
"啟用"
+ "結束投票"
"忘記密碼?"
"轉寄"
"邀請"
@@ -33,16 +38,19 @@
"離開聊天室"
"管理帳號"
"管理裝置"
- "下一個"
+ "下一步"
"否"
"以後再說"
"OK"
+ "開啟設定"
"用其他方式開啟"
"快速回覆"
"引用"
"回應"
"移除"
"回覆"
+ "在討論串中回覆"
+ "回報程式錯誤"
"檢舉內容"
"再試一次"
"再次嘗試解密"
@@ -52,7 +60,7 @@
"傳送訊息"
"分享"
"分享連結"
- "跳過"
+ "略過"
"開始"
"開始聊天"
"開始驗證"
@@ -61,6 +69,8 @@
"檢視原始碼"
"是"
"關於"
+ "可接受使用政策"
+ "進階設定"
"分析"
"音訊"
"著作權"
@@ -70,6 +80,7 @@
"開發者選項"
"(已編輯)"
"編輯中"
+ "* %1$s %2$s"
"已啟用加密"
"錯誤"
"檔案"
@@ -77,6 +88,7 @@
"訊息轉寄"
"GIF"
"圖片"
+ "回覆 %1$s"
"找不到此 Matrix ID,因此可能沒有人會收到邀請。"
"正在離開聊天室"
"連結已複製到剪貼簿"
@@ -91,14 +103,20 @@
"密碼"
"夥伴"
"永久連結"
+ "權限"
+ "總票數:%1$s"
"結果將在投票結束後公佈"
"隱私權政策"
+ "回應"
"回應"
- "重新整理…"
+ "重新整理中…"
"正在回覆%1$s"
+ "回報程式錯誤"
+ "格式化文字編輯器"
"聊天室名稱"
"範例:您的計畫名稱"
"搜尋結果"
+ "安全性"
"選擇您的伺服器"
"傳送中…"
"伺服器 URL"
@@ -107,6 +125,8 @@
"成功"
"建議"
"同步中"
+ "文字"
+ "討論串"
"主題"
"無法解密"
"無法發送邀請給一或多個使用者。"
@@ -117,6 +137,7 @@
"驗證完成"
"影片"
"等待中…"
+ "投票:%1$s"
"確認"
"警告"
"活動"
@@ -148,6 +169,7 @@
"無法上傳媒體檔案,請稍後再試。"
"其他設定"
"私訊"
+ "僅限提及與關鍵字"
"在這個裝置上開啟通知"
"群組聊天"
"提及"
@@ -155,6 +177,7 @@
"系統設定"
"已關閉系統通知"
"通知"
+ "帳號與裝置"
"分享位置"
"分享我的位置"
"在 Apple Maps 中開啟"
@@ -168,7 +191,7 @@
"錯誤"
"成功"
"分享匿名的使用數據以協助我們釐清問題"
- "您可以到 %1$s 閱讀我們的條款。"
+ "您可以到%1$s閱讀我們的條款。"
"這裡"
"封鎖使用者"
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 @@
"Take photo"
"View Source"
"Yes"
+ "Edit poll"
"About"
"Acceptable use policy"
"Advanced settings"
@@ -93,6 +94,7 @@
"GIF"
"Image"
"In reply to %1$s"
+ "Install APK"
"This Matrix ID can\'t be found, so the invite might not be received."
"Leaving room"
"Link copied to clipboard"
@@ -149,6 +151,7 @@
"Verification complete"
"Video"
"Waiting…"
+ "Are you sure you want to end this poll?"
"Poll: %1$s"
"Confirmation"
"Warning"
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 = 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