diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml
deleted file mode 100644
index dd650c15e1..0000000000
--- a/.idea/dictionaries/bmarty.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- homeserver
-
-
-
\ No newline at end of file
diff --git a/.idea/dictionaries/shared.xml b/.idea/dictionaries/shared.xml
index 7c04ccd5e7..9353e11fd9 100644
--- a/.idea/dictionaries/shared.xml
+++ b/.idea/dictionaries/shared.xml
@@ -2,8 +2,10 @@
backstack
+ homeserver
kover
onboarding
+ showkase
textfields
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 78c39f93e6..733485fcef 100644
--- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt
+++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt
@@ -44,6 +44,7 @@ import io.element.android.appnav.root.RootPresenter
import io.element.android.appnav.root.RootView
import io.element.android.features.login.api.oidc.OidcAction
import io.element.android.features.login.api.oidc.OidcActionFlow
+import io.element.android.features.preferences.api.CacheService
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
@@ -54,7 +55,9 @@ import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize
@@ -65,6 +68,7 @@ class RootFlowNode @AssistedInject constructor(
@Assisted val buildContext: BuildContext,
@Assisted plugins: List,
private val authenticationService: MatrixAuthenticationService,
+ private val cacheService: CacheService,
private val matrixClientsHolder: MatrixClientsHolder,
private val presenter: RootPresenter,
private val bugReportEntryPoint: BugReportEntryPoint,
@@ -88,11 +92,19 @@ class RootFlowNode @AssistedInject constructor(
private fun observeLoggedInState() {
authenticationService.isLoggedIn()
.distinctUntilChanged()
- .onEach { isLoggedIn ->
- Timber.v("isLoggedIn=$isLoggedIn")
+ .combine(
+ cacheService.cacheIndex().onEach {
+ Timber.v("cacheIndex=$it")
+ matrixClientsHolder.removeAll()
+ }
+ ) { isLoggedIn, cacheIdx -> isLoggedIn to cacheIdx }
+ .onEach { pair ->
+ val isLoggedIn = pair.first
+ val cacheIndex = pair.second
+ Timber.v("isLoggedIn=$isLoggedIn, cacheIndex=$cacheIndex")
if (isLoggedIn) {
tryToRestoreLatestSession(
- onSuccess = { switchToLoggedInFlow(it) },
+ onSuccess = { switchToLoggedInFlow(it, cacheIndex) },
onFailure = { switchToNotLoggedInFlow() }
)
} else {
@@ -102,8 +114,8 @@ class RootFlowNode @AssistedInject constructor(
.launchIn(lifecycleScope)
}
- private fun switchToLoggedInFlow(sessionId: SessionId) {
- backstack.safeRoot(NavTarget.LoggedInFlow(sessionId))
+ private fun switchToLoggedInFlow(sessionId: SessionId, cacheIndex: Int) {
+ backstack.safeRoot(NavTarget.LoggedInFlow(sessionId, cacheIndex))
}
private fun switchToNotLoggedInFlow() {
@@ -163,7 +175,7 @@ class RootFlowNode @AssistedInject constructor(
object NotLoggedInFlow : NavTarget
@Parcelize
- data class LoggedInFlow(val sessionId: SessionId) : NavTarget
+ data class LoggedInFlow(val sessionId: SessionId, val cacheIndex: Int) : NavTarget
@Parcelize
object BugReport : NavTarget
@@ -235,8 +247,9 @@ class RootFlowNode @AssistedInject constructor(
}
private suspend fun attachSession(sessionId: SessionId): LoggedInFlowNode {
+ val cacheIndex = cacheService.cacheIndex().first()
return attachChild {
- backstack.newRoot(NavTarget.LoggedInFlow(sessionId))
+ backstack.newRoot(NavTarget.LoggedInFlow(sessionId, cacheIndex))
}
}
}
diff --git a/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/FakeAnalyticsService.kt b/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/FakeAnalyticsService.kt
index 3d969567f7..7ff0f50d9c 100644
--- a/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/FakeAnalyticsService.kt
+++ b/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/FakeAnalyticsService.kt
@@ -29,9 +29,9 @@ class FakeAnalyticsService(
didAskUserConsent: Boolean = false
): AnalyticsService {
- private var isEnabledFlow = MutableStateFlow(isEnabled)
- private var didAskUserConsentFlow = MutableStateFlow(didAskUserConsent)
- var capturedEvents = mutableListOf()
+ private val isEnabledFlow = MutableStateFlow(isEnabled)
+ private val didAskUserConsentFlow = MutableStateFlow(didAskUserConsent)
+ val capturedEvents = mutableListOf()
override fun getAvailableAnalyticsProviders(): List = emptyList()
diff --git a/features/invitelist/test/src/main/kotlin/io/element/android/features/invitelist/test/FakeSeenInvitesStore.kt b/features/invitelist/test/src/main/kotlin/io/element/android/features/invitelist/test/FakeSeenInvitesStore.kt
index 3716cd4456..486d3fb4a8 100644
--- a/features/invitelist/test/src/main/kotlin/io/element/android/features/invitelist/test/FakeSeenInvitesStore.kt
+++ b/features/invitelist/test/src/main/kotlin/io/element/android/features/invitelist/test/FakeSeenInvitesStore.kt
@@ -23,7 +23,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
class FakeSeenInvitesStore : SeenInvitesStore {
- private var existing = MutableStateFlow(emptySet())
+ private val existing = MutableStateFlow(emptySet())
private var provided: Set? = null
fun publishRoomIds(invites: Set) {
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaFactory.kt
index 7500f0d91e..ff2f8aaeeb 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaFactory.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaFactory.kt
@@ -21,7 +21,7 @@ import android.net.Uri
import androidx.core.net.toUri
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.messages.impl.timeline.util.FileExtensionExtractor
-import io.element.android.features.messages.impl.timeline.util.FileSizeFormatter
+import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
import io.element.android.libraries.androidutils.file.getFileName
import io.element.android.libraries.androidutils.file.getFileSize
import io.element.android.libraries.androidutils.file.getMimeType
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 c99ab46ab1..d9a12cf615 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
@@ -25,8 +25,8 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
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.features.messages.impl.timeline.util.FileExtensionExtractor
-import io.element.android.features.messages.impl.timeline.util.FileSizeFormatter
import io.element.android.features.messages.impl.timeline.util.toHtmlDocument
+import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt
index beb37f8e51..41daff47fd 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt
@@ -32,7 +32,7 @@ import io.element.android.features.messages.impl.timeline.factories.virtual.Time
import io.element.android.features.messages.impl.timeline.factories.virtual.TimelineItemVirtualFactory
import io.element.android.features.messages.impl.timeline.groups.TimelineItemGrouper
import io.element.android.features.messages.impl.timeline.util.FileExtensionExtractorWithoutValidation
-import io.element.android.features.messages.timeline.FakeFileSizeFormatter
+import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter
import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter
import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
diff --git a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/CacheService.kt b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/CacheService.kt
new file mode 100644
index 0000000000..0bc9285853
--- /dev/null
+++ b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/CacheService.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.preferences.api
+
+import kotlinx.coroutines.flow.Flow
+
+interface CacheService {
+ /**
+ * Returns a flow of the current cache index, can let the app to know when the
+ * cache has been cleared, for instance to restart the app.
+ * Will be a flow of Int, starting from 0, and incrementing each time the cache is cleared.
+ */
+ fun cacheIndex(): Flow
+}
diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts
index 631884fe47..1e76ee5c93 100644
--- a/features/preferences/impl/build.gradle.kts
+++ b/features/preferences/impl/build.gradle.kts
@@ -32,6 +32,7 @@ anvil {
dependencies {
implementation(projects.anvilannotations)
anvil(projects.anvilcodegen)
+ implementation(projects.libraries.androidutils)
implementation(projects.libraries.core)
implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api)
@@ -39,6 +40,7 @@ dependencies {
implementation(projects.libraries.featureflag.api)
implementation(projects.libraries.featureflag.ui)
implementation(projects.libraries.elementresources)
+ implementation(projects.libraries.network)
implementation(projects.libraries.testtags)
implementation(projects.libraries.uiStrings)
implementation(projects.features.rageshake.api)
@@ -47,6 +49,7 @@ dependencies {
implementation(projects.features.logout.api)
implementation(libs.datetime)
implementation(libs.accompanist.placeholder)
+ implementation(libs.coil.compose)
api(projects.features.preferences.api)
ksp(libs.showkase.processor)
@@ -62,6 +65,7 @@ dependencies {
testImplementation(projects.features.logout.impl)
testImplementation(projects.features.analytics.test)
testImplementation(projects.features.analytics.impl)
+ testImplementation(projects.tests.testutils)
androidTestImplementation(libs.test.junitext)
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/FileSizeFormatter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultCacheService.kt
similarity index 56%
rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/FileSizeFormatter.kt
rename to features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultCacheService.kt
index 4ede9b7f21..7675ec3dd6 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/FileSizeFormatter.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultCacheService.kt
@@ -14,25 +14,26 @@
* limitations under the License.
*/
-package io.element.android.features.messages.impl.timeline.util
+package io.element.android.features.preferences.impl
-import android.content.Context
-import android.text.format.Formatter
import com.squareup.anvil.annotations.ContributesBinding
+import io.element.android.features.preferences.api.CacheService
import io.element.android.libraries.di.AppScope
-import io.element.android.libraries.di.ApplicationContext
+import io.element.android.libraries.di.SingleIn
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject
-interface FileSizeFormatter {
- /**
- * Formats a content size to be in the form of bytes, kilobytes, megabytes, etc.
- */
- fun format(fileSize: Long): String
-}
-
+@SingleIn(AppScope::class)
@ContributesBinding(AppScope::class)
-class AndroidFileSizeFormatter @Inject constructor(@ApplicationContext private val context: Context) : FileSizeFormatter {
- override fun format(fileSize: Long): String {
- return Formatter.formatShortFileSize(context, fileSize)
+class DefaultCacheService @Inject constructor() : CacheService {
+ private val cacheIndexState = MutableStateFlow(0)
+
+ override fun cacheIndex(): Flow {
+ return cacheIndexState
+ }
+
+ fun incrementCacheIndex() {
+ cacheIndexState.value++
}
}
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt
index b79484592f..bb3879b129 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt
@@ -20,4 +20,5 @@ import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
sealed interface DeveloperSettingsEvents {
data class UpdateEnabledFeature(val feature: FeatureUiModel, val isEnabled: Boolean) : DeveloperSettingsEvents
+ object ClearCache: DeveloperSettingsEvents
}
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt
index 9f9cd636eb..d4430dd2e3 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt
@@ -18,12 +18,18 @@ package io.element.android.features.preferences.impl.developer
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateMapOf
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshots.SnapshotStateMap
+import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
+import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase
+import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter
+import io.element.android.libraries.architecture.execute
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.featureflag.api.Feature
import io.element.android.libraries.featureflag.api.FeatureFlagService
@@ -36,6 +42,8 @@ import javax.inject.Inject
class DeveloperSettingsPresenter @Inject constructor(
private val featureFlagService: FeatureFlagService,
+ private val computeCacheSizeUseCase: ComputeCacheSizeUseCase,
+ private val clearCacheUseCase: ClearCacheUseCase,
) : Presenter {
@Composable
@@ -47,6 +55,12 @@ class DeveloperSettingsPresenter @Inject constructor(
val enabledFeatures = remember {
mutableStateMapOf()
}
+ val cacheSize = remember {
+ mutableStateOf>(Async.Uninitialized)
+ }
+ val clearCacheAction = remember {
+ mutableStateOf>(Async.Uninitialized)
+ }
LaunchedEffect(Unit) {
FeatureFlags.values().forEach { feature ->
features[feature.key] = feature
@@ -55,6 +69,10 @@ class DeveloperSettingsPresenter @Inject constructor(
}
val featureUiModels = createUiModels(features, enabledFeatures)
val coroutineScope = rememberCoroutineScope()
+ // Compute cache size each time the clear cache action value is changed
+ LaunchedEffect(clearCacheAction.value) {
+ computeCacheSize(cacheSize)
+ }
fun handleEvents(event: DeveloperSettingsEvents) {
when (event) {
@@ -64,11 +82,14 @@ class DeveloperSettingsPresenter @Inject constructor(
event.feature,
event.isEnabled
)
+ DeveloperSettingsEvents.ClearCache -> coroutineScope.clearCache(clearCacheAction)
}
}
return DeveloperSettingsState(
features = featureUiModels.toImmutableList(),
+ cacheSize = cacheSize.value,
+ clearCacheAction = clearCacheAction.value,
eventSink = ::handleEvents
)
}
@@ -103,6 +124,18 @@ class DeveloperSettingsPresenter @Inject constructor(
enabledFeatures[featureUiModel.key] = enabled
}
}
+
+ private fun CoroutineScope.computeCacheSize(cacheSize: MutableState>) = launch {
+ suspend {
+ computeCacheSizeUseCase()
+ }.execute(cacheSize)
+ }
+
+ private fun CoroutineScope.clearCache(clearCacheAction: MutableState>) = launch {
+ suspend {
+ clearCacheUseCase()
+ }.execute(clearCacheAction)
+ }
}
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt
index 53ff80967e..61205e7f7d 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt
@@ -16,10 +16,13 @@
package io.element.android.features.preferences.impl.developer
+import io.element.android.libraries.architecture.Async
import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
import kotlinx.collections.immutable.ImmutableList
-data class DeveloperSettingsState(
+data class DeveloperSettingsState constructor(
val features: ImmutableList,
+ val cacheSize: Async,
+ val clearCacheAction: Async,
val eventSink: (DeveloperSettingsEvents) -> Unit
)
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt
index f69f73e6e5..de94bd6664 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt
@@ -17,16 +17,20 @@
package io.element.android.features.preferences.impl.developer
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import io.element.android.libraries.architecture.Async
import io.element.android.libraries.featureflag.ui.model.aFeatureUiModelList
open class DeveloperSettingsStateProvider : PreviewParameterProvider {
override val values: Sequence
get() = sequenceOf(
aDeveloperSettingsState(),
+ aDeveloperSettingsState().copy(clearCacheAction = Async.Loading()),
)
}
fun aDeveloperSettingsState() = DeveloperSettingsState(
features = aFeatureUiModelList(),
+ cacheSize = Async.Success("1.2 MB"),
+ clearCacheAction = Async.Uninitialized,
eventSink = {}
)
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt
index 697081c397..027b3cfd1d 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt
@@ -16,11 +16,15 @@
package io.element.android.features.preferences.impl.developer
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Delete
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
+import io.element.android.libraries.architecture.Async
+import io.element.android.libraries.architecture.isLoading
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
import io.element.android.libraries.designsystem.components.preferences.PreferenceView
@@ -52,6 +56,20 @@ fun DeveloperSettingsView(
onClick = onOpenShowkase
)
}
+ val cache = state.cacheSize
+ PreferenceCategory(title = "Cache") {
+ PreferenceText(
+ title = "Clear cache",
+ icon = Icons.Default.Delete,
+ currentValue = cache.dataOrNull(),
+ loadingCurrentValue = state.cacheSize.isLoading() || state.clearCacheAction.isLoading(),
+ onClick = {
+ if (state.clearCacheAction.isLoading().not()) {
+ state.eventSink(DeveloperSettingsEvents.ClearCache)
+ }
+ }
+ )
+ }
}
}
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt
new file mode 100644
index 0000000000..f7b0d01130
--- /dev/null
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoilApi::class)
+
+package io.element.android.features.preferences.impl.tasks
+
+import android.content.Context
+import coil.Coil
+import coil.annotation.ExperimentalCoilApi
+import com.squareup.anvil.annotations.ContributesBinding
+import io.element.android.features.preferences.impl.DefaultCacheService
+import io.element.android.libraries.core.coroutine.CoroutineDispatchers
+import io.element.android.libraries.di.ApplicationContext
+import io.element.android.libraries.di.SessionScope
+import io.element.android.libraries.matrix.api.MatrixClient
+import kotlinx.coroutines.withContext
+import okhttp3.OkHttpClient
+import javax.inject.Inject
+import javax.inject.Provider
+
+interface ClearCacheUseCase {
+ suspend operator fun invoke()
+}
+
+@ContributesBinding(SessionScope::class)
+class DefaultClearCacheUseCase @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val matrixClient: MatrixClient,
+ private val coroutineDispatchers: CoroutineDispatchers,
+ private val defaultCacheIndexProvider: DefaultCacheService,
+ private val okHttpClient: Provider,
+) : ClearCacheUseCase {
+ override suspend fun invoke() = withContext(coroutineDispatchers.io) {
+ // Clear Matrix cache
+ matrixClient.clearCache()
+ // Clear Coil cache
+ Coil.imageLoader(context).let {
+ it.diskCache?.clear()
+ it.memoryCache?.clear()
+ }
+ // Clear OkHttp cache
+ okHttpClient.get().cache?.delete()
+ // Clear app cache
+ context.cacheDir.deleteRecursively()
+ // Ensure the app is restarted
+ defaultCacheIndexProvider.incrementCacheIndex()
+ }
+}
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ComputeCacheSizeUseCase.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ComputeCacheSizeUseCase.kt
new file mode 100644
index 0000000000..661f6493ec
--- /dev/null
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ComputeCacheSizeUseCase.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.preferences.impl.tasks
+
+import android.content.Context
+import com.squareup.anvil.annotations.ContributesBinding
+import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
+import io.element.android.libraries.androidutils.file.getSizeOfFiles
+import io.element.android.libraries.core.coroutine.CoroutineDispatchers
+import io.element.android.libraries.di.ApplicationContext
+import io.element.android.libraries.di.SessionScope
+import io.element.android.libraries.matrix.api.MatrixClient
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+interface ComputeCacheSizeUseCase {
+ suspend operator fun invoke(): String
+}
+
+@ContributesBinding(SessionScope::class)
+class DefaultComputeCacheSizeUseCase @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val matrixClient: MatrixClient,
+ private val coroutineDispatchers: CoroutineDispatchers,
+ private val fileSizeFormatter: FileSizeFormatter,
+) : ComputeCacheSizeUseCase {
+ override suspend fun invoke(): String = withContext(coroutineDispatchers.io) {
+ var cumulativeSize = 0L
+ cumulativeSize += matrixClient.getCacheSize()
+ // - 4096 to not include the size fo the folder
+ cumulativeSize += (context.cacheDir.getSizeOfFiles() - 4096).coerceAtLeast(0)
+ fileSizeFormatter.format(cumulativeSize)
+ }
+}
diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt
index 6b7c8c2df4..226140647d 100644
--- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt
+++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt
@@ -20,6 +20,9 @@ import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
+import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase
+import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase
+import io.element.android.libraries.architecture.Async
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import kotlinx.coroutines.test.runTest
@@ -29,13 +32,17 @@ class DeveloperSettingsPresenterTest {
@Test
fun `present - ensures initial state is correct`() = runTest {
val presenter = DeveloperSettingsPresenter(
- FakeFeatureFlagService()
+ FakeFeatureFlagService(),
+ FakeComputeCacheSizeUseCase(),
+ FakeClearCacheUseCase(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.features).isEmpty()
+ assertThat(initialState.clearCacheAction).isEqualTo(Async.Uninitialized)
+ assertThat(initialState.cacheSize).isEqualTo(Async.Uninitialized)
cancelAndIgnoreRemainingEvents()
}
}
@@ -43,7 +50,9 @@ class DeveloperSettingsPresenterTest {
@Test
fun `present - ensures feature list is loaded`() = runTest {
val presenter = DeveloperSettingsPresenter(
- FakeFeatureFlagService()
+ FakeFeatureFlagService(),
+ FakeComputeCacheSizeUseCase(),
+ FakeClearCacheUseCase(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@@ -58,7 +67,9 @@ class DeveloperSettingsPresenterTest {
@Test
fun `present - ensures state is updated when enabled feature event is triggered`() = runTest {
val presenter = DeveloperSettingsPresenter(
- FakeFeatureFlagService()
+ FakeFeatureFlagService(),
+ FakeComputeCacheSizeUseCase(),
+ FakeClearCacheUseCase(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@@ -74,4 +85,28 @@ class DeveloperSettingsPresenterTest {
cancelAndIgnoreRemainingEvents()
}
}
+
+ @Test
+ fun `present - clear cache`() = runTest {
+ val clearCacheUseCase = FakeClearCacheUseCase()
+ val presenter = DeveloperSettingsPresenter(
+ FakeFeatureFlagService(),
+ FakeComputeCacheSizeUseCase(),
+ clearCacheUseCase,
+ )
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ skipItems(1)
+ val initialState = awaitItem()
+ assertThat(clearCacheUseCase.executeHasBeenCalled).isFalse()
+ initialState.eventSink(DeveloperSettingsEvents.ClearCache)
+ val stateAfterEvent = awaitItem()
+ assertThat(stateAfterEvent.clearCacheAction).isInstanceOf(Async.Loading::class.java)
+ skipItems(1)
+ assertThat(awaitItem().clearCacheAction).isInstanceOf(Async.Success::class.java)
+ assertThat(clearCacheUseCase.executeHasBeenCalled).isTrue()
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
}
diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeClearCacheUseCase.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeClearCacheUseCase.kt
new file mode 100644
index 0000000000..7415e09e96
--- /dev/null
+++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeClearCacheUseCase.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.preferences.impl.tasks
+
+import io.element.android.tests.testutils.simulateLongTask
+
+class FakeClearCacheUseCase : ClearCacheUseCase {
+ var executeHasBeenCalled = false
+ private set
+
+ override suspend fun invoke() = simulateLongTask {
+ executeHasBeenCalled = true
+ }
+}
diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeComputeCacheSizeUseCase.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeComputeCacheSizeUseCase.kt
new file mode 100644
index 0000000000..fa8556630f
--- /dev/null
+++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeComputeCacheSizeUseCase.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.preferences.impl.tasks
+
+import io.element.android.tests.testutils.simulateLongTask
+
+class FakeComputeCacheSizeUseCase : ComputeCacheSizeUseCase {
+ override suspend fun invoke() = simulateLongTask {
+ "O kB"
+ }
+}
diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt
index 874da06acb..91f761bda9 100755
--- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt
+++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt
@@ -52,6 +52,7 @@ import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.util.Locale
import javax.inject.Inject
+import javax.inject.Provider
/**
* BugReporter creates and sends the bug reports.
@@ -62,7 +63,7 @@ class DefaultBugReporter @Inject constructor(
private val screenshotHolder: ScreenshotHolder,
private val crashDataStore: CrashDataStore,
private val coroutineDispatchers: CoroutineDispatchers,
- private val okHttpClient: OkHttpClient,
+ private val okHttpClient: Provider,
/*
private val activeSessionHolder: ActiveSessionHolder,
private val versionProvider: VersionProvider,
@@ -339,7 +340,7 @@ class DefaultBugReporter @Inject constructor(
// trigger the request
try {
- mBugReportCall = okHttpClient.newCall(request)
+ mBugReportCall = okHttpClient.get().newCall(request)
response = mBugReportCall!!.execute()
responseCode = response.code
} catch (e: Exception) {
diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt
index 269407d3b5..ea214ff683 100644
--- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt
+++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt
@@ -17,9 +17,11 @@
package io.element.android.libraries.androidutils.file
import android.content.Context
+import androidx.annotation.WorkerThread
import io.element.android.libraries.core.data.tryOrNull
import timber.log.Timber
import java.io.File
+import java.util.Locale
import java.util.UUID
fun File.safeDelete() {
@@ -52,3 +54,99 @@ fun Context.createTmpFile(baseDir: File = cacheDir, extension: String? = null):
val suffix = extension?.let { ".$extension" }
return File.createTempFile(UUID.randomUUID().toString(), suffix, baseDir).apply { mkdirs() }
}
+
+// Implementation should return true in case of success
+typealias ActionOnFile = (file: File) -> Boolean
+
+/* ==========================================================================================
+ * Log
+ * ========================================================================================== */
+
+fun lsFiles(context: Context) {
+ Timber.v("Content of cache dir:")
+ recursiveActionOnFile(context.cacheDir, ::logAction)
+
+ Timber.v("Content of files dir:")
+ recursiveActionOnFile(context.filesDir, ::logAction)
+}
+
+private fun logAction(file: File): Boolean {
+ if (file.isDirectory) {
+ Timber.v(file.toString())
+ } else {
+ Timber.v("$file ${file.length()} bytes")
+ }
+ return true
+}
+
+/* ==========================================================================================
+ * Private
+ * ========================================================================================== */
+
+/**
+ * Return true in case of success.
+ */
+private fun recursiveActionOnFile(file: File, action: ActionOnFile): Boolean {
+ if (file.isDirectory) {
+ file.list()?.forEach {
+ val result = recursiveActionOnFile(File(file, it), action)
+
+ if (!result) {
+ // Break the loop
+ return false
+ }
+ }
+ }
+
+ return action.invoke(file)
+}
+
+/**
+ * Get the file extension of a fileUri or a filename.
+ *
+ * @param fileUri the fileUri (can be a simple filename)
+ * @return the file extension, in lower case, or null is extension is not available or empty
+ */
+fun getFileExtension(fileUri: String): String? {
+ var reducedStr = fileUri
+
+ if (reducedStr.isNotEmpty()) {
+ // Remove fragment
+ reducedStr = reducedStr.substringBeforeLast('#')
+
+ // Remove query
+ reducedStr = reducedStr.substringBeforeLast('?')
+
+ // Remove path
+ val filename = reducedStr.substringAfterLast('/')
+
+ // Contrary to method MimeTypeMap.getFileExtensionFromUrl, we do not check the pattern
+ // See https://stackoverflow.com/questions/14320527/android-should-i-use-mimetypemap-getfileextensionfromurl-bugs
+ if (filename.isNotEmpty()) {
+ val dotPos = filename.lastIndexOf('.')
+ if (0 <= dotPos) {
+ val ext = filename.substring(dotPos + 1)
+
+ if (ext.isNotBlank()) {
+ return ext.lowercase(Locale.ROOT)
+ }
+ }
+ }
+ }
+
+ return null
+}
+
+/* ==========================================================================================
+ * Size
+ * ========================================================================================== */
+
+@WorkerThread
+fun File.getSizeOfFiles(): Long {
+ return walkTopDown()
+ .onEnter {
+ Timber.v("Get size of ${it.absolutePath}")
+ true
+ }
+ .sumOf { it.length() }
+}
diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatter.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatter.kt
new file mode 100644
index 0000000000..9cd70febcc
--- /dev/null
+++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatter.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.libraries.androidutils.filesize
+
+import android.content.Context
+import android.os.Build
+import android.text.format.Formatter
+import com.squareup.anvil.annotations.ContributesBinding
+import io.element.android.libraries.di.AppScope
+import io.element.android.libraries.di.ApplicationContext
+import javax.inject.Inject
+
+@ContributesBinding(AppScope::class)
+class AndroidFileSizeFormatter @Inject constructor(
+ @ApplicationContext private val context: Context,
+ ) : FileSizeFormatter {
+ override fun format(fileSize: Long, useShortFormat: Boolean): String {
+ // Since Android O, the system considers that 1kB = 1000 bytes instead of 1024 bytes.
+ // We want to avoid that.
+ val normalizedSize = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
+ fileSize
+ } else {
+ // First convert the size
+ when {
+ fileSize < 1024 -> fileSize
+ fileSize < 1024 * 1024 -> fileSize * 1000 / 1024
+ fileSize < 1024 * 1024 * 1024 -> fileSize * 1000 / 1024 * 1000 / 1024
+ else -> fileSize * 1000 / 1024 * 1000 / 1024 * 1000 / 1024
+ }
+ }
+
+ return if (useShortFormat) {
+ Formatter.formatShortFileSize(context, normalizedSize)
+ } else {
+ Formatter.formatFileSize(context, normalizedSize)
+ }
+ }
+}
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/FakeFileSizeFormatter.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FakeFileSizeFormatter.kt
similarity index 78%
rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/FakeFileSizeFormatter.kt
rename to libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FakeFileSizeFormatter.kt
index 4ff65b0146..32c0239428 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/FakeFileSizeFormatter.kt
+++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FakeFileSizeFormatter.kt
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package io.element.android.features.messages.timeline
-
-import io.element.android.features.messages.impl.timeline.util.FileSizeFormatter
+package io.element.android.libraries.androidutils.filesize
class FakeFileSizeFormatter : FileSizeFormatter {
- override fun format(fileSize: Long): String {
+ override fun format(fileSize: Long, useShortFormat: Boolean): String {
return "$fileSize Bytes"
}
}
diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FileSizeFormatter.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FileSizeFormatter.kt
new file mode 100644
index 0000000000..7be38bf9bd
--- /dev/null
+++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FileSizeFormatter.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.libraries.androidutils.filesize
+
+interface FileSizeFormatter {
+ /**
+ * Formats a content size to be in the form of bytes, kilobytes, megabytes, etc.
+ */
+ fun format(fileSize: Long, useShortFormat: Boolean = true): String
+}
diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceText.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceText.kt
index 2172f4518a..2195be296f 100644
--- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceText.kt
+++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceText.kt
@@ -17,6 +17,7 @@
package io.element.android.libraries.designsystem.components.preferences
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -26,7 +27,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
import androidx.compose.foundation.progressSemantics
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BugReport
@@ -55,7 +55,7 @@ fun PreferenceText(
tintColor: Color? = null,
onClick: () -> Unit = {},
) {
- val minHeight = if (subtitle == null) preferenceMinHeightOnlyTitle else preferenceMinHeight
+ val minHeight = if (subtitle == null) preferenceMinHeightOnlyTitle else preferenceMinHeight
Box(
modifier = modifier
.fillMaxWidth()
@@ -69,9 +69,10 @@ fun PreferenceText(
.padding(vertical = preferencePaddingVertical)
) {
PreferenceIcon(icon = icon, tintColor = tintColor)
- Column(modifier = Modifier
- .weight(1f)
- .align(Alignment.CenterVertically)
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .align(Alignment.CenterVertically)
) {
if (title != null) {
Text(
@@ -92,15 +93,24 @@ fun PreferenceText(
}
}
if (currentValue != null) {
- Text(currentValue, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary)
- Spacer(Modifier.width(16.dp))
+ Text(
+ modifier = Modifier
+ .align(Alignment.CenterVertically)
+ .padding(horizontal = 16.dp),
+ text = currentValue,
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.secondary,
+ )
} else if (loadingCurrentValue) {
- CircularProgressIndicator(modifier = Modifier
- .progressSemantics()
- .size(20.dp), strokeWidth = 2.dp)
- Spacer(Modifier.width(16.dp))
+ CircularProgressIndicator(
+ modifier = Modifier
+ .progressSemantics()
+ .padding(horizontal = 16.dp)
+ .size(20.dp)
+ .align(Alignment.CenterVertically),
+ strokeWidth = 2.dp
+ )
}
-
}
}
}
@@ -111,9 +121,39 @@ internal fun PreferenceTextPreview() = ElementThemedPreview { ContentToPreview()
@Composable
private fun ContentToPreview() {
- PreferenceText(
- title = "Title",
- subtitle = "Some content",
- icon = Icons.Default.BugReport,
- )
+ Column(
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ PreferenceText(
+ title = "Title",
+ icon = Icons.Default.BugReport,
+ )
+ PreferenceText(
+ title = "Title",
+ subtitle = "Some content",
+ icon = Icons.Default.BugReport,
+ )
+ PreferenceText(
+ title = "Title",
+ subtitle = "Some content",
+ icon = Icons.Default.BugReport,
+ currentValue = "123",
+ )
+ PreferenceText(
+ title = "Title",
+ subtitle = "Some content",
+ icon = Icons.Default.BugReport,
+ loadingCurrentValue = true,
+ )
+ PreferenceText(
+ title = "Title",
+ icon = Icons.Default.BugReport,
+ currentValue = "123",
+ )
+ PreferenceText(
+ title = "Title",
+ icon = Icons.Default.BugReport,
+ loadingCurrentValue = true,
+ )
+ }
}
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt
index 4a018e18da..0bd7a8ab22 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt
@@ -50,6 +50,12 @@ interface MatrixClient : Closeable {
fun sessionVerificationService(): SessionVerificationService
fun pushersService(): PushersService
fun notificationService(): NotificationService
+ suspend fun getCacheSize(): Long
+
+ /**
+ * Will close the client and delete the cache data.
+ */
+ suspend fun clearCache()
suspend fun logout()
suspend fun loadUserDisplayName(): Result
suspend fun loadUserAvatarURLString(): Result
diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts
index 1ac5b4b507..5709b5a6d7 100644
--- a/libraries/matrix/impl/build.gradle.kts
+++ b/libraries/matrix/impl/build.gradle.kts
@@ -32,6 +32,7 @@ dependencies {
// api(projects.libraries.rustsdk)
implementation(libs.matrix.sdk)
implementation(projects.libraries.di)
+ implementation(projects.libraries.androidutils)
implementation(projects.services.toolbox.api)
api(projects.libraries.matrix.api)
implementation(libs.dagger)
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt
index 268e09c764..be31855ecd 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt
@@ -18,6 +18,8 @@
package io.element.android.libraries.matrix.impl
+import io.element.android.libraries.androidutils.file.getSizeOfFiles
+import io.element.android.libraries.androidutils.file.safeDelete
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.ProgressCallback
@@ -336,15 +338,25 @@ class RustMatrixClient constructor(
client.destroy()
}
+ override suspend fun getCacheSize(): Long {
+ // Do not use client.userId since it can throw if client has been closed (during clear cache)
+ return baseDirectory.getCacheSize(userID = sessionId.value)
+ }
+
+ override suspend fun clearCache() {
+ close()
+ baseDirectory.deleteSessionDirectory(userID = sessionId.value, deleteCryptoDb = false)
+ }
+
override suspend fun logout() = withContext(dispatchers.io) {
try {
client.logout()
} catch (failure: Throwable) {
Timber.e(failure, "Fail to call logout on HS. Still delete local files.")
}
- baseDirectory.deleteSessionDirectory(userID = client.userId())
- sessionStore.removeSession(client.userId())
close()
+ baseDirectory.deleteSessionDirectory(userID = sessionId.value, deleteCryptoDb = true)
+ sessionStore.removeSession(sessionId.value)
}
override suspend fun loadUserDisplayName(): Result = withContext(dispatchers.io) {
@@ -378,11 +390,48 @@ class RustMatrixClient constructor(
override fun roomMembershipObserver(): RoomMembershipObserver = roomMembershipObserver
- private fun File.deleteSessionDirectory(userID: String): Boolean {
+ private suspend fun File.getCacheSize(
+ userID: String,
+ includeCryptoDb: Boolean = false,
+ ): Long = withContext(dispatchers.io) {
// Rust sanitises the user ID replacing invalid characters with an _
val sanitisedUserID = userID.replace(":", "_")
- val sessionDirectory = File(this, sanitisedUserID)
- return sessionDirectory.deleteRecursively()
+ val sessionDirectory = File(this@getCacheSize, sanitisedUserID)
+ if (includeCryptoDb) {
+ sessionDirectory.getSizeOfFiles()
+ } else {
+ listOf(
+ "matrix-sdk-state.sqlite3",
+ "matrix-sdk-state.sqlite3-shm",
+ "matrix-sdk-state.sqlite3-wal",
+ ).map { fileName ->
+ File(sessionDirectory, fileName)
+ }.sumOf { file ->
+ file.length()
+ }
+ }
+ }
+
+ private suspend fun File.deleteSessionDirectory(
+ userID: String,
+ deleteCryptoDb: Boolean = false,
+ ): Boolean = withContext(dispatchers.io) {
+ // Rust sanitises the user ID replacing invalid characters with an _
+ val sanitisedUserID = userID.replace(":", "_")
+ val sessionDirectory = File(this@deleteSessionDirectory, sanitisedUserID)
+ if (deleteCryptoDb) {
+ // Delete the folder and all its content
+ sessionDirectory.deleteRecursively()
+ } else {
+ // Delete only the state.db file
+ sessionDirectory.listFiles().orEmpty()
+ .filter { it.name.contains("matrix-sdk-state") }
+ .forEach { file ->
+ Timber.w("Deleting file ${file.name}...")
+ file.safeDelete()
+ }
+ true
+ }
}
}
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt
index 8b38a74457..eb5e4624d6 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt
@@ -102,6 +102,13 @@ class FakeMatrixClient(
override fun stopSync() = Unit
+ override suspend fun getCacheSize(): Long {
+ return 0
+ }
+
+ override suspend fun clearCache() {
+ }
+
override suspend fun logout() {
delay(100)
logoutFailure?.let { throw it }
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt
index 816bfc572a..81fa3b677c 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt
@@ -31,7 +31,7 @@ import kotlinx.coroutines.flow.flowOf
val A_OIDC_DATA = OidcDetails(url = "a-url")
class FakeAuthenticationService : MatrixAuthenticationService {
- private var homeserver = MutableStateFlow(null)
+ private val homeserver = MutableStateFlow(null)
private var oidcError: Throwable? = null
private var oidcCancelError: Throwable? = null
private var loginError: Throwable? = null
diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt
index 9038e03611..c5b7f1ed44 100644
--- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt
+++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt
@@ -26,16 +26,17 @@ import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.matrix.api.MatrixClient
import okhttp3.OkHttpClient
import javax.inject.Inject
+import javax.inject.Provider
class LoggedInImageLoaderFactory @Inject constructor(
@ApplicationContext private val context: Context,
private val matrixClient: MatrixClient,
- private val okHttpClient: OkHttpClient,
+ private val okHttpClient: Provider,
) : ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader
.Builder(context)
- .okHttpClient(okHttpClient)
+ .okHttpClient { okHttpClient.get() }
.components {
// Add gif support
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
@@ -54,12 +55,12 @@ class LoggedInImageLoaderFactory @Inject constructor(
class NotLoggedInImageLoaderFactory @Inject constructor(
@ApplicationContext private val context: Context,
- private val okHttpClient: OkHttpClient,
+ private val okHttpClient: Provider,
) : ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader
.Builder(context)
- .okHttpClient(okHttpClient)
+ .okHttpClient { okHttpClient.get() }
.build()
}
}
diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt
index e4cb389d94..5d8e0bfd1c 100644
--- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt
+++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt
@@ -43,6 +43,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.datetime.Clock
import kotlinx.datetime.TimeZone
+import timber.log.Timber
import java.util.Locale
class RoomListScreen(
@@ -106,8 +107,10 @@ class RoomListScreen(
)
DisposableEffect(Unit) {
+ Timber.w("Start sync!")
matrixClient.startSync()
onDispose {
+ Timber.w("Stop sync!")
matrixClient.stopSync()
}
}
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
index 57b4a846e2..db838e0f1f 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5d806cbab0f26fb4f471adb5fbecfc600603a7651c5391501d42b13b23617a4c
-size 29301
+oid sha256:9597821bbe6b65693470b40e5f570cf318821d2dbf5bdf525d447daff7d352ae
+size 35345
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..db838e0f1f
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9597821bbe6b65693470b40e5f570cf318821d2dbf5bdf525d447daff7d352ae
+size 35345
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
index d8d2dbc8d1..9786515e1c 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d04eb5d2ebb8b740edcaac00912994e8c164e7b5083595d4085d350af0ca6c33
-size 28482
+oid sha256:61880d7b08cc92743a12a74246495039b76e1e80e2704839f726329efb6958a0
+size 34308
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..9786515e1c
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:61880d7b08cc92743a12a74246495039b76e1e80e2704839f726329efb6958a0
+size 34308
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_0,NEXUS_5,1.0,en].png
index 719089d8e9..cf208695d7 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:04e83ead8c23ec9160eb9cfd656aa5e9bf8c302dcf435d00b78aa2eba3243f0d
-size 64181
+oid sha256:05dc60cbfa8de0e27acecc5d44f7e1841d15f1cdcb749dcf23995aa49d40d924
+size 64174
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_1,NEXUS_5,1.0,en].png
index dcdf364d71..9d69f48d15 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b59df47ab2ad44752eb3441ed117fff517e42c2c08f5e0c6402168223f35daef
-size 53042
+oid sha256:dc5b5b7a08b61201bde775880eadd40dc51773fe616209c85c143fc703091cef
+size 53024
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_2,NEXUS_5,1.0,en].png
index ef99f706a2..cfb41488ee 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:002fa211918a6d41c993339b30261fb8b564df442756bafe9f8866d2ecf51c81
-size 54256
+oid sha256:51f3ee04917b0725bc0a721ac770636b7c273c1ef6f800c5ff3087ea59906396
+size 54267
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_3,NEXUS_5,1.0,en].png
index bfc84aa2bf..2c2e946b6d 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ec598fdc25acf4300e9e44a7b4913e3b9dd881cd27222393047421cd8c361217
-size 54722
+oid sha256:05e6a676f742d70c75d1e4ed35667aadbf22e0d796ef165b5265e8889c5bd062
+size 54715
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_4,NEXUS_5,1.0,en].png
index c64bff1e9f..dd3e5c1a5e 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:df7c6f78b6a54d1444388aafdd76c8c00ba15be688e2ee10f0582339e74e2499
-size 67872
+oid sha256:51fb6dadd1af1bc140877f205d57270ff67a46a3b4c4508ddcdf11e2402f60e4
+size 67859
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_7,NEXUS_5,1.0,en].png
index d4c96e56b4..85103e6e4d 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_7,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_7,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:872c9b5285190577292485c2c0439ceb2259b68740591f1cad7129f322b089b3
-size 57677
+oid sha256:1471ebd62c5514c04bb943e83ae1f2bf51edad409da5fb9c8f6613fbae09d333
+size 57679
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_8,NEXUS_5,1.0,en].png
index 29f39d40c7..b101fabb04 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_8,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview--1_1_null_8,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d3bdab2714bd1b232b255fbe0799209da5f40a390384fb21f56bbc1788032129
-size 64512
+oid sha256:c164ad24729e25e5b4a1031bb6bb452516e9172b65d867d584efa63e5a095355
+size 64505
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_0,NEXUS_5,1.0,en].png
index a12afbc7c9..625da9d324 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a9a1a5bb3c04fb601b84a2b1373544e8eba94f688d938a990308a0e788d96445
-size 61182
+oid sha256:e651c80d07b153dde903f427b4e41e3475c51b524d877ffe55b265ffaac8c32a
+size 61197
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_1,NEXUS_5,1.0,en].png
index 79ebe73047..9e9b406f63 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b7ee7551b233428723d79b099d7bac4634188a3ac120ff3389b2ed33625dcc94
-size 51252
+oid sha256:cb6a8ad69606bac57df9e1c758756eaf09e887875989546c8aa628207926b79e
+size 51274
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_2,NEXUS_5,1.0,en].png
index d0c208aa7c..f70b484141 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:48e7307ffc1d8f7f7a885e67851314c3b135e74a4268ff5fede59758f91a3358
-size 51926
+oid sha256:75eab912dfea0e0eac42742dc131d1630270ac91bf840986be56a755765ee18a
+size 51917
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_3,NEXUS_5,1.0,en].png
index 2a02579eb9..faf29851e2 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:632f982253a6e6f0101fd60a0ef48601cc2c06268ab3d66db084571a5861a52c
-size 52417
+oid sha256:d0d5bee6b1b4b6069a55c0f297a864979b7c1f1f212bc5bccf8806bd82004abc
+size 52429
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_4,NEXUS_5,1.0,en].png
index 43fe21a48a..73cdbe63be 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c49a225d80fe15170e6444271b63324f67099bb733eb1c4b989508a4d0d678ef
-size 64502
+oid sha256:4c2ce12fd0e6edff9ab4a468ec9f3eab2a206b7c4c51b26fa9fa7bf5a5efdc63
+size 64472
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_7,NEXUS_5,1.0,en].png
index 4975863ccb..7c14d499d2 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_7,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_7,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d8612af1823d149e503e5d47814ecb171a4b2a4be19a243751c89034a1fdb30d
-size 55076
+oid sha256:2e000d7940a2fae0a350d508df8b5c95a27f76f60a865cd7372b8a32d89318f6
+size 55082
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_8,NEXUS_5,1.0,en].png
index 885fc65fb2..e34c095393 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_8,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview--0_0_null_8,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:40ecdf8314109687437223860b5e28847d0cb8ba7d546829de5f061640e58315
-size 61500
+oid sha256:86cca8af9ab505cd9cd1ae4a941d5053f2d3155eb033ed1121e7d15f92a6cb43
+size 61509
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_Preferences_PreferenceTextPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_Preferences_PreferenceTextPreview_0_null,NEXUS_5,1.0,en].png
index d818c6f2a0..21db86fe46 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_Preferences_PreferenceTextPreview_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_Preferences_PreferenceTextPreview_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1fc465aa6658ace0327804f04e329cae12bf358daca27e48a8ae6bd516752a9c
-size 12843
+oid sha256:197b1b5fa33ba31f4e47f70b12e4b6eaf7fb3ea30368e96b7dec08f37bdeb62c
+size 28185