From ee033a2aa4cf2fe59f0f4d18caa98814792f6194 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Jun 2023 12:46:24 +0200 Subject: [PATCH 01/25] Improve PreferenceText rendering, in particular center the test on the right vertically, add padding in add more previews. --- .../components/preferences/PreferenceText.kt | 74 ++++++++++++++----- 1 file changed, 57 insertions(+), 17 deletions(-) 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, + ) + } } From 67c4df3b8855a0e27fce146cd1a55ff164da9d96 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Jun 2023 12:46:49 +0200 Subject: [PATCH 02/25] Add logs. --- .../io/element/android/samples/minimal/RoomListScreen.kt | 3 +++ 1 file changed, 3 insertions(+) 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() } } From bf80c251aae71b5f19ef0462fb2542c8c07860c9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Jun 2023 12:52:20 +0200 Subject: [PATCH 03/25] Add clear cache action in the developer settings (#643) --- features/preferences/impl/build.gradle.kts | 1 + .../impl/developer/DeveloperSettingsEvents.kt | 1 + .../developer/DeveloperSettingsPresenter.kt | 17 +++++++ .../impl/developer/DeveloperSettingsState.kt | 4 +- .../DeveloperSettingsStateProvider.kt | 3 ++ .../impl/developer/DeveloperSettingsView.kt | 15 +++++++ .../impl/tasks/ClearCacheUseCase.kt | 44 +++++++++++++++++++ .../DeveloperSettingsPresenterTest.kt | 33 ++++++++++++-- .../impl/tasks/FakeClearCacheUseCase.kt | 28 ++++++++++++ .../libraries/matrix/api/MatrixClient.kt | 1 + libraries/matrix/impl/build.gradle.kts | 1 + .../libraries/matrix/impl/RustMatrixClient.kt | 27 +++++++++++- .../libraries/matrix/test/FakeMatrixClient.kt | 3 ++ 13 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt create mode 100644 features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeClearCacheUseCase.kt diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index 631884fe47..bf8f04b61b 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -62,6 +62,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/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..cb4a5d1ec9 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,17 @@ 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.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 +41,7 @@ import javax.inject.Inject class DeveloperSettingsPresenter @Inject constructor( private val featureFlagService: FeatureFlagService, + private val clearCacheUseCase: ClearCacheUseCase, ) : Presenter { @Composable @@ -47,6 +53,9 @@ class DeveloperSettingsPresenter @Inject constructor( val enabledFeatures = remember { mutableStateMapOf() } + val clearCacheAction = remember { + mutableStateOf>(Async.Uninitialized) + } LaunchedEffect(Unit) { FeatureFlags.values().forEach { feature -> features[feature.key] = feature @@ -64,11 +73,13 @@ class DeveloperSettingsPresenter @Inject constructor( event.feature, event.isEnabled ) + DeveloperSettingsEvents.ClearCache -> coroutineScope.clearCache(clearCacheAction) } } return DeveloperSettingsState( features = featureUiModels.toImmutableList(), + clearCacheAction = clearCacheAction.value, eventSink = ::handleEvents ) } @@ -103,6 +114,12 @@ class DeveloperSettingsPresenter @Inject constructor( enabledFeatures[featureUiModel.key] = enabled } } + + private fun CoroutineScope.clearCache(clearCacheAction: MutableState>) = launch { + suspend { + clearCacheUseCase.execute() + }.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..392f83357f 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,12 @@ 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 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..f600edd602 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,19 @@ 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(), + 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..9bd1a4331c 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,14 @@ 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.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 +55,18 @@ fun DeveloperSettingsView( onClick = onOpenShowkase ) } + PreferenceCategory(title = "Cache") { + PreferenceText( + title = "Clear cache", + icon = Icons.Default.Delete, + loadingCurrentValue = 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..4ccfbb6b81 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt @@ -0,0 +1,44 @@ +/* + * 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.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 ClearCacheUseCase { + suspend fun execute() +} + +@ContributesBinding(SessionScope::class) +class DefaultClearCacheUseCase @Inject constructor( + @ApplicationContext private val context: Context, + private val matrixClient: MatrixClient, + private val coroutineDispatchers: CoroutineDispatchers, +) : ClearCacheUseCase { + override suspend fun execute() = withContext(coroutineDispatchers.io) { + matrixClient.stopSync() + matrixClient.clearCache() + context.cacheDir.deleteRecursively() + matrixClient.startSync() + } +} 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..5710289217 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,8 @@ 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.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 +31,15 @@ class DeveloperSettingsPresenterTest { @Test fun `present - ensures initial state is correct`() = runTest { val presenter = DeveloperSettingsPresenter( - FakeFeatureFlagService() + FakeFeatureFlagService(), + FakeClearCacheUseCase() ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { val initialState = awaitItem() assertThat(initialState.features).isEmpty() + assertThat(initialState.clearCacheAction).isEqualTo(Async.Uninitialized) cancelAndIgnoreRemainingEvents() } } @@ -43,7 +47,8 @@ class DeveloperSettingsPresenterTest { @Test fun `present - ensures feature list is loaded`() = runTest { val presenter = DeveloperSettingsPresenter( - FakeFeatureFlagService() + FakeFeatureFlagService(), + FakeClearCacheUseCase() ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -58,7 +63,8 @@ class DeveloperSettingsPresenterTest { @Test fun `present - ensures state is updated when enabled feature event is triggered`() = runTest { val presenter = DeveloperSettingsPresenter( - FakeFeatureFlagService() + FakeFeatureFlagService(), + FakeClearCacheUseCase() ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -74,4 +80,25 @@ class DeveloperSettingsPresenterTest { cancelAndIgnoreRemainingEvents() } } + + @Test + fun `present - clear cache`() = runTest { + val clearCacheUseCase = FakeClearCacheUseCase() + val presenter = DeveloperSettingsPresenter( + FakeFeatureFlagService(), + 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) + assertThat(awaitItem().clearCacheAction).isInstanceOf(Async.Success::class.java) + assertThat(clearCacheUseCase.executeHasBeenCalled).isTrue() + } + } } 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..f5bc83c443 --- /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 execute() = simulateLongTask { + executeHasBeenCalled = 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..77e4bf810b 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,7 @@ interface MatrixClient : Closeable { fun sessionVerificationService(): SessionVerificationService fun pushersService(): PushersService fun notificationService(): NotificationService + 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..e6e49b5feb 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,7 @@ package io.element.android.libraries.matrix.impl +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,6 +337,10 @@ class RustMatrixClient constructor( client.destroy() } + override suspend fun clearCache() { + baseDirectory.deleteSessionDirectory(userID = client.userId(), deleteCryptoDb = false) + } + override suspend fun logout() = withContext(dispatchers.io) { try { client.logout() @@ -378,11 +383,29 @@ class RustMatrixClient constructor( override fun roomMembershipObserver(): RoomMembershipObserver = roomMembershipObserver - private fun File.deleteSessionDirectory(userID: String): Boolean { + private fun File.deleteSessionDirectory( + userID: String, + deleteCryptoDb: Boolean = false, + ): Boolean { // Rust sanitises the user ID replacing invalid characters with an _ val sanitisedUserID = userID.replace(":", "_") val sessionDirectory = File(this, sanitisedUserID) - return sessionDirectory.deleteRecursively() + return if (deleteCryptoDb) { + // Delete the folder and all its content + sessionDirectory.deleteRecursively() + } else { + // Delete only the state.db file + listOf( + "matrix-sdk-state.sqlite3", + "matrix-sdk-state.sqlite3-shm", + "matrix-sdk-state.sqlite3-wal", + ).map { fileName -> + File(sessionDirectory, fileName) + }.forEach { file -> + 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..85a3ea3a5c 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,9 @@ class FakeMatrixClient( override fun stopSync() = Unit + override suspend fun clearCache() { + } + override suspend fun logout() { delay(100) logoutFailure?.let { throw it } From 5d8adb25aeaf88401fb750ca187750b3709c3df1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Jun 2023 13:41:04 +0200 Subject: [PATCH 04/25] Display cache size in the developer settings (#643) --- features/preferences/impl/build.gradle.kts | 1 + .../developer/DeveloperSettingsPresenter.kt | 16 ++ .../impl/developer/DeveloperSettingsState.kt | 1 + .../DeveloperSettingsStateProvider.kt | 1 + .../impl/developer/DeveloperSettingsView.kt | 7 +- .../impl/tasks/ComputeCacheSizeUseCase.kt | 45 ++++++ .../DeveloperSettingsPresenterTest.kt | 16 +- .../impl/tasks/FakeComputeCacheSizeUseCase.kt | 25 ++++ .../libraries/androidutils/file/FileUtils.kt | 137 ++++++++++++++++++ .../libraries/matrix/api/MatrixClient.kt | 1 + .../libraries/matrix/impl/RustMatrixClient.kt | 41 +++++- .../libraries/matrix/test/FakeMatrixClient.kt | 4 + 12 files changed, 283 insertions(+), 12 deletions(-) create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ComputeCacheSizeUseCase.kt create mode 100644 features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeComputeCacheSizeUseCase.kt create mode 100644 libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/FileUtils.kt diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index bf8f04b61b..e0ecbe9ddd 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) 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 cb4a5d1ec9..ffcbb92ba9 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 @@ -26,6 +26,7 @@ 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 @@ -41,6 +42,7 @@ import javax.inject.Inject class DeveloperSettingsPresenter @Inject constructor( private val featureFlagService: FeatureFlagService, + private val computeCacheSizeUseCase: ComputeCacheSizeUseCase, private val clearCacheUseCase: ClearCacheUseCase, ) : Presenter { @@ -53,6 +55,9 @@ class DeveloperSettingsPresenter @Inject constructor( val enabledFeatures = remember { mutableStateMapOf() } + val cacheSize = remember { + mutableStateOf>(Async.Uninitialized) + } val clearCacheAction = remember { mutableStateOf>(Async.Uninitialized) } @@ -64,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) { @@ -79,6 +88,7 @@ class DeveloperSettingsPresenter @Inject constructor( return DeveloperSettingsState( features = featureUiModels.toImmutableList(), + cacheSizeInBytes = cacheSize.value, clearCacheAction = clearCacheAction.value, eventSink = ::handleEvents ) @@ -115,6 +125,12 @@ class DeveloperSettingsPresenter @Inject constructor( } } + private fun CoroutineScope.computeCacheSize(cacheSize: MutableState>) = launch { + suspend { + computeCacheSizeUseCase.execute() + }.execute(cacheSize) + } + private fun CoroutineScope.clearCache(clearCacheAction: MutableState>) = launch { suspend { clearCacheUseCase.execute() 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 392f83357f..7d9bfed714 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 @@ -22,6 +22,7 @@ import kotlinx.collections.immutable.ImmutableList data class DeveloperSettingsState constructor( val features: ImmutableList, + val cacheSizeInBytes: 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 f600edd602..92fb248142 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 @@ -30,6 +30,7 @@ open class DeveloperSettingsStateProvider : PreviewParameterProvider Boolean + +/* ========================================================================================== + * Delete + * ========================================================================================== */ + +fun deleteAllFiles(root: File) { + Timber.v("Delete ${root.absolutePath}") + recursiveActionOnFile(root, ::deleteAction) +} + +private fun deleteAction(file: File): Boolean { + if (file.exists()) { + Timber.v("deleteFile: $file") + return file.delete() + } + + return true +} + +/* ========================================================================================== + * 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/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 77e4bf810b..479e3169d9 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,7 @@ interface MatrixClient : Closeable { fun sessionVerificationService(): SessionVerificationService fun pushersService(): PushersService fun notificationService(): NotificationService + suspend fun getCacheSize(): Long suspend fun clearCache() suspend fun logout() suspend fun loadUserDisplayName(): Result 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 e6e49b5feb..03d18abba5 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,7 @@ 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 @@ -337,8 +338,12 @@ class RustMatrixClient constructor( client.destroy() } + override suspend fun getCacheSize(): Long { + return baseDirectory.getCacheSize(userID = client.userId()) + } + override suspend fun clearCache() { - baseDirectory.deleteSessionDirectory(userID = client.userId(), deleteCryptoDb = false) + baseDirectory.deleteSessionDirectory(userID = client.userId()) } override suspend fun logout() = withContext(dispatchers.io) { @@ -347,7 +352,7 @@ class RustMatrixClient constructor( } catch (failure: Throwable) { Timber.e(failure, "Fail to call logout on HS. Still delete local files.") } - baseDirectory.deleteSessionDirectory(userID = client.userId()) + baseDirectory.deleteSessionDirectory(userID = client.userId(), deleteCryptoDb = true) sessionStore.removeSession(client.userId()) close() } @@ -383,14 +388,36 @@ class RustMatrixClient constructor( override fun roomMembershipObserver(): RoomMembershipObserver = roomMembershipObserver - private fun File.deleteSessionDirectory( + private suspend fun File.getCacheSize( userID: String, - deleteCryptoDb: Boolean = false, - ): Boolean { + 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 if (deleteCryptoDb) { + 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 { 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 85a3ea3a5c..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,10 @@ class FakeMatrixClient( override fun stopSync() = Unit + override suspend fun getCacheSize(): Long { + return 0 + } + override suspend fun clearCache() { } From ac60ef0cbed894e95a059ad959d14e177aad5a38 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Jun 2023 14:46:25 +0200 Subject: [PATCH 05/25] Move FileSizeFormatter to a new module for injectable Android utilities. --- features/messages/impl/build.gradle.kts | 2 + .../media/local/AndroidLocalMediaFactory.kt | 2 +- .../TimelineItemContentMessageFactory.kt | 2 +- .../messages/fixtures/timelineItemsFactory.kt | 2 +- libraries/androidtools/api/build.gradle.kts | 23 ++++++++++ .../androidtools/api/FileSizeFormatter.kt | 24 +++++++++++ libraries/androidtools/impl/build.gradle.kts | 42 +++++++++++++++++++ .../impl/AndroidFileSizeFormatter.kt | 10 +---- libraries/androidtools/test/build.gradle.kts | 27 ++++++++++++ .../test}/FakeFileSizeFormatter.kt | 4 +- 10 files changed, 125 insertions(+), 13 deletions(-) create mode 100644 libraries/androidtools/api/build.gradle.kts create mode 100644 libraries/androidtools/api/src/main/kotlin/io/element/android/libraries/androidtools/api/FileSizeFormatter.kt create mode 100644 libraries/androidtools/impl/build.gradle.kts rename features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/FileSizeFormatter.kt => libraries/androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl/AndroidFileSizeFormatter.kt (82%) create mode 100644 libraries/androidtools/test/build.gradle.kts rename {features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline => libraries/androidtools/test/src/main/kotlin/io/element/android/libraries/androidtools/test}/FakeFileSizeFormatter.kt (84%) diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index f8377733a6..73360a0a25 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { anvil(projects.anvilcodegen) api(projects.features.messages.api) implementation(projects.libraries.androidutils) + implementation(projects.libraries.androidtools.api) implementation(projects.libraries.core) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) @@ -64,6 +65,7 @@ dependencies { testImplementation(libs.molecule.runtime) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) + testImplementation(projects.libraries.androidtools.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.dateformatter.test) testImplementation(projects.features.networkmonitor.test) 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..c231825524 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.androidtools.api.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..db5ee8190e 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.androidtools.api.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..6e2bf33477 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.androidtools.test.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/libraries/androidtools/api/build.gradle.kts b/libraries/androidtools/api/build.gradle.kts new file mode 100644 index 0000000000..aeaec3ad30 --- /dev/null +++ b/libraries/androidtools/api/build.gradle.kts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.androidtools.api" +} diff --git a/libraries/androidtools/api/src/main/kotlin/io/element/android/libraries/androidtools/api/FileSizeFormatter.kt b/libraries/androidtools/api/src/main/kotlin/io/element/android/libraries/androidtools/api/FileSizeFormatter.kt new file mode 100644 index 0000000000..6c67d9a588 --- /dev/null +++ b/libraries/androidtools/api/src/main/kotlin/io/element/android/libraries/androidtools/api/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.androidtools.api + +interface FileSizeFormatter { + /** + * Formats a content size to be in the form of bytes, kilobytes, megabytes, etc. + */ + fun format(fileSize: Long): String +} diff --git a/libraries/androidtools/impl/build.gradle.kts b/libraries/androidtools/impl/build.gradle.kts new file mode 100644 index 0000000000..5b3b85036f --- /dev/null +++ b/libraries/androidtools/impl/build.gradle.kts @@ -0,0 +1,42 @@ +/* + * 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. + */ + +plugins { + id("io.element.android-library") + alias(libs.plugins.ksp) + alias(libs.plugins.anvil) +} + +anvil { + generateDaggerFactories.set(true) +} + +android { + namespace = "io.element.android.libraries.androidtools.impl" + + dependencies { + anvil(projects.anvilcodegen) + implementation(libs.dagger) + implementation(projects.libraries.di) + implementation(projects.anvilannotations) + + api(projects.libraries.androidtools.api) + + testImplementation(libs.test.junit) + testImplementation(libs.test.truth) + testImplementation(projects.libraries.androidtools.test) + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/FileSizeFormatter.kt b/libraries/androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl/AndroidFileSizeFormatter.kt similarity index 82% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/FileSizeFormatter.kt rename to libraries/androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl/AndroidFileSizeFormatter.kt index 4ede9b7f21..5a57c1d904 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/FileSizeFormatter.kt +++ b/libraries/androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl/AndroidFileSizeFormatter.kt @@ -14,22 +14,16 @@ * limitations under the License. */ -package io.element.android.features.messages.impl.timeline.util +package io.element.android.libraries.androidtools.impl import android.content.Context import android.text.format.Formatter import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.androidtools.api.FileSizeFormatter import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext 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 -} - @ContributesBinding(AppScope::class) class AndroidFileSizeFormatter @Inject constructor(@ApplicationContext private val context: Context) : FileSizeFormatter { override fun format(fileSize: Long): String { diff --git a/libraries/androidtools/test/build.gradle.kts b/libraries/androidtools/test/build.gradle.kts new file mode 100644 index 0000000000..8e045d6e01 --- /dev/null +++ b/libraries/androidtools/test/build.gradle.kts @@ -0,0 +1,27 @@ +/* + * 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. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.androidtools.test" + + dependencies { + api(projects.libraries.androidtools.api) + } +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/FakeFileSizeFormatter.kt b/libraries/androidtools/test/src/main/kotlin/io/element/android/libraries/androidtools/test/FakeFileSizeFormatter.kt similarity index 84% rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/FakeFileSizeFormatter.kt rename to libraries/androidtools/test/src/main/kotlin/io/element/android/libraries/androidtools/test/FakeFileSizeFormatter.kt index 4ff65b0146..d316510ec8 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/FakeFileSizeFormatter.kt +++ b/libraries/androidtools/test/src/main/kotlin/io/element/android/libraries/androidtools/test/FakeFileSizeFormatter.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package io.element.android.features.messages.timeline +package io.element.android.libraries.androidtools.test -import io.element.android.features.messages.impl.timeline.util.FileSizeFormatter +import io.element.android.libraries.androidtools.api.FileSizeFormatter class FakeFileSizeFormatter : FileSizeFormatter { override fun format(fileSize: Long): String { From 2809d6fc2f197fcaf60d96f111baf53dfa4567b9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Jun 2023 14:55:26 +0200 Subject: [PATCH 06/25] Improve FileSizeFormatter.format API. --- .../androidtools/api/FileSizeFormatter.kt | 2 +- .../impl/AndroidFileSizeFormatter.kt | 23 +++++++++++++++++-- .../test/FakeFileSizeFormatter.kt | 2 +- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/libraries/androidtools/api/src/main/kotlin/io/element/android/libraries/androidtools/api/FileSizeFormatter.kt b/libraries/androidtools/api/src/main/kotlin/io/element/android/libraries/androidtools/api/FileSizeFormatter.kt index 6c67d9a588..261b7cbecc 100644 --- a/libraries/androidtools/api/src/main/kotlin/io/element/android/libraries/androidtools/api/FileSizeFormatter.kt +++ b/libraries/androidtools/api/src/main/kotlin/io/element/android/libraries/androidtools/api/FileSizeFormatter.kt @@ -20,5 +20,5 @@ interface FileSizeFormatter { /** * Formats a content size to be in the form of bytes, kilobytes, megabytes, etc. */ - fun format(fileSize: Long): String + fun format(fileSize: Long, useShortFormat: Boolean = true): String } diff --git a/libraries/androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl/AndroidFileSizeFormatter.kt b/libraries/androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl/AndroidFileSizeFormatter.kt index 5a57c1d904..fe156f1635 100644 --- a/libraries/androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl/AndroidFileSizeFormatter.kt +++ b/libraries/androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl/AndroidFileSizeFormatter.kt @@ -17,6 +17,7 @@ package io.element.android.libraries.androidtools.impl import android.content.Context +import android.os.Build import android.text.format.Formatter import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.androidtools.api.FileSizeFormatter @@ -26,7 +27,25 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class AndroidFileSizeFormatter @Inject constructor(@ApplicationContext private val context: Context) : FileSizeFormatter { - override fun format(fileSize: Long): String { - return Formatter.formatShortFileSize(context, fileSize) + override fun format(fileSize: Long, useShortFormat: Boolean): String { + // Since Android O, the system considers that 1ko = 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/libraries/androidtools/test/src/main/kotlin/io/element/android/libraries/androidtools/test/FakeFileSizeFormatter.kt b/libraries/androidtools/test/src/main/kotlin/io/element/android/libraries/androidtools/test/FakeFileSizeFormatter.kt index d316510ec8..183a78eaf1 100644 --- a/libraries/androidtools/test/src/main/kotlin/io/element/android/libraries/androidtools/test/FakeFileSizeFormatter.kt +++ b/libraries/androidtools/test/src/main/kotlin/io/element/android/libraries/androidtools/test/FakeFileSizeFormatter.kt @@ -19,7 +19,7 @@ package io.element.android.libraries.androidtools.test import io.element.android.libraries.androidtools.api.FileSizeFormatter class FakeFileSizeFormatter : FileSizeFormatter { - override fun format(fileSize: Long): String { + override fun format(fileSize: Long, useShortFormat: Boolean): String { return "$fileSize Bytes" } } From df4bc3d7ce8919237c6f3a466b5e51b6b5454924 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Jun 2023 15:26:16 +0200 Subject: [PATCH 07/25] Improve rendering of cache size (and fix compilation issue) --- features/messages/impl/build.gradle.kts | 2 +- features/preferences/impl/build.gradle.kts | 1 + .../impl/developer/DeveloperSettingsPresenter.kt | 6 +++--- .../impl/developer/DeveloperSettingsState.kt | 2 +- .../impl/developer/DeveloperSettingsStateProvider.kt | 2 +- .../impl/developer/DeveloperSettingsView.kt | 8 +++----- .../preferences/impl/tasks/ComputeCacheSizeUseCase.kt | 11 +++++++---- .../impl/developer/DeveloperSettingsPresenterTest.kt | 2 +- .../androidtools/impl/AndroidFileSizeFormatter.kt | 4 +++- .../main/kotlin/extension/DependencyHandleScope.kt | 1 + 10 files changed, 22 insertions(+), 17 deletions(-) diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 73360a0a25..882c226b6b 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -34,7 +34,7 @@ dependencies { anvil(projects.anvilcodegen) api(projects.features.messages.api) implementation(projects.libraries.androidutils) - implementation(projects.libraries.androidtools.api) + api(projects.libraries.androidtools.api) implementation(projects.libraries.core) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index e0ecbe9ddd..bb55094347 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { implementation(projects.anvilannotations) anvil(projects.anvilcodegen) implementation(projects.libraries.androidutils) + api(projects.libraries.androidtools.api) implementation(projects.libraries.core) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) 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 ffcbb92ba9..08a787e998 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 @@ -56,7 +56,7 @@ class DeveloperSettingsPresenter @Inject constructor( mutableStateMapOf() } val cacheSize = remember { - mutableStateOf>(Async.Uninitialized) + mutableStateOf>(Async.Uninitialized) } val clearCacheAction = remember { mutableStateOf>(Async.Uninitialized) @@ -88,7 +88,7 @@ class DeveloperSettingsPresenter @Inject constructor( return DeveloperSettingsState( features = featureUiModels.toImmutableList(), - cacheSizeInBytes = cacheSize.value, + cacheSize = cacheSize.value, clearCacheAction = clearCacheAction.value, eventSink = ::handleEvents ) @@ -125,7 +125,7 @@ class DeveloperSettingsPresenter @Inject constructor( } } - private fun CoroutineScope.computeCacheSize(cacheSize: MutableState>) = launch { + private fun CoroutineScope.computeCacheSize(cacheSize: MutableState>) = launch { suspend { computeCacheSizeUseCase.execute() }.execute(cacheSize) 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 7d9bfed714..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 @@ -22,7 +22,7 @@ import kotlinx.collections.immutable.ImmutableList data class DeveloperSettingsState constructor( val features: ImmutableList, - val cacheSizeInBytes: Async, + 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 92fb248142..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 @@ -30,7 +30,7 @@ open class DeveloperSettingsStateProvider : PreviewParameterProvider Date: Tue, 20 Jun 2023 14:12:02 +0000 Subject: [PATCH 08/25] Update screenshots --- ...eloperSettingsViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...eloperSettingsViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 3 +++ ...loperSettingsViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...loperSettingsViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 3 +++ ...oup_RoomDetailsDarkPreview--1_1_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...oup_RoomDetailsDarkPreview--1_1_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...oup_RoomDetailsDarkPreview--1_1_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...oup_RoomDetailsDarkPreview--1_1_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...oup_RoomDetailsDarkPreview--1_1_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...oup_RoomDetailsDarkPreview--1_1_null_7,NEXUS_5,1.0,en].png | 4 ++-- ...oup_RoomDetailsDarkPreview--1_1_null_8,NEXUS_5,1.0,en].png | 4 ++-- ...up_RoomDetailsLightPreview--0_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...up_RoomDetailsLightPreview--0_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...up_RoomDetailsLightPreview--0_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...up_RoomDetailsLightPreview--0_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...up_RoomDetailsLightPreview--0_0_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...up_RoomDetailsLightPreview--0_0_null_7,NEXUS_5,1.0,en].png | 4 ++-- ...up_RoomDetailsLightPreview--0_0_null_8,NEXUS_5,1.0,en].png | 4 ++-- ...eferences_PreferenceTextPreview_0_null,NEXUS_5,1.0,en].png | 4 ++-- 19 files changed, 40 insertions(+), 34 deletions(-) create mode 100644 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 create mode 100644 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 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 From 4b8d11b7e4f3c7a72afe11cd80cb2ee7f55bbe15 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Jun 2023 17:49:32 +0200 Subject: [PATCH 09/25] Ignore typo --- .idea/dictionaries/bmarty.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml index dd650c15e1..588cfdb67d 100644 --- a/.idea/dictionaries/bmarty.xml +++ b/.idea/dictionaries/bmarty.xml @@ -2,6 +2,7 @@ homeserver + showkase \ No newline at end of file From 58860a9440f61da5d7504ebe72b1174ae7b9d353 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jun 2023 09:34:30 +0200 Subject: [PATCH 10/25] Close the client before deleting data, and ensure the app is restarted, using a cache Index. --- .../io/element/android/appnav/RootFlowNode.kt | 21 +++++++++++++------ .../impl/tasks/ClearCacheUseCase.kt | 5 +++-- .../libraries/matrix/api/MatrixClient.kt | 4 ++++ .../api/auth/MatrixAuthenticationService.kt | 7 +++++++ .../libraries/matrix/impl/RustMatrixClient.kt | 6 ++++-- .../auth/RustMatrixAuthenticationService.kt | 10 +++++++++ 6 files changed, 43 insertions(+), 10 deletions(-) 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..d8847b55b3 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -54,7 +54,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 @@ -88,11 +90,17 @@ class RootFlowNode @AssistedInject constructor( private fun observeLoggedInState() { authenticationService.isLoggedIn() .distinctUntilChanged() + .combine( + authenticationService.cacheIdx().onEach { + Timber.v("cacheIdx=$it") + matrixClientsHolder.removeAll() + } + ) { isLoggedIn, cacheIdx -> isLoggedIn to cacheIdx } .onEach { isLoggedIn -> Timber.v("isLoggedIn=$isLoggedIn") - if (isLoggedIn) { + if (isLoggedIn.first) { tryToRestoreLatestSession( - onSuccess = { switchToLoggedInFlow(it) }, + onSuccess = { switchToLoggedInFlow(it, isLoggedIn.second) }, onFailure = { switchToNotLoggedInFlow() } ) } else { @@ -102,8 +110,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 +171,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 +243,9 @@ class RootFlowNode @AssistedInject constructor( } private suspend fun attachSession(sessionId: SessionId): LoggedInFlowNode { + val cacheIdx = authenticationService.cacheIdx().first() return attachChild { - backstack.newRoot(NavTarget.LoggedInFlow(sessionId)) + backstack.newRoot(NavTarget.LoggedInFlow(sessionId, cacheIdx)) } } } 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 index 4ccfbb6b81..1624a81d64 100644 --- 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 @@ -22,6 +22,7 @@ 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 io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import kotlinx.coroutines.withContext import javax.inject.Inject @@ -34,11 +35,11 @@ class DefaultClearCacheUseCase @Inject constructor( @ApplicationContext private val context: Context, private val matrixClient: MatrixClient, private val coroutineDispatchers: CoroutineDispatchers, + private val authenticationService: MatrixAuthenticationService, ) : ClearCacheUseCase { override suspend fun execute() = withContext(coroutineDispatchers.io) { - matrixClient.stopSync() matrixClient.clearCache() context.cacheDir.deleteRecursively() - matrixClient.startSync() + authenticationService.incrementCacheIdx() } } 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 479e3169d9..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 @@ -51,6 +51,10 @@ interface MatrixClient : Closeable { 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 diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt index c15153876c..8d63642eae 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt @@ -29,6 +29,13 @@ interface MatrixAuthenticationService { suspend fun setHomeserver(homeserver: String): Result suspend fun login(username: String, password: String): Result + /* + * Cache index + */ + + fun cacheIdx(): Flow + fun incrementCacheIdx() + /* * OIDC part. */ 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 03d18abba5..773dc8d03b 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 @@ -339,11 +339,13 @@ class RustMatrixClient constructor( } override suspend fun getCacheSize(): Long { - return baseDirectory.getCacheSize(userID = client.userId()) + // 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() { - baseDirectory.deleteSessionDirectory(userID = client.userId()) + close() + baseDirectory.deleteSessionDirectory(userID = sessionId.value, deleteCryptoDb = false) } override suspend fun logout() = withContext(dispatchers.io) { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index d599029923..2e3c406348 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -58,6 +58,8 @@ class RustMatrixAuthenticationService @Inject constructor( private val clock: SystemClock, ) : MatrixAuthenticationService { + private val cacheIdxState = MutableStateFlow(0) + private val authService: RustAuthenticationService = RustAuthenticationService( basePath = baseDirectory.absolutePath, passphrase = null, @@ -71,6 +73,14 @@ class RustMatrixAuthenticationService @Inject constructor( return sessionStore.isLoggedIn() } + override fun incrementCacheIdx() { + cacheIdxState.value++ + } + + override fun cacheIdx(): Flow { + return cacheIdxState + } + override suspend fun getLatestSessionId(): SessionId? = withContext(coroutineDispatchers.io) { sessionStore.getLatestSession()?.userId?.let { SessionId(it) } } From 1c069435302e80eedb9ce7d787981d4169b70f37 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jun 2023 09:35:21 +0200 Subject: [PATCH 11/25] Close the client before removing its data. Probably safer. --- .../element/android/libraries/matrix/impl/RustMatrixClient.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 773dc8d03b..776747270c 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 @@ -354,9 +354,9 @@ class RustMatrixClient constructor( } catch (failure: Throwable) { Timber.e(failure, "Fail to call logout on HS. Still delete local files.") } - baseDirectory.deleteSessionDirectory(userID = client.userId(), deleteCryptoDb = true) - sessionStore.removeSession(client.userId()) close() + baseDirectory.deleteSessionDirectory(userID = sessionId.value, deleteCryptoDb = true) + sessionStore.removeSession(sessionId.value) } override suspend fun loadUserDisplayName(): Result = withContext(dispatchers.io) { From 5fe186f9de4b0aefa3e1db0b0027d536e88b868b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jun 2023 10:11:08 +0200 Subject: [PATCH 12/25] var -> val --- .../android/features/analytics/test/FakeAnalyticsService.kt | 6 +++--- .../features/invitelist/test/FakeSeenInvitesStore.kt | 2 +- .../libraries/matrix/test/auth/FakeAuthenticationService.kt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) 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/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 From 12e54ac08071d3f07a480a870889c293d7432e26 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jun 2023 10:11:43 +0200 Subject: [PATCH 13/25] Lazy usage of OkHttpClient --- .../rageshake/impl/reporter/DefaultBugReporter.kt | 5 +++-- .../libraries/matrix/ui/media/ImageLoaderFactories.kt | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) 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/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() } } From 0ee2861452e1ec13b3be810efd4a8c416c4d4185 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jun 2023 10:12:19 +0200 Subject: [PATCH 14/25] Clear cache: clear Coil and OkHttpClient cache. --- features/preferences/impl/build.gradle.kts | 2 ++ .../preferences/impl/tasks/ClearCacheUseCase.kt | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index bb55094347..e8fa540f6c 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -41,6 +41,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) @@ -49,6 +50,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) 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 index 1624a81d64..c351bcc127 100644 --- 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 @@ -14,9 +14,13 @@ * 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.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.ApplicationContext @@ -24,7 +28,9 @@ import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient import javax.inject.Inject +import javax.inject.Provider interface ClearCacheUseCase { suspend fun execute() @@ -36,10 +42,21 @@ class DefaultClearCacheUseCase @Inject constructor( private val matrixClient: MatrixClient, private val coroutineDispatchers: CoroutineDispatchers, private val authenticationService: MatrixAuthenticationService, + private val okHttpClient: Provider, ) : ClearCacheUseCase { override suspend fun execute() = 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 authenticationService.incrementCacheIdx() } } From 89d49698336ef2a401e14d41f2888204b7512cb9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jun 2023 10:12:37 +0200 Subject: [PATCH 15/25] Implement missing methods in Fake class. --- .../matrix/test/auth/FakeAuthenticationService.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 81fa3b677c..95bb077252 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 @@ -65,6 +65,16 @@ class FakeAuthenticationService : MatrixAuthenticationService { loginError?.let { Result.failure(it) } ?: Result.success(A_USER_ID) } + private val cacheIdxFlow = MutableStateFlow(0) + + override fun cacheIdx(): Flow { + return cacheIdxFlow + } + + override fun incrementCacheIdx() { + cacheIdxFlow.value++ + } + override suspend fun getOidcUrl(): Result = simulateLongTask { oidcError?.let { Result.failure(it) } ?: Result.success(A_OIDC_DATA) } From 231d4b15772aad7d2cee876ddf581c0557b8ff94 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jun 2023 10:15:43 +0200 Subject: [PATCH 16/25] Fix compilation issue in Fake class. --- .../preferences/impl/tasks/FakeComputeCacheSizeUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index fdf0d657ad..36f60eca7c 100644 --- 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 @@ -20,6 +20,6 @@ import io.element.android.tests.testutils.simulateLongTask class FakeComputeCacheSizeUseCase : ComputeCacheSizeUseCase { override suspend fun execute() = simulateLongTask { - 0L + "O kB" } } From 32ddc7e6f7358f6b5fd143dabdfc57c4acb87b11 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jun 2023 10:24:05 +0200 Subject: [PATCH 17/25] Ensure all file about sqlite are deleted. --- .../libraries/matrix/impl/RustMatrixClient.kt | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) 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 776747270c..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 @@ -424,15 +424,12 @@ class RustMatrixClient constructor( sessionDirectory.deleteRecursively() } else { // Delete only the state.db file - listOf( - "matrix-sdk-state.sqlite3", - "matrix-sdk-state.sqlite3-shm", - "matrix-sdk-state.sqlite3-wal", - ).map { fileName -> - File(sessionDirectory, fileName) - }.forEach { file -> - file.safeDelete() - } + sessionDirectory.listFiles().orEmpty() + .filter { it.name.contains("matrix-sdk-state") } + .forEach { file -> + Timber.w("Deleting file ${file.name}...") + file.safeDelete() + } true } } From cd3b653e1d393aeec9af5f612f983864b85d9c3f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 23 Jun 2023 15:08:44 +0200 Subject: [PATCH 18/25] Merge dict. --- .idea/dictionaries/bmarty.xml | 8 -------- .idea/dictionaries/shared.xml | 2 ++ 2 files changed, 2 insertions(+), 8 deletions(-) delete mode 100644 .idea/dictionaries/bmarty.xml diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml deleted file mode 100644 index 588cfdb67d..0000000000 --- a/.idea/dictionaries/bmarty.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - homeserver - showkase - - - \ 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 From 6591fbec1714f7a05fa4323f40de9118711da7e2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 23 Jun 2023 15:13:03 +0200 Subject: [PATCH 19/25] Use operator invoke. --- .../preferences/impl/developer/DeveloperSettingsPresenter.kt | 4 ++-- .../features/preferences/impl/tasks/ClearCacheUseCase.kt | 4 ++-- .../preferences/impl/tasks/ComputeCacheSizeUseCase.kt | 4 ++-- .../features/preferences/impl/tasks/FakeClearCacheUseCase.kt | 2 +- .../preferences/impl/tasks/FakeComputeCacheSizeUseCase.kt | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) 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 08a787e998..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 @@ -127,13 +127,13 @@ class DeveloperSettingsPresenter @Inject constructor( private fun CoroutineScope.computeCacheSize(cacheSize: MutableState>) = launch { suspend { - computeCacheSizeUseCase.execute() + computeCacheSizeUseCase() }.execute(cacheSize) } private fun CoroutineScope.clearCache(clearCacheAction: MutableState>) = launch { suspend { - clearCacheUseCase.execute() + clearCacheUseCase() }.execute(clearCacheAction) } } 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 index c351bcc127..df70b6f148 100644 --- 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 @@ -33,7 +33,7 @@ import javax.inject.Inject import javax.inject.Provider interface ClearCacheUseCase { - suspend fun execute() + suspend operator fun invoke() } @ContributesBinding(SessionScope::class) @@ -44,7 +44,7 @@ class DefaultClearCacheUseCase @Inject constructor( private val authenticationService: MatrixAuthenticationService, private val okHttpClient: Provider, ) : ClearCacheUseCase { - override suspend fun execute() = withContext(coroutineDispatchers.io) { + override suspend fun invoke() = withContext(coroutineDispatchers.io) { // Clear Matrix cache matrixClient.clearCache() // Clear Coil cache 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 index 9da6909e5f..038694201b 100644 --- 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 @@ -28,7 +28,7 @@ import kotlinx.coroutines.withContext import javax.inject.Inject interface ComputeCacheSizeUseCase { - suspend fun execute(): String + suspend operator fun invoke(): String } @ContributesBinding(SessionScope::class) @@ -38,7 +38,7 @@ class DefaultComputeCacheSizeUseCase @Inject constructor( private val coroutineDispatchers: CoroutineDispatchers, private val fileSizeFormatter: FileSizeFormatter, ) : ComputeCacheSizeUseCase { - override suspend fun execute(): String = withContext(coroutineDispatchers.io) { + override suspend fun invoke(): String = withContext(coroutineDispatchers.io) { var cumulativeSize = 0L cumulativeSize += matrixClient.getCacheSize() // - 4096 to not include the size fo the folder 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 index f5bc83c443..7415e09e96 100644 --- 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 @@ -22,7 +22,7 @@ class FakeClearCacheUseCase : ClearCacheUseCase { var executeHasBeenCalled = false private set - override suspend fun execute() = simulateLongTask { + 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 index 36f60eca7c..fa8556630f 100644 --- 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 @@ -19,7 +19,7 @@ package io.element.android.features.preferences.impl.tasks import io.element.android.tests.testutils.simulateLongTask class FakeComputeCacheSizeUseCase : ComputeCacheSizeUseCase { - override suspend fun execute() = simulateLongTask { + override suspend fun invoke() = simulateLongTask { "O kB" } } From fc61d452c6da83d4ee6c2ae14eecf3c010323143 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 23 Jun 2023 15:13:47 +0200 Subject: [PATCH 20/25] Fix typo in comment. --- .../libraries/androidtools/impl/AndroidFileSizeFormatter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl/AndroidFileSizeFormatter.kt b/libraries/androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl/AndroidFileSizeFormatter.kt index 1437c15ed5..f31151e15c 100644 --- a/libraries/androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl/AndroidFileSizeFormatter.kt +++ b/libraries/androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl/AndroidFileSizeFormatter.kt @@ -30,7 +30,7 @@ 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 1ko = 1000 bytes instead of 1024 bytes. + // 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 From b563b1ec950b3f44edd6b1f8216e3eee2f75d772 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 23 Jun 2023 15:20:19 +0200 Subject: [PATCH 21/25] `Idx` -> `Index` --- .../kotlin/io/element/android/appnav/RootFlowNode.kt | 8 ++++---- .../preferences/impl/tasks/ClearCacheUseCase.kt | 2 +- .../matrix/api/auth/MatrixAuthenticationService.kt | 4 ++-- .../impl/auth/RustMatrixAuthenticationService.kt | 10 +++++----- .../matrix/test/auth/FakeAuthenticationService.kt | 10 +++++----- 5 files changed, 17 insertions(+), 17 deletions(-) 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 d8847b55b3..1a8118f95f 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -91,8 +91,8 @@ class RootFlowNode @AssistedInject constructor( authenticationService.isLoggedIn() .distinctUntilChanged() .combine( - authenticationService.cacheIdx().onEach { - Timber.v("cacheIdx=$it") + authenticationService.cacheIndex().onEach { + Timber.v("cacheIndex=$it") matrixClientsHolder.removeAll() } ) { isLoggedIn, cacheIdx -> isLoggedIn to cacheIdx } @@ -243,9 +243,9 @@ class RootFlowNode @AssistedInject constructor( } private suspend fun attachSession(sessionId: SessionId): LoggedInFlowNode { - val cacheIdx = authenticationService.cacheIdx().first() + val cacheIndex = authenticationService.cacheIndex().first() return attachChild { - backstack.newRoot(NavTarget.LoggedInFlow(sessionId, cacheIdx)) + backstack.newRoot(NavTarget.LoggedInFlow(sessionId, cacheIndex)) } } } 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 index df70b6f148..68ed41babd 100644 --- 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 @@ -57,6 +57,6 @@ class DefaultClearCacheUseCase @Inject constructor( // Clear app cache context.cacheDir.deleteRecursively() // Ensure the app is restarted - authenticationService.incrementCacheIdx() + authenticationService.incrementCacheIndex() } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt index 8d63642eae..d1e2362da6 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt @@ -33,8 +33,8 @@ interface MatrixAuthenticationService { * Cache index */ - fun cacheIdx(): Flow - fun incrementCacheIdx() + fun cacheIndex(): Flow + fun incrementCacheIndex() /* * OIDC part. diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 2e3c406348..ac239d83c1 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -58,7 +58,7 @@ class RustMatrixAuthenticationService @Inject constructor( private val clock: SystemClock, ) : MatrixAuthenticationService { - private val cacheIdxState = MutableStateFlow(0) + private val cacheIndexState = MutableStateFlow(0) private val authService: RustAuthenticationService = RustAuthenticationService( basePath = baseDirectory.absolutePath, @@ -73,12 +73,12 @@ class RustMatrixAuthenticationService @Inject constructor( return sessionStore.isLoggedIn() } - override fun incrementCacheIdx() { - cacheIdxState.value++ + override fun incrementCacheIndex() { + cacheIndexState.value++ } - override fun cacheIdx(): Flow { - return cacheIdxState + override fun cacheIndex(): Flow { + return cacheIndexState } override suspend fun getLatestSessionId(): SessionId? = withContext(coroutineDispatchers.io) { 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 95bb077252..b40dd766ff 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 @@ -65,14 +65,14 @@ class FakeAuthenticationService : MatrixAuthenticationService { loginError?.let { Result.failure(it) } ?: Result.success(A_USER_ID) } - private val cacheIdxFlow = MutableStateFlow(0) + private val cacheIndexFlow = MutableStateFlow(0) - override fun cacheIdx(): Flow { - return cacheIdxFlow + override fun cacheIndex(): Flow { + return cacheIndexFlow } - override fun incrementCacheIdx() { - cacheIdxFlow.value++ + override fun incrementCacheIndex() { + cacheIndexFlow.value++ } override suspend fun getOidcUrl(): Result = simulateLongTask { From 375ac17e14e50d44017008a1b55d911903a3c6a9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 23 Jun 2023 15:20:54 +0200 Subject: [PATCH 22/25] Move content to to File.kt. Also remove deleteAllFiles, we have deleteRecursively now. --- .../libraries/androidutils/file/File.kt | 98 +++++++++++++ .../libraries/androidutils/file/FileUtils.kt | 137 ------------------ 2 files changed, 98 insertions(+), 137 deletions(-) delete mode 100644 libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/FileUtils.kt 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/file/FileUtils.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/FileUtils.kt deleted file mode 100644 index dbd284d305..0000000000 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/FileUtils.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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.file - -import android.content.Context -import androidx.annotation.WorkerThread -import timber.log.Timber -import java.io.File -import java.util.Locale - -// Implementation should return true in case of success -typealias ActionOnFile = (file: File) -> Boolean - -/* ========================================================================================== - * Delete - * ========================================================================================== */ - -fun deleteAllFiles(root: File) { - Timber.v("Delete ${root.absolutePath}") - recursiveActionOnFile(root, ::deleteAction) -} - -private fun deleteAction(file: File): Boolean { - if (file.exists()) { - Timber.v("deleteFile: $file") - return file.delete() - } - - return true -} - -/* ========================================================================================== - * 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() } -} From 963c4a5f40d58ec5bf690914266cb1413021ffac Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 23 Jun 2023 15:24:46 +0200 Subject: [PATCH 23/25] Improve readability --- .../kotlin/io/element/android/appnav/RootFlowNode.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 1a8118f95f..57aa598dc5 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -96,11 +96,13 @@ class RootFlowNode @AssistedInject constructor( matrixClientsHolder.removeAll() } ) { isLoggedIn, cacheIdx -> isLoggedIn to cacheIdx } - .onEach { isLoggedIn -> - Timber.v("isLoggedIn=$isLoggedIn") - if (isLoggedIn.first) { + .onEach { pair -> + val isLoggedIn = pair.first + val cacheIndex = pair.second + Timber.v("isLoggedIn=$isLoggedIn, cacheIndex=$cacheIndex") + if (isLoggedIn) { tryToRestoreLatestSession( - onSuccess = { switchToLoggedInFlow(it, isLoggedIn.second) }, + onSuccess = { switchToLoggedInFlow(it, cacheIndex) }, onFailure = { switchToNotLoggedInFlow() } ) } else { From f1019f344458aa104f86769e4819f6143a573d93 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 23 Jun 2023 15:38:04 +0200 Subject: [PATCH 24/25] Move cache management to a dedicated class and in the setting module, for clarity. --- .../io/element/android/appnav/RootFlowNode.kt | 6 ++- .../features/preferences/api/CacheService.kt | 28 +++++++++++++ .../preferences/impl/DefaultCacheService.kt | 39 +++++++++++++++++++ .../impl/tasks/ClearCacheUseCase.kt | 6 +-- .../api/auth/MatrixAuthenticationService.kt | 7 ---- .../auth/RustMatrixAuthenticationService.kt | 10 ----- .../test/auth/FakeAuthenticationService.kt | 10 ----- 7 files changed, 74 insertions(+), 32 deletions(-) create mode 100644 features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/CacheService.kt create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultCacheService.kt 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 57aa598dc5..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 @@ -67,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, @@ -91,7 +93,7 @@ class RootFlowNode @AssistedInject constructor( authenticationService.isLoggedIn() .distinctUntilChanged() .combine( - authenticationService.cacheIndex().onEach { + cacheService.cacheIndex().onEach { Timber.v("cacheIndex=$it") matrixClientsHolder.removeAll() } @@ -245,7 +247,7 @@ class RootFlowNode @AssistedInject constructor( } private suspend fun attachSession(sessionId: SessionId): LoggedInFlowNode { - val cacheIndex = authenticationService.cacheIndex().first() + val cacheIndex = cacheService.cacheIndex().first() return attachChild { backstack.newRoot(NavTarget.LoggedInFlow(sessionId, cacheIndex)) } 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/src/main/kotlin/io/element/android/features/preferences/impl/DefaultCacheService.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultCacheService.kt new file mode 100644 index 0000000000..7675ec3dd6 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultCacheService.kt @@ -0,0 +1,39 @@ +/* + * 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 + +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.SingleIn +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import javax.inject.Inject + +@SingleIn(AppScope::class) +@ContributesBinding(AppScope::class) +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/tasks/ClearCacheUseCase.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt index 68ed41babd..f7b0d01130 100644 --- 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 @@ -22,11 +22,11 @@ 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 io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import javax.inject.Inject @@ -41,7 +41,7 @@ class DefaultClearCacheUseCase @Inject constructor( @ApplicationContext private val context: Context, private val matrixClient: MatrixClient, private val coroutineDispatchers: CoroutineDispatchers, - private val authenticationService: MatrixAuthenticationService, + private val defaultCacheIndexProvider: DefaultCacheService, private val okHttpClient: Provider, ) : ClearCacheUseCase { override suspend fun invoke() = withContext(coroutineDispatchers.io) { @@ -57,6 +57,6 @@ class DefaultClearCacheUseCase @Inject constructor( // Clear app cache context.cacheDir.deleteRecursively() // Ensure the app is restarted - authenticationService.incrementCacheIndex() + defaultCacheIndexProvider.incrementCacheIndex() } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt index d1e2362da6..c15153876c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt @@ -29,13 +29,6 @@ interface MatrixAuthenticationService { suspend fun setHomeserver(homeserver: String): Result suspend fun login(username: String, password: String): Result - /* - * Cache index - */ - - fun cacheIndex(): Flow - fun incrementCacheIndex() - /* * OIDC part. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index ac239d83c1..d599029923 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -58,8 +58,6 @@ class RustMatrixAuthenticationService @Inject constructor( private val clock: SystemClock, ) : MatrixAuthenticationService { - private val cacheIndexState = MutableStateFlow(0) - private val authService: RustAuthenticationService = RustAuthenticationService( basePath = baseDirectory.absolutePath, passphrase = null, @@ -73,14 +71,6 @@ class RustMatrixAuthenticationService @Inject constructor( return sessionStore.isLoggedIn() } - override fun incrementCacheIndex() { - cacheIndexState.value++ - } - - override fun cacheIndex(): Flow { - return cacheIndexState - } - override suspend fun getLatestSessionId(): SessionId? = withContext(coroutineDispatchers.io) { sessionStore.getLatestSession()?.userId?.let { SessionId(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 b40dd766ff..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 @@ -65,16 +65,6 @@ class FakeAuthenticationService : MatrixAuthenticationService { loginError?.let { Result.failure(it) } ?: Result.success(A_USER_ID) } - private val cacheIndexFlow = MutableStateFlow(0) - - override fun cacheIndex(): Flow { - return cacheIndexFlow - } - - override fun incrementCacheIndex() { - cacheIndexFlow.value++ - } - override suspend fun getOidcUrl(): Result = simulateLongTask { oidcError?.let { Result.failure(it) } ?: Result.success(A_OIDC_DATA) } From a226580c8cf90ce37aed77f68e93ee5ebfa93b55 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 26 Jun 2023 11:14:53 +0200 Subject: [PATCH 25/25] Move FileSizeFormatter to module :libraries:androidutils --- features/messages/impl/build.gradle.kts | 2 - .../media/local/AndroidLocalMediaFactory.kt | 2 +- .../TimelineItemContentMessageFactory.kt | 2 +- .../messages/fixtures/timelineItemsFactory.kt | 2 +- features/preferences/impl/build.gradle.kts | 1 - .../impl/tasks/ComputeCacheSizeUseCase.kt | 2 +- libraries/androidtools/api/build.gradle.kts | 23 ---------- libraries/androidtools/impl/build.gradle.kts | 42 ------------------- libraries/androidtools/test/build.gradle.kts | 27 ------------ .../filesize}/AndroidFileSizeFormatter.kt | 3 +- .../filesize}/FakeFileSizeFormatter.kt | 4 +- .../filesize}/FileSizeFormatter.kt | 2 +- .../kotlin/extension/DependencyHandleScope.kt | 1 - 13 files changed, 7 insertions(+), 106 deletions(-) delete mode 100644 libraries/androidtools/api/build.gradle.kts delete mode 100644 libraries/androidtools/impl/build.gradle.kts delete mode 100644 libraries/androidtools/test/build.gradle.kts rename libraries/{androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl => androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize}/AndroidFileSizeFormatter.kt (93%) rename libraries/{androidtools/test/src/main/kotlin/io/element/android/libraries/androidtools/test => androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize}/FakeFileSizeFormatter.kt (85%) rename libraries/{androidtools/api/src/main/kotlin/io/element/android/libraries/androidtools/api => androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize}/FileSizeFormatter.kt (93%) diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 882c226b6b..f8377733a6 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -34,7 +34,6 @@ dependencies { anvil(projects.anvilcodegen) api(projects.features.messages.api) implementation(projects.libraries.androidutils) - api(projects.libraries.androidtools.api) implementation(projects.libraries.core) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) @@ -65,7 +64,6 @@ dependencies { testImplementation(libs.molecule.runtime) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) - testImplementation(projects.libraries.androidtools.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.dateformatter.test) testImplementation(projects.features.networkmonitor.test) 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 c231825524..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.libraries.androidtools.api.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 db5ee8190e..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 @@ -26,7 +26,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt 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.toHtmlDocument -import io.element.android.libraries.androidtools.api.FileSizeFormatter +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 6e2bf33477..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.libraries.androidtools.test.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/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index e8fa540f6c..1e76ee5c93 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -33,7 +33,6 @@ dependencies { implementation(projects.anvilannotations) anvil(projects.anvilcodegen) implementation(projects.libraries.androidutils) - api(projects.libraries.androidtools.api) implementation(projects.libraries.core) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) 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 index 038694201b..661f6493ec 100644 --- 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 @@ -18,7 +18,7 @@ package io.element.android.features.preferences.impl.tasks import android.content.Context import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.libraries.androidtools.api.FileSizeFormatter +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 diff --git a/libraries/androidtools/api/build.gradle.kts b/libraries/androidtools/api/build.gradle.kts deleted file mode 100644 index aeaec3ad30..0000000000 --- a/libraries/androidtools/api/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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. - */ - -plugins { - id("io.element.android-library") -} - -android { - namespace = "io.element.android.libraries.androidtools.api" -} diff --git a/libraries/androidtools/impl/build.gradle.kts b/libraries/androidtools/impl/build.gradle.kts deleted file mode 100644 index 5b3b85036f..0000000000 --- a/libraries/androidtools/impl/build.gradle.kts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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. - */ - -plugins { - id("io.element.android-library") - alias(libs.plugins.ksp) - alias(libs.plugins.anvil) -} - -anvil { - generateDaggerFactories.set(true) -} - -android { - namespace = "io.element.android.libraries.androidtools.impl" - - dependencies { - anvil(projects.anvilcodegen) - implementation(libs.dagger) - implementation(projects.libraries.di) - implementation(projects.anvilannotations) - - api(projects.libraries.androidtools.api) - - testImplementation(libs.test.junit) - testImplementation(libs.test.truth) - testImplementation(projects.libraries.androidtools.test) - } -} diff --git a/libraries/androidtools/test/build.gradle.kts b/libraries/androidtools/test/build.gradle.kts deleted file mode 100644 index 8e045d6e01..0000000000 --- a/libraries/androidtools/test/build.gradle.kts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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. - */ - -plugins { - id("io.element.android-library") -} - -android { - namespace = "io.element.android.libraries.androidtools.test" - - dependencies { - api(projects.libraries.androidtools.api) - } -} diff --git a/libraries/androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl/AndroidFileSizeFormatter.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatter.kt similarity index 93% rename from libraries/androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl/AndroidFileSizeFormatter.kt rename to libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatter.kt index f31151e15c..9cd70febcc 100644 --- a/libraries/androidtools/impl/src/main/kotlin/io/element/android/libraries/androidtools/impl/AndroidFileSizeFormatter.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatter.kt @@ -14,13 +14,12 @@ * limitations under the License. */ -package io.element.android.libraries.androidtools.impl +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.androidtools.api.FileSizeFormatter import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import javax.inject.Inject diff --git a/libraries/androidtools/test/src/main/kotlin/io/element/android/libraries/androidtools/test/FakeFileSizeFormatter.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FakeFileSizeFormatter.kt similarity index 85% rename from libraries/androidtools/test/src/main/kotlin/io/element/android/libraries/androidtools/test/FakeFileSizeFormatter.kt rename to libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FakeFileSizeFormatter.kt index 183a78eaf1..32c0239428 100644 --- a/libraries/androidtools/test/src/main/kotlin/io/element/android/libraries/androidtools/test/FakeFileSizeFormatter.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FakeFileSizeFormatter.kt @@ -14,9 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.androidtools.test - -import io.element.android.libraries.androidtools.api.FileSizeFormatter +package io.element.android.libraries.androidutils.filesize class FakeFileSizeFormatter : FileSizeFormatter { override fun format(fileSize: Long, useShortFormat: Boolean): String { diff --git a/libraries/androidtools/api/src/main/kotlin/io/element/android/libraries/androidtools/api/FileSizeFormatter.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FileSizeFormatter.kt similarity index 93% rename from libraries/androidtools/api/src/main/kotlin/io/element/android/libraries/androidtools/api/FileSizeFormatter.kt rename to libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FileSizeFormatter.kt index 261b7cbecc..7be38bf9bd 100644 --- a/libraries/androidtools/api/src/main/kotlin/io/element/android/libraries/androidtools/api/FileSizeFormatter.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FileSizeFormatter.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.androidtools.api +package io.element.android.libraries.androidutils.filesize interface FileSizeFormatter { /** diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index 47bdabcbcd..88f499b993 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -77,7 +77,6 @@ private fun DependencyHandlerScope.addImplementationProjects( fun DependencyHandlerScope.allLibrariesImpl() { implementation(project(":libraries:androidutils")) implementation(project(":libraries:deeplink")) - implementation(project(":libraries:androidtools:impl")) implementation(project(":libraries:designsystem")) implementation(project(":libraries:matrix:impl")) implementation(project(":libraries:matrixui"))