diff --git a/.editorconfig b/.editorconfig index 3682168027..ac30459c21 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,10 +14,19 @@ ij_smart_tabs = false ij_visual_guides = none ij_wrap_on_typing = false -# Ktlint rule, for more information see https://pinterest.github.io/ktlint/faq/#why-is-editorconfig-property-disabled_rules-deprecated-and-how-do-i-resolve-this +# Ktlint rule, for more information see https://pinterest.github.io/ktlint/1.1.1/faq/#how-do-i-enable-or-disable-a-rule ktlint_standard_wrapping = disabled ktlint_standard_trailing-comma-on-call-site = disabled - +ktlint_standard_trailing-comma-on-declaration-site = disabled +ktlint_standard_multiline-expression-wrapping = disabled +ktlint_standard_string-template-indent = disabled +ktlint_standard_spacing-between-declarations-with-annotations = disabled +ktlint_standard_function-signature = disabled +ktlint_standard_annotation = disabled +ktlint_standard_parameter-list-wrapping = disabled +ktlint_standard_indent = disabled +ktlint_standard_blank-line-before-declaration = disabled +ktlint_function_naming_ignore_when_annotated_with=Composable [*.java] ij_java_align_consecutive_assignments = false diff --git a/.github/renovate.json b/.github/renovate.json index b3d57b233b..724e223383 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -18,12 +18,6 @@ ], "groupName" : "kotlin" }, - { - "matchPackageNames" : [ - "org.jetbrains.kotlinx.kover" - ], - "enabled" : false - }, { "matchPackagePatterns" : [ "^org.maplibre" diff --git a/.github/workflows/fork-pr-notice.yml b/.github/workflows/fork-pr-notice.yml new file mode 100644 index 0000000000..42b1e54c3f --- /dev/null +++ b/.github/workflows/fork-pr-notice.yml @@ -0,0 +1,31 @@ +name: Community PR notice + +on: + workflow_dispatch: + pull_request_target: + types: + - opened + - reopened + +jobs: + welcome: + runs-on: ubuntu-latest + name: Welcome comment + if: github.event.pull_request.fork != null + steps: + - name: Add auto-generated commit warning + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `Thank you for your contribution! Here are a few things to check in the PR to ensure it's reviewed as quickly as possible: + + - Your branch should be based on \`origin/develop\`, at least when it was created. + - There is a changelog entry in the \`changelog.d\` folder with [the Towncrier format](https://towncrier.readthedocs.io/en/latest/tutorial.html#creating-news-fragments). + - The test pass locally running \`./gradlew test\`. + - The code quality check suite pass locally running \`./gradlew runQualityChecks\`. + - If you modified anything related to the UI, including previews, you'll have to run the \`Record screenshots\` GH action in your forked repo: that will generate compatible new screenshots. However, given Github Actions limitations, **it will prevent the CI from running temporarily**, until you upload a new commit after that one. To do so, just pull the latest changes and push [an empty commit](https://coderwall.com/p/vkdekq/git-commit-allow-empty).` + }) diff --git a/.github/workflows/maestro.yml b/.github/workflows/maestro.yml index a4bf0cc5ae..0e2dc74b92 100644 --- a/.github/workflows/maestro.yml +++ b/.github/workflows/maestro.yml @@ -24,27 +24,31 @@ jobs: cancel-in-progress: true steps: - name: Remove Run-Maestro label - if: ${{ github.event.label.name == 'Run-Maestro' }} + if: ${{ github.event_name == 'pull_request' && github.event.label.name == 'Run-Maestro' }} uses: actions-ecosystem/action-remove-labels@v1 with: labels: Run-Maestro - uses: actions/checkout@v4 + if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch' with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} - uses: actions/setup-java@v4 name: Use JDK 17 + if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch' with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Assemble debug APK run: ./gradlew :app:assembleDebug $CI_GRADLE_ARG_PROPERTIES + if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch' env: ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }} ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }} ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} - uses: mobile-dev-inc/action-maestro-cloud@v1.8.0 + if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch' with: api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }} # Doc says (https://github.com/mobile-dev-inc/action-maestro-cloud#android): diff --git a/.github/workflows/nightlyReports.yml b/.github/workflows/nightlyReports.yml index 3cb4119f99..7861425e28 100644 --- a/.github/workflows/nightlyReports.yml +++ b/.github/workflows/nightlyReports.yml @@ -33,7 +33,7 @@ jobs: run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES - name: 📈 Generate kover report and verify coverage - run: ./gradlew koverMergedReport koverMergedVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true + run: ./gradlew :app:koverHtmlReport :app:koverXmlReport :app:koverVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true - name: ✅ Upload kover report if: always() @@ -41,7 +41,7 @@ jobs: with: name: kover-results path: | - **/build/reports/kover/merged + **/build/reports/kover - name: 🔊 Publish results to Sonar env: diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml index ea86142700..11cd8e3352 100644 --- a/.github/workflows/recordScreenshots.yml +++ b/.github/workflows/recordScreenshots.yml @@ -26,7 +26,7 @@ jobs: uses: nschloe/action-cached-lfs-checkout@v1.2.2 with: persist-credentials: false - ref: ${{ github.event.pull_request.head.ref }} + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }} - name: ⏬ Checkout with LFS (Branch) if: github.event_name == 'workflow_dispatch' uses: nschloe/action-cached-lfs-checkout@v1.2.2 @@ -43,8 +43,8 @@ jobs: with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Record screenshots + id: record run: ./.github/workflows/scripts/recordScreenshots.sh env: - GITHUB_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }} + GITHUB_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_REPOSITORY: ${{ secrets.GITHUB_REPOSITORY }} - diff --git a/.github/workflows/scripts/recordScreenshots.sh b/.github/workflows/scripts/recordScreenshots.sh index 5a86a91580..ad3e372970 100755 --- a/.github/workflows/scripts/recordScreenshots.sh +++ b/.github/workflows/scripts/recordScreenshots.sh @@ -68,11 +68,27 @@ echo "Record screenshots" ./gradlew recordPaparazziDebug --stacktrace -PpreDexEnable=false --max-workers 4 --warn echo "Committing changes" -git config user.name "ElementBot" -git config user.email "benoitm+elementbot@element.io" +git config http.sslVerify false + +if [[ -z ${INPUT_AUTHOR_NAME} ]]; then + git config user.name "ElementBot" +else + git config --local user.name "${INPUT_AUTHOR_NAME}" +fi + +if [[ -z ${INPUT_AUTHOR_EMAIL} ]]; then + git config user.email "benoitm+elementbot@element.io" +else + git config --local user.name "${INPUT_AUTHOR_EMAIL}" +fi git add -A git commit -m "Update screenshots" +GITHUB_REPO="https://$GITHUB_ACTOR:$TOKEN@github.com/$REPO.git" echo "Pushing changes" -git push "https://$TOKEN@github.com/$REPO.git" $BRANCH +if [[ -z ${GITHUB_ACTOR} ]]; then + echo "No GITHUB_ACTOR env var" + GITHUB_REPO="https://$TOKEN@github.com/$REPO.git" +fi +git push $GITHUB_REPO "$BRANCH" echo "Done!" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6188145d67..bf0379f6b6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -55,7 +55,7 @@ jobs: run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES - name: 📈Generate kover report and verify coverage - run: ./gradlew koverMergedReport koverMergedVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true + run: ./gradlew :app:koverHtmlReport :app:koverXmlReport :app:koverVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true - name: 🚫 Upload kover failed coverage reports if: failure() @@ -63,7 +63,7 @@ jobs: with: name: kover-error-report path: | - **/kover/merged/verification/errors.txt + app/build/reports/kover/verify.err - name: ✅ Upload kover report (disabled) if: always() @@ -83,4 +83,4 @@ jobs: if: always() uses: codecov/codecov-action@v3 # with: - # files: build/reports/kover/merged/xml/report.xml + # files: build/reports/kover/xml/report.xml diff --git a/.maestro/tests/account/logout.yaml b/.maestro/tests/account/logout.yaml index f10d5ef364..3019f1d2c3 100644 --- a/.maestro/tests/account/logout.yaml +++ b/.maestro/tests/account/logout.yaml @@ -6,12 +6,10 @@ appId: ${APP_ID} - takeScreenshot: build/maestro/900-SignOutScreen - back - tapOn: "Sign out" -- tapOn: - id: "sign-out-submit" # Ensure cancel cancels -- tapOn: "Cancel" - tapOn: - id: "sign-out-submit" + id: "dialog-negative" +- tapOn: "Sign out" - tapOn: id: "dialog-positive" - runFlow: ../assertions/assertInitDisplayed.yaml diff --git a/.maestro/tests/roomList/createAndDeleteDM.yaml b/.maestro/tests/roomList/createAndDeleteDM.yaml index f79a7418e4..6c2ebed11e 100644 --- a/.maestro/tests/roomList/createAndDeleteDM.yaml +++ b/.maestro/tests/roomList/createAndDeleteDM.yaml @@ -9,5 +9,5 @@ appId: ${APP_ID} index: 1 - takeScreenshot: build/maestro/330-createAndDeleteDM - tapOn: "maestroelement2" -- tapOn: "Leave room" +- tapOn: "Leave conversation" - tapOn: "Leave" diff --git a/CHANGES.md b/CHANGES.md index 494b241823..3ceac1ef35 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,39 @@ +Changes in Element X v0.4.1 (2024-01-17) +======================================== + +Features ✨ +---------- + - Render m.sticker events ([#1949](https://github.com/element-hq/element-x-android/issues/1949)) + - Add support for sending images from the keyboard ([#1977](https://github.com/element-hq/element-x-android/issues/1977)) + - Added support for MSC4027 (render custom images in reactions) ([#2159](https://github.com/element-hq/element-x-android/issues/2159)) + +Bugfixes 🐛 +---------- + - Fix crash sending image with latest Posthog because of an usage of an internal Android method. ([#+crash-sending-image-with-latest-posthog](https://github.com/element-hq/element-x-android/issues/+crash-sending-image-with-latest-posthog)) + - Make sure the media viewer tries the main url first (if not empty) then the thumbnail url and then not open if both are missing instead of failing with an error dialog ([#1949](https://github.com/element-hq/element-x-android/issues/1949)) + - Fix room transition animation happens twice. ([#2084](https://github.com/element-hq/element-x-android/issues/2084)) + - Disable ability to send reaction if the user does not have the permission to. ([#2093](https://github.com/element-hq/element-x-android/issues/2093)) + - Trim whitespace at the end of messages to ensure we render the right content. ([#2099](https://github.com/element-hq/element-x-android/issues/2099)) + - Fix crashes in room list when the last message for a room was an extremely long one (several thousands of characters) with no line breaks. ([#2105](https://github.com/element-hq/element-x-android/issues/2105)) + - Disable rasterisation of Vector XMLs, which was causing crashes on API 23. ([#2124](https://github.com/element-hq/element-x-android/issues/2124)) + - Use `SubomposeLayout` for `ContentAvoidingLayout` to prevent wrong measurements in the layout process, leading to cut-off text messages in the timeline. ([#2155](https://github.com/element-hq/element-x-android/issues/2155)) + - Improve rendering of voice messages in the timeline in large displays ([#2156](https://github.com/element-hq/element-x-android/issues/2156)) + - Fix no indication that user list is loading when inviting to room. ([#2172](https://github.com/element-hq/element-x-android/issues/2172)) + - Hide keyboard when tapping on a message in the timeline. ([#2182](https://github.com/element-hq/element-x-android/issues/2182)) + - Mention selector gets stuck when quickly deleting the prompt. ([#2192](https://github.com/element-hq/element-x-android/issues/2192)) + - Hide verbose state events from the timeline ([#2216](https://github.com/element-hq/element-x-android/issues/2216)) + +Other changes +------------- + - Only apply `com.autonomousapps.dependency-analysis` plugin in those modules that need it. ([#+only-apply-dependency-analysis-plugin-where-needed](https://github.com/element-hq/element-x-android/issues/+only-apply-dependency-analysis-plugin-where-needed)) + - Migrate to Kover 0.7.X ([#1782](https://github.com/element-hq/element-x-android/issues/1782)) + - Remove extra logout screen. ([#2072](https://github.com/element-hq/element-x-android/issues/2072)) + - Handle `MembershipChange.NONE` rendering in the timeline. ([#2102](https://github.com/element-hq/element-x-android/issues/2102)) + - Remove extra previews for timestamp view with 'document' case ([#2127](https://github.com/element-hq/element-x-android/issues/2127)) + - Bump AGP version to 8.2.0 ([#2142](https://github.com/element-hq/element-x-android/issues/2142)) + - Replace 'leave room' text with 'leave conversation' for DMs. ([#2218](https://github.com/element-hq/element-x-android/issues/2218)) + + Changes in Element X v0.4.0 (2023-12-22) ======================================== diff --git a/anvilcodegen/src/main/kotlin/io/element/android/anvilcodegen/ContributesNodeCodeGenerator.kt b/anvilcodegen/src/main/kotlin/io/element/android/anvilcodegen/ContributesNodeCodeGenerator.kt index 576a52df89..d9e58f6482 100644 --- a/anvilcodegen/src/main/kotlin/io/element/android/anvilcodegen/ContributesNodeCodeGenerator.kt +++ b/anvilcodegen/src/main/kotlin/io/element/android/anvilcodegen/ContributesNodeCodeGenerator.kt @@ -58,7 +58,6 @@ import java.io.File */ @AutoService(CodeGenerator::class) class ContributesNodeCodeGenerator : CodeGenerator { - override fun isApplicable(context: AnvilContext): Boolean = true override fun generateCode(codeGenDir: File, module: ModuleDescriptor, projectFiles: Collection): Collection { diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f936e991fc..543a414091 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,6 +20,7 @@ import com.android.build.api.variant.FilterConfiguration.FilterType.ABI import extension.allFeaturesImpl import extension.allLibrariesImpl import extension.allServicesImpl +import org.jetbrains.kotlin.cli.common.toBooleanLenient plugins { id("io.element.android-compose-application") @@ -27,7 +28,8 @@ plugins { alias(libs.plugins.anvil) alias(libs.plugins.ksp) alias(libs.plugins.kapt) - alias(libs.plugins.firebaseAppDistribution) + // When using precompiled plugins, we need to apply the firebase plugin like this + id(libs.plugins.firebaseAppDistribution.get().pluginId) alias(libs.plugins.knit) id("kotlin-parcelize") // To be able to update the firebase.xml files, uncomment and build the project @@ -43,10 +45,6 @@ android { versionCode = Versions.versionCode versionName = Versions.versionName - vectorDrawables { - useSupportLibrary = true - } - // Keep abiFilter for the universalApk ndk { abiFilters += listOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64") @@ -194,6 +192,26 @@ knit { } } +val ciBuildProperty = "ci-build" +val isCiBuild = if (project.hasProperty(ciBuildProperty)) { + val raw = project.property(ciBuildProperty) as? String + raw?.toBooleanLenient() == true || raw?.toIntOrNull() == 1 +} else { + false +} + +kover { + // When running on the CI, run only debug test variants + if (isCiBuild) { + excludeTests { + // Disable instrumentation for debug test tasks + tasks( + "testDebugUnitTest", + ) + } + } +} + dependencies { allLibrariesImpl() allServicesImpl() diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 949ccfce40..c800b9d996 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -10,6 +10,9 @@ -keep class com.sun.jna.** { *; } -keep class * implements com.sun.jna.** { *; } +# TagSoup, coming from the RTE library +-keep class org.ccil.cowan.tagsoup.** { *; } + # kotlinx.serialization # Kotlin serialization looks up the generated serializer classes through a function on companion @@ -32,3 +35,8 @@ -dontwarn org.conscrypt.** -dontwarn org.bouncycastle.** -dontwarn org.openjsse.** + +# Needed for Posthog +-keepclassmembers class android.view.JavaViewSpy { + static int windowAttachCount(android.view.View); +} diff --git a/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt b/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt index 03f0b20429..437c6700f5 100644 --- a/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt +++ b/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt @@ -27,7 +27,6 @@ import io.element.android.x.initializer.CrashInitializer import io.element.android.x.initializer.TracingInitializer class ElementXApplication : Application(), DaggerComponentOwner { - override val daggerComponent: AppComponent = DaggerAppComponent.factory().create(this) override fun onCreate() { diff --git a/app/src/main/kotlin/io/element/android/x/MainActivity.kt b/app/src/main/kotlin/io/element/android/x/MainActivity.kt index 00d8a48d9a..99d940c5f5 100644 --- a/app/src/main/kotlin/io/element/android/x/MainActivity.kt +++ b/app/src/main/kotlin/io/element/android/x/MainActivity.kt @@ -51,7 +51,6 @@ import timber.log.Timber private val loggerTag = LoggerTag("MainActivity") class MainActivity : NodeActivity() { - private lateinit var mainNode: MainNode private lateinit var appBindings: AppBindings diff --git a/app/src/main/kotlin/io/element/android/x/MainNode.kt b/app/src/main/kotlin/io/element/android/x/MainNode.kt index 94cd7fa7e1..e4f16d323e 100644 --- a/app/src/main/kotlin/io/element/android/x/MainNode.kt +++ b/app/src/main/kotlin/io/element/android/x/MainNode.kt @@ -48,7 +48,6 @@ class MainNode( plugins = plugins, ), DaggerComponentOwner { - override val daggerComponent = (context as DaggerComponentOwner).daggerComponent override fun resolve(navTarget: RootNavTarget, buildContext: BuildContext): Node { @@ -68,5 +67,4 @@ class MainNode( @Parcelize object RootNavTarget : Parcelable - } diff --git a/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt b/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt index 96439ff973..a6ce26c237 100644 --- a/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt +++ b/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt @@ -27,8 +27,12 @@ import io.element.android.libraries.matrix.api.tracing.TracingService @ContributesTo(AppScope::class) interface AppBindings { fun snackbarDispatcher(): SnackbarDispatcher + fun tracingService(): TracingService + fun bugReporter(): BugReporter + fun lockScreenService(): LockScreenService + fun preferencesStore(): PreferencesStore } diff --git a/app/src/main/kotlin/io/element/android/x/di/AppComponent.kt b/app/src/main/kotlin/io/element/android/x/di/AppComponent.kt index d614556413..4e8c631281 100644 --- a/app/src/main/kotlin/io/element/android/x/di/AppComponent.kt +++ b/app/src/main/kotlin/io/element/android/x/di/AppComponent.kt @@ -28,9 +28,11 @@ import io.element.android.libraries.di.SingleIn @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface AppComponent : NodeFactoriesBindings { - @Component.Factory interface Factory { - fun create(@ApplicationContext @BindsInstance context: Context): AppComponent + fun create( + @ApplicationContext @BindsInstance + context: Context + ): AppComponent } } diff --git a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt index 037cec1e71..995dcf61e5 100644 --- a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt +++ b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt @@ -46,7 +46,6 @@ import java.io.File @Module @ContributesTo(AppScope::class) object AppModule { - @Provides fun providesBaseDirectory(@ApplicationContext context: Context): File { return File(context.filesDir, "sessions") @@ -82,14 +81,20 @@ object AppModule { buildType = buildType, applicationName = context.getString(R.string.app_name), applicationId = BuildConfig.APPLICATION_ID, - lowPrivacyLoggingEnabled = false, // TODO EAx Config.LOW_PRIVACY_LOG_ENABLE, + // TODO EAx Config.LOW_PRIVACY_LOG_ENABLE, + lowPrivacyLoggingEnabled = false, versionName = BuildConfig.VERSION_NAME, versionCode = BuildConfig.VERSION_CODE, - gitRevision = "TODO", // BuildConfig.GIT_REVISION, - gitRevisionDate = "TODO", // BuildConfig.GIT_REVISION_DATE, - gitBranchName = "TODO", // BuildConfig.GIT_BRANCH_NAME, - flavorDescription = "TODO", // BuildConfig.FLAVOR_DESCRIPTION, - flavorShortDescription = "TODO", // BuildConfig.SHORT_FLAVOR_DESCRIPTION, + // BuildConfig.GIT_REVISION, + gitRevision = "TODO", + // BuildConfig.GIT_REVISION_DATE, + gitRevisionDate = "TODO", + // BuildConfig.GIT_BRANCH_NAME, + gitBranchName = "TODO", + // BuildConfig.FLAVOR_DESCRIPTION, + flavorDescription = "TODO", + // BuildConfig.SHORT_FLAVOR_DESCRIPTION, + flavorShortDescription = "TODO", ) @Provides diff --git a/app/src/main/kotlin/io/element/android/x/di/DefaultRoomComponentFactory.kt b/app/src/main/kotlin/io/element/android/x/di/DefaultRoomComponentFactory.kt index 44fc54570d..830ca4a65d 100644 --- a/app/src/main/kotlin/io/element/android/x/di/DefaultRoomComponentFactory.kt +++ b/app/src/main/kotlin/io/element/android/x/di/DefaultRoomComponentFactory.kt @@ -26,7 +26,6 @@ import javax.inject.Inject class DefaultRoomComponentFactory @Inject constructor( private val roomComponentBuilder: RoomComponent.Builder ) : RoomComponentFactory { - override fun create(room: MatrixRoom): Any { return roomComponentBuilder.room(room).build() } diff --git a/app/src/main/kotlin/io/element/android/x/di/DefaultSessionComponentFactory.kt b/app/src/main/kotlin/io/element/android/x/di/DefaultSessionComponentFactory.kt index 922d25f6af..7ea85e81a8 100644 --- a/app/src/main/kotlin/io/element/android/x/di/DefaultSessionComponentFactory.kt +++ b/app/src/main/kotlin/io/element/android/x/di/DefaultSessionComponentFactory.kt @@ -26,7 +26,6 @@ import javax.inject.Inject class DefaultSessionComponentFactory @Inject constructor( private val sessionComponentBuilder: SessionComponent.Builder ) : SessionComponentFactory { - override fun create(client: MatrixClient): Any { return sessionComponentBuilder.client(client).build() } diff --git a/app/src/main/kotlin/io/element/android/x/di/RoomComponent.kt b/app/src/main/kotlin/io/element/android/x/di/RoomComponent.kt index 68c700bdb8..55d0b9c653 100644 --- a/app/src/main/kotlin/io/element/android/x/di/RoomComponent.kt +++ b/app/src/main/kotlin/io/element/android/x/di/RoomComponent.kt @@ -29,7 +29,6 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom @SingleIn(RoomScope::class) @MergeSubcomponent(RoomScope::class) interface RoomComponent : NodeFactoriesBindings { - @Subcomponent.Builder interface Builder { @BindsInstance diff --git a/app/src/main/kotlin/io/element/android/x/di/SessionComponent.kt b/app/src/main/kotlin/io/element/android/x/di/SessionComponent.kt index 54e8c27498..4ef2840e8e 100644 --- a/app/src/main/kotlin/io/element/android/x/di/SessionComponent.kt +++ b/app/src/main/kotlin/io/element/android/x/di/SessionComponent.kt @@ -29,7 +29,6 @@ import io.element.android.libraries.matrix.api.MatrixClient @SingleIn(SessionScope::class) @MergeSubcomponent(SessionScope::class) interface SessionComponent : NodeFactoriesBindings { - @Subcomponent.Builder interface Builder { @BindsInstance diff --git a/app/src/main/kotlin/io/element/android/x/initializer/CrashInitializer.kt b/app/src/main/kotlin/io/element/android/x/initializer/CrashInitializer.kt index c947bc20e3..af381c2dcf 100644 --- a/app/src/main/kotlin/io/element/android/x/initializer/CrashInitializer.kt +++ b/app/src/main/kotlin/io/element/android/x/initializer/CrashInitializer.kt @@ -21,7 +21,6 @@ import androidx.startup.Initializer import io.element.android.features.rageshake.impl.crash.VectorUncaughtExceptionHandler class CrashInitializer : Initializer { - override fun create(context: Context) { VectorUncaughtExceptionHandler(context).activate() } diff --git a/app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt b/app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt index 8dc0bf0e0e..5a7bf0fb82 100644 --- a/app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt +++ b/app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt @@ -31,7 +31,6 @@ import io.element.android.x.di.AppBindings import timber.log.Timber class TracingInitializer : Initializer { - override fun create(context: Context) { val appBindings = context.bindings() val tracingService = appBindings.tracingService() diff --git a/app/src/test/kotlin/io/element/android/x/intent/IntentProviderImplTest.kt b/app/src/test/kotlin/io/element/android/x/intent/IntentProviderImplTest.kt index 9f0cde38cf..340af6f4a8 100644 --- a/app/src/test/kotlin/io/element/android/x/intent/IntentProviderImplTest.kt +++ b/app/src/test/kotlin/io/element/android/x/intent/IntentProviderImplTest.kt @@ -31,7 +31,6 @@ import org.robolectric.RuntimeEnvironment @RunWith(RobolectricTestRunner::class) class IntentProviderImplTest { - @Test fun `test getViewRoomIntent with Session`() { val sut = createIntentProviderImpl() diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt index edeb0c0643..02586bea0d 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt @@ -25,48 +25,27 @@ import kotlin.time.Duration.Companion.seconds /** * Configuration for the lock screen feature. + * @property isPinMandatory Whether the PIN is mandatory or not. + * @property pinBlacklist Some PINs are forbidden. + * @property pinSize The size of the PIN. + * @property maxPinCodeAttemptsBeforeLogout Number of attempts before the user is logged out. + * @property gracePeriod Time period before locking the app once backgrounded. + * @property isStrongBiometricsEnabled Authentication with strong methods (fingerprint, some face/iris unlock implementations) is supported. + * @property isWeakBiometricsEnabled Authentication with weak methods (most face/iris unlock implementations) is supported. */ data class LockScreenConfig( - /** - * Whether the PIN is mandatory or not. - */ val isPinMandatory: Boolean, - - /** - * Some PINs are forbidden. - */ val pinBlacklist: Set, - - /** - * The size of the PIN. - */ val pinSize: Int, - - /** - * Number of attempts before the user is logged out. - */ val maxPinCodeAttemptsBeforeLogout: Int, - - /** - * Time period before locking the app once backgrounded. - */ val gracePeriod: Duration, - - /** - * Authentication with strong methods (fingerprint, some face/iris unlock implementations) is supported. - */ val isStrongBiometricsEnabled: Boolean, - - /** - * Authentication with weak methods (most face/iris unlock implementations) is supported. - */ val isWeakBiometricsEnabled: Boolean, ) @ContributesTo(AppScope::class) @Module object LockScreenConfigModule { - @Provides fun providesLockScreenConfig(): LockScreenConfig = LockScreenConfig( isPinMandatory = false, diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/MatrixConfiguration.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/MatrixConfiguration.kt index e4d6ee7ca2..2dcdb6e458 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/MatrixConfiguration.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/MatrixConfiguration.kt @@ -17,6 +17,6 @@ package io.element.android.appconfig object MatrixConfiguration { - const val matrixToPermalinkBaseUrl: String = "https://matrix.to/#/" + const val MATRIX_TO_PERMALINK_BASE_URL: String = "https://matrix.to/#/" val clientPermalinkBaseUrl: String? = null } diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/NotificationConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/NotificationConfig.kt index 30940cbcaf..8d41a2a207 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/NotificationConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/NotificationConfig.kt @@ -18,8 +18,8 @@ package io.element.android.appconfig object NotificationConfig { // TODO EAx Implement and set to true at some point - const val supportMarkAsReadAction = false + const val SUPPORT_MARK_AS_READ_ACTION = false // TODO EAx Implement and set to true at some point - const val supportQuickReplyAction = false + const val SUPPORT_QUICK_REPLY_ACTION = false } diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/PushConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/PushConfig.kt index 1836a5acb6..592c7c4b9e 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/PushConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/PushConfig.kt @@ -20,5 +20,5 @@ object PushConfig { /** * Note: pusher_app_id cannot exceed 64 chars. */ - const val pusher_app_id: String = "im.vector.app.android" + const val PUSHER_APP_ID: String = "im.vector.app.android" } diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/RoomListConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/RoomListConfig.kt index b464d9794c..b6565a2129 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/RoomListConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/RoomListConfig.kt @@ -17,8 +17,8 @@ package io.element.android.appconfig object RoomListConfig { - const val showInviteMenuItem = false - const val showReportProblemMenuItem = false + const val SHOW_INVITE_MENU_ITEM = false + const val SHOW_REPORT_PROBLEM_MENU_ITEM = false - const val hasDropdownMenu = showInviteMenuItem || showReportProblemMenuItem + const val HAS_DROP_DOWN_MENU = SHOW_INVITE_MENU_ITEM || SHOW_REPORT_PROBLEM_MENU_ITEM } diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/SecureBackupConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/SecureBackupConfig.kt index 61f015d239..47ce0e48c6 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/SecureBackupConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/SecureBackupConfig.kt @@ -17,5 +17,5 @@ package io.element.android.appconfig object SecureBackupConfig { - const val LearnMoreUrl: String = "https://element.io/help#encryption5" + const val LEARN_MORE_URL: String = "https://element.io/help#encryption5" } diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt index 625282f1e8..c309e01277 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt @@ -17,5 +17,5 @@ package io.element.android.appconfig object TimelineConfig { - const val maxReadReceiptToDisplay = 3 + const val MAX_READ_RECEIPT_TO_DISPLAY = 3 } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt b/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt index 36b267debb..b42330795a 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt @@ -40,4 +40,3 @@ fun BackStack.removeLast(element: T) { } ?: return accept(Remove(lastExpectedNavElement.key)) } - diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt index 6bf7687fef..b59c1cebdf 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt @@ -36,7 +36,6 @@ class LoggedInEventProcessor @Inject constructor( roomMembershipObserver: RoomMembershipObserver, sessionVerificationService: SessionVerificationService, ) { - private var observingJob: Job? = null private val displayLeftRoomMessage = roomMembershipObserver.updates diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 884681766e..3985128b21 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -111,7 +111,6 @@ class LoggedInFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins ) { - interface Callback : Plugin { fun onOpenBugReport() } @@ -138,7 +137,7 @@ class LoggedInFlowNode @AssistedInject constructor( }, onStop = { coroutineScope.launch { - //Counterpart startSync is done in observeSyncStateAndNetworkStatus method. + // Counterpart startSync is done in observeSyncStateAndNetworkStatus method. syncService.stopSync() } }, 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 c9dcd62048..42c9869523 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -81,7 +81,6 @@ class RootFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins ) { - override fun onBuilt() { matrixClientsHolder.restoreWithSavedState(buildContext.savedStateMap) super.onBuilt() @@ -268,10 +267,9 @@ class RootFlowNode @AssistedInject constructor( } private suspend fun attachSession(sessionId: SessionId): LoggedInAppScopeFlowNode { - //TODO handle multi-session + // TODO handle multi-session return waitForChildAttached { navTarget -> navTarget is NavTarget.LoggedInFlow && navTarget.sessionId == sessionId } } } - diff --git a/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixClientsHolder.kt b/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixClientsHolder.kt index f75725b1bb..517becef2a 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixClientsHolder.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixClientsHolder.kt @@ -37,7 +37,6 @@ private const val SAVE_INSTANCE_KEY = "io.element.android.x.di.MatrixClientsHold @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class MatrixClientsHolder @Inject constructor(private val authenticationService: MatrixAuthenticationService) : MatrixClientProvider { - private val sessionIdsToMatrixClient = ConcurrentHashMap() private val restoreMutex = Mutex() @@ -65,8 +64,9 @@ class MatrixClientsHolder @Inject constructor(private val authenticationService: @Suppress("UNCHECKED_CAST") fun restoreWithSavedState(state: SavedStateMap?) { Timber.d("Restore state") - if (state == null || sessionIdsToMatrixClient.isNotEmpty()) return Unit.also { + if (state == null || sessionIdsToMatrixClient.isNotEmpty()) { Timber.w("Restore with non-empty map") + return } val sessionIds = state[SAVE_INSTANCE_KEY] as? Array Timber.d("Restore matrix session keys = ${sessionIds?.map { it.value }}") diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInNode.kt index 5ddbb164d8..a61e6b69b6 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInNode.kt @@ -35,7 +35,6 @@ class LoggedInNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins ) { - @Composable override fun View(modifier: Modifier) { val loggedInState = loggedInPresenter.present() diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt index 7683b7278f..a9c4a6ecf8 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt @@ -35,7 +35,6 @@ class LoggedInPresenter @Inject constructor( private val networkMonitor: NetworkMonitor, private val pushService: PushService, ) : Presenter { - @Composable override fun present(): LoggedInState { LaunchedEffect(Unit) { diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt index 43b770f327..9522d8835f 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt @@ -59,15 +59,15 @@ fun SyncStateView( ) { Row( modifier = Modifier - .background(color = ElementTheme.colors.bgSubtleSecondary) - .padding(horizontal = 24.dp, vertical = 10.dp), + .background(color = ElementTheme.colors.bgSubtleSecondary) + .padding(horizontal = 24.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp) ) { CircularProgressIndicator( modifier = Modifier - .progressSemantics() - .size(12.dp), + .progressSemantics() + .size(12.dp), color = ElementTheme.colors.textPrimary, strokeWidth = 1.5.dp, ) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/LoadingRoomNodeView.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/LoadingRoomNodeView.kt index 69a5e9c8a2..26d69cc213 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/LoadingRoomNodeView.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/LoadingRoomNodeView.kt @@ -18,7 +18,6 @@ package io.element.android.appnav.room import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize @@ -43,7 +42,6 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.ui.strings.CommonStrings -@OptIn(ExperimentalLayoutApi::class) @Composable fun LoadingRoomNodeView( state: LoadingRoomState, diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/LoadingRoomState.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/LoadingRoomState.kt index 3836fbff74..0798979812 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/LoadingRoomState.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/LoadingRoomState.kt @@ -47,7 +47,6 @@ open class LoadingRoomStateProvider : PreviewParameterProvider @SingleIn(SessionScope::class) class LoadingRoomStateFlowFactory @Inject constructor(private val matrixClient: MatrixClient) { - fun create(lifecycleScope: CoroutineScope, roomId: RoomId): StateFlow = getRoomFlow(roomId) .map { room -> diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt index 8085494483..b45207b034 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.navigation.transition.JumpToEndTransitionHandler import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.node import com.bumble.appyx.core.plugin.Plugin @@ -65,7 +66,6 @@ class RoomFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins ) { - data class Inputs( val roomId: RoomId, val initialElement: RoomLoadedFlowNode.NavTarget = RoomLoadedFlowNode.NavTarget.Messages, @@ -130,7 +130,8 @@ class RoomFlowNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { - BackstackView() + BackstackView( + transitionHandler = JumpToEndTransitionHandler(), + ) } } - diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt index 045823be6f..d6ea4fc533 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt @@ -71,7 +71,6 @@ class RoomLoadedFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins, ), DaggerComponentOwner { - interface Callback : Plugin { fun onOpenRoom(roomId: RoomId) fun onForwardedToSingleRoom(roomId: RoomId) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavStateFlowFactory.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavStateFlowFactory.kt index e6a46f5950..13c0204f79 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavStateFlowFactory.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavStateFlowFactory.kt @@ -41,7 +41,6 @@ class RootNavStateFlowFactory @Inject constructor( private val matrixClientsHolder: MatrixClientsHolder, private val loginUserStory: LoginUserStory, ) { - private var currentCacheIndex = 0 fun create(savedStateMap: SavedStateMap?): Flow { diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootPresenter.kt index cffc4cf35c..8ec673ceec 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/RootPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootPresenter.kt @@ -30,7 +30,6 @@ class RootPresenter @Inject constructor( private val rageshakeDetectionPresenter: RageshakeDetectionPresenter, private val appErrorStateService: AppErrorStateService, ) : Presenter { - @Composable override fun present(): RootState { val rageshakeDetectionState = rageshakeDetectionPresenter.present() diff --git a/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt index 09ca595080..ecc6a5dad0 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt @@ -41,7 +41,6 @@ import org.junit.Rule import org.junit.Test class RoomFlowNodeTest { - @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() @@ -49,7 +48,6 @@ class RoomFlowNodeTest { val mainDispatcherRule = MainDispatcherRule() private class FakeMessagesEntryPoint : MessagesEntryPoint { - var nodeId: String? = null var callback: MessagesEntryPoint.Callback? = null @@ -68,12 +66,10 @@ class RoomFlowNodeTest { } private class FakeRoomDetailsEntryPoint : RoomDetailsEntryPoint { - var nodeId: String? = null override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomDetailsEntryPoint.NodeBuilder { return object : RoomDetailsEntryPoint.NodeBuilder { - override fun params(params: RoomDetailsEntryPoint.Params): RoomDetailsEntryPoint.NodeBuilder { return this } diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt index d7b951fbbc..079ccab17a 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt @@ -36,7 +36,6 @@ import org.junit.Rule import org.junit.Test class LoggedInPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/appnav/src/test/kotlin/io/element/android/appnav/room/LoadingRoomStateFlowFactoryTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/room/LoadingRoomStateFlowFactoryTest.kt index 6997f07233..58e35b2d58 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/room/LoadingRoomStateFlowFactoryTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/room/LoadingRoomStateFlowFactoryTest.kt @@ -28,25 +28,24 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class LoadingRoomStateFlowFactoryTest { - @Test fun `flow should emit Loading and then Loaded when there is a room in cache`() = runTest { - val room = FakeMatrixRoom(sessionId= A_SESSION_ID, roomId = A_ROOM_ID) + val room = FakeMatrixRoom(sessionId = A_SESSION_ID, roomId = A_ROOM_ID) val matrixClient = FakeMatrixClient(A_SESSION_ID).apply { givenGetRoomResult(A_ROOM_ID, room) } val flowFactory = LoadingRoomStateFlowFactory(matrixClient) flowFactory .create(this, A_ROOM_ID) - .test { - assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading) - assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room)) - } + .test { + assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading) + assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room)) + } } @Test fun `flow should emit Loading and then Loaded when there is a room in cache after SS is loaded`() = runTest { - val room = FakeMatrixRoom(sessionId= A_SESSION_ID, roomId = A_ROOM_ID) + val room = FakeMatrixRoom(sessionId = A_SESSION_ID, roomId = A_ROOM_ID) val roomListService = FakeRoomListService() val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService) val flowFactory = LoadingRoomStateFlowFactory(matrixClient) diff --git a/build.gradle.kts b/build.gradle.kts index 1a19007a28..051a7a4112 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,5 @@ import com.google.devtools.ksp.gradle.KspTask -import kotlinx.kover.api.KoverTaskExtension import org.apache.tools.ant.taskdefs.optional.ReplaceRegExp -import org.jetbrains.kotlin.cli.common.toBooleanLenient buildscript { dependencies { @@ -28,6 +26,7 @@ buildscript { // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { + id("io.element.android-root") alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false alias(libs.plugins.kotlin.android) apply false @@ -41,7 +40,6 @@ plugins { alias(libs.plugins.ktlint) alias(libs.plugins.dependencygraph) alias(libs.plugins.sonarqube) - alias(libs.plugins.kover) } tasks.register("clean").configure { @@ -62,7 +60,7 @@ allprojects { config.from(files("$rootDir/tools/detekt/detekt.yml")) } dependencies { - detektPlugins("io.nlopez.compose.rules:detekt:0.3.8") + detektPlugins("io.nlopez.compose.rules:detekt:0.3.9") } // KtLint @@ -74,7 +72,7 @@ allprojects { configure { // See https://github.com/pinterest/ktlint/releases/ // TODO Regularly check for new version here ^ - version.set("0.48.2") + version.set("1.1.1") android.set(true) ignoreFailures.set(false) enableExperimentalRules.set(true) @@ -100,11 +98,16 @@ allprojects { // You can override by passing `-PallWarningsAsErrors=true` in the command line // Or add a line with "allWarningsAsErrors=true" in your ~/.gradle/gradle.properties file kotlinOptions.allWarningsAsErrors = project.properties["allWarningsAsErrors"] == "true" - } - // Detect unused dependencies - apply { - plugin("com.autonomousapps.dependency-analysis") + kotlinOptions { + /* + // Uncomment to suppress Compose Kotlin compiler compatibility warning + freeCompilerArgs += listOf( + "-P", + "plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true" + ) + */ + } } } @@ -158,180 +161,6 @@ allprojects { } } -allprojects { - apply(plugin = "kover") -} - -// https://kotlin.github.io/kotlinx-kover/ -// Run `./gradlew koverMergedHtmlReport` to get report at ./build/reports/kover -// Run `./gradlew koverMergedReport` to also get XML report -koverMerged { - enable() - - filters { - classes { - excludes.addAll( - listOf( - // Exclude generated classes. - "*_ModuleKt", - "anvil.hint.binding.io.element.*", - "anvil.hint.merge.*", - "anvil.hint.multibinding.io.element.*", - "anvil.module.*", - "com.airbnb.android.showkase*", - "io.element.android.libraries.designsystem.showkase.*", - "io.element.android.x.di.DaggerAppComponent*", - "*_Factory", - "*_Factory_Impl", - "*_Factory$*", - "*_Module", - "*_Module$*", - "*Module_Provides*", - "Dagger*Component*", - "*ComposableSingletons$*", - "*_AssistedFactory_Impl*", - "*BuildConfig", - // Generated by Showkase - "*Ioelementandroid*PreviewKt$*", - "*Ioelementandroid*PreviewKt", - // Other - // We do not cover Nodes (normally covered by maestro, but code coverage is not computed with maestro) - "*Node", - "*Node$*", - // Exclude `:libraries:matrix:impl` module, it contains only wrappers to access the Rust Matrix SDK api, so it is not really relevant to unit test it: there is no logic to test. - "io.element.android.libraries.matrix.impl.*", - "*Presenter\$present\$*", - // Forked from compose - "io.element.android.libraries.designsystem.theme.components.bottomsheet.*", - ) - ) - } - - annotations { - excludes.addAll( - listOf( - "*Preview", - ) - ) - } - - projects { - excludes.addAll( - listOf( - ":anvilannotations", - ":anvilcodegen", - ":samples:minimal", - ":tests:testutils", - ) - ) - } - } - - // Run ./gradlew koverMergedVerify to check the rules. - verify { - // Does not seems to work, so also run the task manually on the workflow. - onCheck.set(true) - // General rule: minimum code coverage. - rule { - name = "Global minimum code coverage." - target = kotlinx.kover.api.VerificationTarget.ALL - bound { - minValue = 65 - // Setting a max value, so that if coverage is bigger, it means that we have to change minValue. - // For instance if we have minValue = 20 and maxValue = 30, and current code coverage is now 31.32%, update - // minValue to 25 and maxValue to 35. - maxValue = 75 - counter = kotlinx.kover.api.CounterType.INSTRUCTION - valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE - } - } - // Rule to ensure that coverage of Presenters is sufficient. - rule { - name = "Check code coverage of presenters" - target = kotlinx.kover.api.VerificationTarget.CLASS - overrideClassFilter { - includes += "*Presenter" - excludes += "*Fake*Presenter" - excludes += "io.element.android.appnav.loggedin.LoggedInPresenter$*" - // Some options can't be tested at the moment - excludes += "io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*" - excludes += "*Presenter\$present\$*" - } - bound { - minValue = 85 - counter = kotlinx.kover.api.CounterType.INSTRUCTION - valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE - } - } - // Rule to ensure that coverage of States is sufficient. - rule { - name = "Check code coverage of states" - target = kotlinx.kover.api.VerificationTarget.CLASS - overrideClassFilter { - includes += "^*State$" - excludes += "io.element.android.appnav.root.RootNavState*" - excludes += "io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*" - excludes += "io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*" - excludes += "io.element.android.libraries.matrix.api.room.RoomMembershipState*" - excludes += "io.element.android.libraries.matrix.api.room.MatrixRoomMembersState*" - excludes += "io.element.android.libraries.push.impl.notifications.NotificationState*" - excludes += "io.element.android.features.messages.impl.media.local.pdf.PdfViewerState" - excludes += "io.element.android.features.messages.impl.media.local.LocalMediaViewState" - excludes += "io.element.android.features.location.impl.map.MapState*" - excludes += "io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*" - excludes += "io.element.android.libraries.designsystem.swipe.SwipeableActionsState*" - excludes += "io.element.android.features.messages.impl.timeline.components.ExpandableState*" - excludes += "io.element.android.features.messages.impl.timeline.model.bubble.BubbleState*" - excludes += "io.element.android.libraries.maplibre.compose.CameraPositionState*" - excludes += "io.element.android.libraries.maplibre.compose.SaveableCameraPositionState" - excludes += "io.element.android.libraries.maplibre.compose.SymbolState*" - excludes += "io.element.android.features.ftue.api.state.*" - excludes += "io.element.android.features.ftue.impl.welcome.state.*" - } - bound { - minValue = 90 - counter = kotlinx.kover.api.CounterType.INSTRUCTION - valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE - } - } - // Rule to ensure that coverage of Views is sufficient (deactivated for now). - rule { - name = "Check code coverage of views" - target = kotlinx.kover.api.VerificationTarget.CLASS - overrideClassFilter { - includes += "*ViewKt" - } - bound { - // TODO Update this value, for now there are too many missing tests. - minValue = 0 - counter = kotlinx.kover.api.CounterType.INSTRUCTION - valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE - } - } - } -} - -// When running on the CI, run only debug test variants -val ciBuildProperty = "ci-build" -val isCiBuild = if (project.hasProperty(ciBuildProperty)) { - val raw = project.property(ciBuildProperty) as? String - raw?.toBooleanLenient() == true || raw?.toIntOrNull() == 1 -} else { - false -} -if (isCiBuild) { - allprojects { - afterEvaluate { - tasks.withType().configureEach { - extensions.configure { - val enabled = name.contains("debug", ignoreCase = true) - isDisabled.set(!enabled) - } - } - } - } -} - // Register quality check tasks. tasks.register("runQualityChecks") { project.subprojects { @@ -361,7 +190,7 @@ subprojects { // Workaround for https://github.com/airbnb/Showkase/issues/335 subprojects { - tasks.withType() { + tasks.withType { doLast { fileTree(layout.buildDirectory).apply { include("**/*ShowkaseExtension*.kt") }.files.forEach { file -> ReplaceRegExp().apply { @@ -383,13 +212,15 @@ subprojects { if (project.findProperty("composeCompilerReports") == "true") { freeCompilerArgs += listOf( "-P", - "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${project.layout.buildDirectory.asFile.get().absolutePath}/compose_compiler" + "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + + "${project.layout.buildDirectory.asFile.get().absolutePath}/compose_compiler" ) } if (project.findProperty("composeCompilerMetrics") == "true") { freeCompilerArgs += listOf( "-P", - "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=${project.layout.buildDirectory.asFile.get().absolutePath}/compose_compiler" + "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + + "${project.layout.buildDirectory.asFile.get().absolutePath}/compose_compiler" ) } } diff --git a/docs/_developer_onboarding.md b/docs/_developer_onboarding.md index b4ea411710..9d5bdafb7a 100644 --- a/docs/_developer_onboarding.md +++ b/docs/_developer_onboarding.md @@ -344,26 +344,26 @@ implementation of our interfaces. Mocking can be used to mock Android framework [kover](https://github.com/Kotlin/kotlinx-kover) is used to compute code coverage. Only have unit tests can produce code coverage result. Running Maestro does not participate to the code coverage results. -Kover configuration is defined in the main [build.gradle.kts](../build.gradle.kts) file. +Kover configuration is defined in the app [build.gradle.kts](../app/build.gradle.kts) file. To compute the code coverage, run: ```bash -./gradlew koverMergedReport +./gradlew :app:koverHtmlReport ``` -and open the Html report: [../build/reports/kover/merged/html/index.html](../build/reports/kover/merged/html/index.html) +and open the Html report: [../app/build/reports/kover/html/index.html](../app/build/reports/kover/html/index.html) To ensure that the code coverage threshold are OK, you can run ```bash -./gradlew koverMergedVerify +./gradlew :app:koverVerify ``` Note that the CI performs this check on every pull requests. Also, if the rule `Global minimum code coverage.` is in error because code coverage is `> maxValue`, `minValue` and `maxValue` can be updated for this rule in -the file [build.gradle.kts](../build.gradle.kts) (you will see further instructions there). +the file [build.gradle.kts](../app/build.gradle.kts) (you will see further instructions there). ### Other points diff --git a/docs/integration_tests.md b/docs/integration_tests.md index b5a830e7ff..dbd3ce2b68 100644 --- a/docs/integration_tests.md +++ b/docs/integration_tests.md @@ -8,7 +8,7 @@ * [Stop Synapse](#stop-synapse) * [Troubleshoot](#troubleshoot) * [Android Emulator does cannot reach the homeserver](#android-emulator-does-cannot-reach-the-homeserver) - * [Tests partially run but some fail with "Unable to contact localhost:8080"](#tests-partially-run-but-some-fail-with-"unable-to-contact-localhost:8080") + * [Tests partially run but some fail with "Unable to contact localhost:8080"](#tests-partially-run-but-some-fail-with-"unable-to-contact-localhost8080") * [virtualenv command fails](#virtualenv-command-fails) diff --git a/fastlane/metadata/android/en-US/changelogs/40004010.txt b/fastlane/metadata/android/en-US/changelogs/40004010.txt new file mode 100644 index 0000000000..cccc7477c2 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40004010.txt @@ -0,0 +1,2 @@ +Main changes in this version: Mainly bug fixes. +Full changelog: https://github.com/element-hq/element-x-android/releases diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt index 9678068a3c..c9820c6556 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt @@ -25,8 +25,8 @@ import io.element.android.features.analytics.api.AnalyticsOptInEvents import io.element.android.features.analytics.api.R import io.element.android.libraries.designsystem.components.LINK_TAG import io.element.android.libraries.designsystem.components.list.ListItemContent -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.ListSupportingText diff --git a/features/analytics/api/src/main/res/values-es/translations.xml b/features/analytics/api/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..f7739f0f2d --- /dev/null +++ b/features/analytics/api/src/main/res/values-es/translations.xml @@ -0,0 +1,7 @@ + + + "Compartir datos analíticos" + "Compartir datos de uso anónimos para ayudarnos a identificar problemas." + "Puedes leer todos nuestros términos %1$s." + "aquí" + diff --git a/features/analytics/api/src/main/res/values-it/translations.xml b/features/analytics/api/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..02053ccc43 --- /dev/null +++ b/features/analytics/api/src/main/res/values-it/translations.xml @@ -0,0 +1,7 @@ + + + "Condividi dati statistici" + "Condividi dati di utilizzo anonimi per aiutarci a identificare problemi." + "Puoi leggere tutti i nostri termini %1$s." + "qui" + diff --git a/features/analytics/api/src/main/res/values-zh-rTW/translations.xml b/features/analytics/api/src/main/res/values-zh-rTW/translations.xml index db8f91ceea..d4f1072c47 100644 --- a/features/analytics/api/src/main/res/values-zh-rTW/translations.xml +++ b/features/analytics/api/src/main/res/values-zh-rTW/translations.xml @@ -1,7 +1,7 @@ - "分享分析數據" - "分享匿名的使用數據以協助我們釐清問題。" + "提供分析數據" + "提供匿名的使用數據以協助我們釐清問題。" "您可以到%1$s閱讀我們的條款。" "這裡" diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt index 480ccfa512..368efa856c 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt @@ -37,7 +37,6 @@ class AnalyticsOptInNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: AnalyticsOptInPresenter, ) : Node(buildContext, plugins = plugins) { - private fun onClickTerms(activity: Activity, darkTheme: Boolean) { activity.openUrlInChromeCustomTab(null, darkTheme, AnalyticsConfig.POLICY_LINK) } diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt index 3cd2203dbe..43a8304674 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt @@ -30,7 +30,6 @@ class AnalyticsOptInPresenter @Inject constructor( private val buildMeta: BuildMeta, private val analyticsService: AnalyticsService, ) : Presenter { - @Composable override fun present(): AnalyticsOptInState { val localCoroutineScope = rememberCoroutineScope() diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt index 544e4a5649..a66ad2a176 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt @@ -19,8 +19,7 @@ package io.element.android.features.analytics.impl import androidx.compose.ui.tooling.preview.PreviewParameterProvider import javax.inject.Inject -open class AnalyticsOptInStateProvider @Inject constructor( -) : PreviewParameterProvider { +open class AnalyticsOptInStateProvider @Inject constructor() : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aAnalyticsOptInState(), diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/DefaultAnalyticsPreferencesPresenter.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/DefaultAnalyticsPreferencesPresenter.kt index 8b46ff3fa3..d095dddc5f 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/DefaultAnalyticsPreferencesPresenter.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/DefaultAnalyticsPreferencesPresenter.kt @@ -36,7 +36,6 @@ class DefaultAnalyticsPreferencesPresenter @Inject constructor( private val analyticsService: AnalyticsService, private val buildMeta: BuildMeta, ) : AnalyticsPreferencesPresenter { - @Composable override fun present(): AnalyticsPreferencesState { val localCoroutineScope = rememberCoroutineScope() diff --git a/features/analytics/impl/src/main/res/values-es/translations.xml b/features/analytics/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..93cf2e1029 --- /dev/null +++ b/features/analytics/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,10 @@ + + + "No registraremos o perfilaremos ningún dato personal" + "Compartir datos de uso anónimos para ayudarnos a identificar problemas." + "Puedes leer todos nuestros términos %1$s." + "aquí" + "Puedes desactivarlo en cualquier momento" + "No compartiremos tus datos con terceros" + "Ayuda a mejorar %1$s" + diff --git a/features/analytics/impl/src/main/res/values-it/translations.xml b/features/analytics/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..6e030d564c --- /dev/null +++ b/features/analytics/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,10 @@ + + + "Non registreremo né profileremo alcun dato personale" + "Condividi dati di utilizzo anonimi per aiutarci a identificare problemi." + "Puoi leggere tutti i nostri termini %1$s." + "qui" + "Puoi disattivarlo in qualsiasi momento" + "Non condivideremo i tuoi dati con terze parti" + "Aiutaci a migliorare %1$s" + diff --git a/features/analytics/impl/src/main/res/values-zh-rTW/translations.xml b/features/analytics/impl/src/main/res/values-zh-rTW/translations.xml index 433c5b051e..ef7a3c7cbe 100644 --- a/features/analytics/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/analytics/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,7 +1,7 @@ "我們不會紀錄或剖繪您的個人資料" - "分享匿名的使用數據以協助我們釐清問題。" + "提供匿名的使用數據以協助我們釐清問題。" "您可以到%1$s閱讀我們的條款。" "這裡" "您可以在任何時候關閉它" diff --git a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt index 3dcb4674df..94a87abf6b 100644 --- a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt +++ b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt @@ -30,7 +30,6 @@ import org.junit.Rule import org.junit.Test class AnalyticsOptInPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -70,4 +69,3 @@ class AnalyticsOptInPresenterTest { } } } - diff --git a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt index 494468c530..24e17db20e 100644 --- a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt +++ b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt @@ -29,7 +29,6 @@ import org.junit.Rule import org.junit.Test class AnalyticsPreferencesPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -82,4 +81,3 @@ class AnalyticsPreferencesPresenterTest { } } } - diff --git a/features/call/src/main/kotlin/io/element/android/features/call/CallForegroundService.kt b/features/call/src/main/kotlin/io/element/android/features/call/CallForegroundService.kt index 7396ff34ab..5f12ace0ac 100644 --- a/features/call/src/main/kotlin/io/element/android/features/call/CallForegroundService.kt +++ b/features/call/src/main/kotlin/io/element/android/features/call/CallForegroundService.kt @@ -30,7 +30,6 @@ import io.element.android.features.call.ui.ElementCallActivity import io.element.android.libraries.designsystem.utils.CommonDrawables class CallForegroundService : Service() { - companion object { fun start(context: Context) { val intent = Intent(context, CallForegroundService::class.java) diff --git a/features/call/src/main/kotlin/io/element/android/features/call/data/WidgetMessage.kt b/features/call/src/main/kotlin/io/element/android/features/call/data/WidgetMessage.kt index 2b38f6850b..0d9be99cf7 100644 --- a/features/call/src/main/kotlin/io/element/android/features/call/data/WidgetMessage.kt +++ b/features/call/src/main/kotlin/io/element/android/features/call/data/WidgetMessage.kt @@ -28,11 +28,11 @@ data class WidgetMessage( @SerialName("action") val action: Action, @SerialName("data") val data: JsonElement? = null, ) { - @Serializable enum class Direction { @SerialName("fromWidget") FromWidget, + @SerialName("toWidget") ToWidget } @@ -41,6 +41,7 @@ data class WidgetMessage( enum class Action { @SerialName("im.vector.hangup") HangUp, + @SerialName("send_event") SendEvent, } diff --git a/features/call/src/main/kotlin/io/element/android/features/call/ui/CallScreenPresenter.kt b/features/call/src/main/kotlin/io/element/android/features/call/ui/CallScreenPresenter.kt index 6883ebeb61..1ec75ed47d 100644 --- a/features/call/src/main/kotlin/io/element/android/features/call/ui/CallScreenPresenter.kt +++ b/features/call/src/main/kotlin/io/element/android/features/call/ui/CallScreenPresenter.kt @@ -34,7 +34,7 @@ import io.element.android.features.call.data.WidgetMessage import io.element.android.features.call.utils.CallWidgetProvider import io.element.android.features.call.utils.WidgetMessageInterceptor import io.element.android.features.call.utils.WidgetMessageSerializer -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -63,7 +63,6 @@ class CallScreenPresenter @AssistedInject constructor( private val matrixClientsProvider: MatrixClientProvider, private val appCoroutineScope: CoroutineScope, ) : Presenter { - @AssistedFactory interface Factory { fun create(callType: CallType, navigator: CallScreenNavigator): CallScreenPresenter @@ -75,7 +74,7 @@ class CallScreenPresenter @AssistedInject constructor( @Composable override fun present(): CallScreenState { val coroutineScope = rememberCoroutineScope() - val urlState = remember { mutableStateOf>(Async.Uninitialized) } + val urlState = remember { mutableStateOf>(AsyncData.Uninitialized) } val callWidgetDriver = remember { mutableStateOf(null) } val messageInterceptor = remember { mutableStateOf(null) } var isJoinedCall by rememberSaveable { mutableStateOf(false) } @@ -154,7 +153,7 @@ class CallScreenPresenter @AssistedInject constructor( private fun CoroutineScope.loadUrl( inputs: CallType, - urlState: MutableState>, + urlState: MutableState>, callWidgetDriver: MutableState, ) = launch { urlState.runCatchingUpdatingState { @@ -224,6 +223,4 @@ class CallScreenPresenter @AssistedInject constructor( navigator.close() widgetDriver?.close() } - } - diff --git a/features/call/src/main/kotlin/io/element/android/features/call/ui/CallScreenState.kt b/features/call/src/main/kotlin/io/element/android/features/call/ui/CallScreenState.kt index 12cd7612ae..76926bfb9f 100644 --- a/features/call/src/main/kotlin/io/element/android/features/call/ui/CallScreenState.kt +++ b/features/call/src/main/kotlin/io/element/android/features/call/ui/CallScreenState.kt @@ -16,10 +16,10 @@ package io.element.android.features.call.ui -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData data class CallScreenState( - val urlState: Async, + val urlState: AsyncData, val userAgent: String, val isInWidgetMode: Boolean, val eventSink: (CallScreenEvents) -> Unit, diff --git a/features/call/src/main/kotlin/io/element/android/features/call/ui/CallScreenView.kt b/features/call/src/main/kotlin/io/element/android/features/call/ui/CallScreenView.kt index f2c8a0cb93..c28bacf88a 100644 --- a/features/call/src/main/kotlin/io/element/android/features/call/ui/CallScreenView.kt +++ b/features/call/src/main/kotlin/io/element/android/features/call/ui/CallScreenView.kt @@ -36,7 +36,7 @@ import androidx.compose.ui.viewinterop.AndroidView import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.call.R import io.element.android.features.call.utils.WebViewWidgetMessageInterceptor -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -96,7 +96,7 @@ internal fun CallScreenView( @Composable private fun CallWebView( - url: Async, + url: AsyncData, userAgent: String, onPermissionsRequested: (PermissionRequest) -> Unit, onWebViewCreated: (WebView) -> Unit, @@ -116,7 +116,7 @@ private fun CallWebView( } }, update = { webView -> - if (url is Async.Success && webView.url != url.data) { + if (url is AsyncData.Success && webView.url != url.data) { webView.loadUrl(url.data) } }, @@ -161,7 +161,7 @@ internal fun CallScreenViewPreview() { ElementPreview { CallScreenView( state = CallScreenState( - urlState = Async.Success("https://call.element.io/some-actual-call?with=parameters"), + urlState = AsyncData.Success("https://call.element.io/some-actual-call?with=parameters"), isInWidgetMode = false, userAgent = "", eventSink = {}, diff --git a/features/call/src/main/kotlin/io/element/android/features/call/utils/CallIntentDataParser.kt b/features/call/src/main/kotlin/io/element/android/features/call/utils/CallIntentDataParser.kt index 0814216745..c9f4532951 100644 --- a/features/call/src/main/kotlin/io/element/android/features/call/utils/CallIntentDataParser.kt +++ b/features/call/src/main/kotlin/io/element/android/features/call/utils/CallIntentDataParser.kt @@ -20,7 +20,6 @@ import android.net.Uri import javax.inject.Inject class CallIntentDataParser @Inject constructor() { - private val validHttpSchemes = sequenceOf("https") fun parse(data: String?): String? { diff --git a/features/call/src/main/kotlin/io/element/android/features/call/utils/WebViewWidgetMessageInterceptor.kt b/features/call/src/main/kotlin/io/element/android/features/call/utils/WebViewWidgetMessageInterceptor.kt index e11529f068..c4676ac9dc 100644 --- a/features/call/src/main/kotlin/io/element/android/features/call/utils/WebViewWidgetMessageInterceptor.kt +++ b/features/call/src/main/kotlin/io/element/android/features/call/utils/WebViewWidgetMessageInterceptor.kt @@ -28,7 +28,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow class WebViewWidgetMessageInterceptor( private val webView: WebView, ) : WidgetMessageInterceptor { - companion object { // We call both the WebMessageListener and the JavascriptInterface objects in JS with this // 'listenerName' so they can both receive the data from the WebView when @@ -56,7 +55,7 @@ class WebViewWidgetMessageInterceptor( || !message.data.response && message.data.api == "fromWidget") { let json = JSON.stringify(event.data) ${"console.log('message sent: ' + json);".takeIf { BuildConfig.DEBUG } } - ${LISTENER_NAME}.postMessage(json); + $LISTENER_NAME.postMessage(json); } else { ${"console.log('message received (ignored): ' + JSON.stringify(event.data));".takeIf { BuildConfig.DEBUG } } } diff --git a/features/call/src/main/kotlin/io/element/android/features/call/utils/WidgetMessageSerializer.kt b/features/call/src/main/kotlin/io/element/android/features/call/utils/WidgetMessageSerializer.kt index 5ed9db028c..aa7424e9a3 100644 --- a/features/call/src/main/kotlin/io/element/android/features/call/utils/WidgetMessageSerializer.kt +++ b/features/call/src/main/kotlin/io/element/android/features/call/utils/WidgetMessageSerializer.kt @@ -20,7 +20,6 @@ import io.element.android.features.call.data.WidgetMessage import kotlinx.serialization.json.Json object WidgetMessageSerializer { - private val coder = Json { ignoreUnknownKeys = true } fun deserialize(message: String): Result { diff --git a/features/call/src/main/res/values-es/translations.xml b/features/call/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..2e93185926 --- /dev/null +++ b/features/call/src/main/res/values-es/translations.xml @@ -0,0 +1,6 @@ + + + "Llamada en curso" + "Pulsa para regresar a la llamada" + "☎️ Llamada en curso" + diff --git a/features/call/src/main/res/values-hu/translations.xml b/features/call/src/main/res/values-hu/translations.xml index fee5a163bb..c85521c392 100644 --- a/features/call/src/main/res/values-hu/translations.xml +++ b/features/call/src/main/res/values-hu/translations.xml @@ -1,6 +1,6 @@ "Folyamatban lévő hívás" - "Koppintson a híváshoz való visszatéréshez" + "Koppints a híváshoz való visszatéréshez" "☎️ Hívás folyamatban" diff --git a/features/call/src/main/res/values-it/translations.xml b/features/call/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..38eb65b09c --- /dev/null +++ b/features/call/src/main/res/values-it/translations.xml @@ -0,0 +1,6 @@ + + + "Chiamata in corso" + "Tocca per tornare alla chiamata" + "☎️ Chiamata in corso" + diff --git a/features/call/src/main/res/values-ru/translations.xml b/features/call/src/main/res/values-ru/translations.xml index c77095f3f2..4399785f75 100644 --- a/features/call/src/main/res/values-ru/translations.xml +++ b/features/call/src/main/res/values-ru/translations.xml @@ -2,5 +2,5 @@ "Текущий вызов" "Коснитесь, чтобы вернуться к вызову" - "Идёт вызов" + "☎️ Идёт вызов" diff --git a/features/call/src/test/kotlin/io/element/android/features/call/MapWebkitPermissionsTest.kt b/features/call/src/test/kotlin/io/element/android/features/call/MapWebkitPermissionsTest.kt index fa60b348b1..6a24698efa 100644 --- a/features/call/src/test/kotlin/io/element/android/features/call/MapWebkitPermissionsTest.kt +++ b/features/call/src/test/kotlin/io/element/android/features/call/MapWebkitPermissionsTest.kt @@ -23,7 +23,6 @@ import io.element.android.features.call.ui.mapWebkitPermissions import org.junit.Test class MapWebkitPermissionsTest { - @Test fun `given Webkit's RESOURCE_AUDIO_CAPTURE returns Android's RECORD_AUDIO permission`() { val permission = mapWebkitPermissions(arrayOf(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) diff --git a/features/call/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt b/features/call/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt index 77f83de209..8eecc94e78 100644 --- a/features/call/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt +++ b/features/call/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt @@ -23,7 +23,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.call.CallType import io.element.android.features.call.utils.FakeCallWidgetProvider import io.element.android.features.call.utils.FakeWidgetMessageInterceptor -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.test.A_ROOM_ID @@ -48,7 +48,6 @@ import org.junit.Rule import org.junit.Test class CallScreenPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -62,7 +61,7 @@ class CallScreenPresenterTest { skipItems(1) val initialState = awaitItem() - assertThat(initialState.urlState).isEqualTo(Async.Success("https://call.element.io")) + assertThat(initialState.urlState).isEqualTo(AsyncData.Success("https://call.element.io")) assertThat(initialState.isInWidgetMode).isFalse() } } @@ -83,7 +82,7 @@ class CallScreenPresenterTest { skipItems(1) val initialState = awaitItem() - assertThat(initialState.urlState).isInstanceOf(Async.Success::class.java) + assertThat(initialState.urlState).isInstanceOf(AsyncData.Success::class.java) assertThat(initialState.isInWidgetMode).isTrue() assertThat(widgetProvider.getWidgetCalled).isTrue() assertThat(widgetDriver.runCalledCount).isEqualTo(1) @@ -191,7 +190,7 @@ class CallScreenPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - consumeItemsUntilTimeout() + consumeItemsUntilTimeout() assertThat(matrixClient.syncService().syncState.value).isEqualTo(SyncState.Running) diff --git a/features/call/src/test/kotlin/io/element/android/features/call/ui/FakeCallScreenNavigator.kt b/features/call/src/test/kotlin/io/element/android/features/call/ui/FakeCallScreenNavigator.kt index 498503cb15..23e0cf9027 100644 --- a/features/call/src/test/kotlin/io/element/android/features/call/ui/FakeCallScreenNavigator.kt +++ b/features/call/src/test/kotlin/io/element/android/features/call/ui/FakeCallScreenNavigator.kt @@ -17,10 +17,10 @@ package io.element.android.features.call.ui class FakeCallScreenNavigator : CallScreenNavigator { - var closeCalled = false - private set + var closeCalled = false + private set - override fun close() { - closeCalled = true - } + override fun close() { + closeCalled = true } +} diff --git a/features/call/src/test/kotlin/io/element/android/features/call/utils/CallIntentDataParserTest.kt b/features/call/src/test/kotlin/io/element/android/features/call/utils/CallIntentDataParserTest.kt index eb8e756182..093b6aab3b 100644 --- a/features/call/src/test/kotlin/io/element/android/features/call/utils/CallIntentDataParserTest.kt +++ b/features/call/src/test/kotlin/io/element/android/features/call/utils/CallIntentDataParserTest.kt @@ -24,7 +24,6 @@ import java.net.URLEncoder @RunWith(RobolectricTestRunner::class) class CallIntentDataParserTest { - private val callIntentDataParser = CallIntentDataParser() @Test @@ -117,7 +116,7 @@ class CallIntentDataParserTest { @Test fun `Element Call url with url extra param appPrompt gets url extracted`() { doTest( - url = "${VALID_CALL_URL_WITH_PARAM}&appPrompt=true", + url = "$VALID_CALL_URL_WITH_PARAM&appPrompt=true", expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS" ) } @@ -125,7 +124,7 @@ class CallIntentDataParserTest { @Test fun `Element Call url with url extra param in fragment appPrompt gets url extracted`() { doTest( - url = "${VALID_CALL_URL_WITH_PARAM}#?appPrompt=true", + url = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=true", expectedResult = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=false&confineToRoom=true" ) } @@ -133,7 +132,7 @@ class CallIntentDataParserTest { @Test fun `Element Call url with url extra param in fragment appPrompt and other gets url extracted`() { doTest( - url = "${VALID_CALL_URL_WITH_PARAM}#?appPrompt=true&otherParam=maybe", + url = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=true&otherParam=maybe", expectedResult = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=false&otherParam=maybe&confineToRoom=true" ) } @@ -141,7 +140,7 @@ class CallIntentDataParserTest { @Test fun `Element Call url with url extra param confineToRoom gets url extracted`() { doTest( - url = "${VALID_CALL_URL_WITH_PARAM}&confineToRoom=false", + url = "$VALID_CALL_URL_WITH_PARAM&confineToRoom=false", expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS" ) } @@ -149,7 +148,7 @@ class CallIntentDataParserTest { @Test fun `Element Call url with url extra param in fragment confineToRoom gets url extracted`() { doTest( - url = "${VALID_CALL_URL_WITH_PARAM}#?confineToRoom=false", + url = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=false", expectedResult = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=true&appPrompt=false" ) } @@ -157,7 +156,7 @@ class CallIntentDataParserTest { @Test fun `Element Call url with url extra param in fragment confineToRoom and more gets url extracted`() { doTest( - url = "${VALID_CALL_URL_WITH_PARAM}#?confineToRoom=false&otherParam=maybe", + url = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=false&otherParam=maybe", expectedResult = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=true&otherParam=maybe&appPrompt=false" ) } @@ -165,7 +164,7 @@ class CallIntentDataParserTest { @Test fun `Element Call url with url fragment gets url extracted`() { doTest( - url = "${VALID_CALL_URL_WITH_PARAM}#fragment", + url = "$VALID_CALL_URL_WITH_PARAM#fragment", expectedResult = "$VALID_CALL_URL_WITH_PARAM#fragment?$EXTRA_PARAMS" ) } @@ -173,7 +172,7 @@ class CallIntentDataParserTest { @Test fun `Element Call url with url fragment with params gets url extracted`() { doTest( - url = "${VALID_CALL_URL_WITH_PARAM}#fragment?otherParam=maybe", + url = "$VALID_CALL_URL_WITH_PARAM#fragment?otherParam=maybe", expectedResult = "$VALID_CALL_URL_WITH_PARAM#fragment?otherParam=maybe&$EXTRA_PARAMS" ) } @@ -181,7 +180,7 @@ class CallIntentDataParserTest { @Test fun `Element Call url with url fragment with other params gets url extracted`() { doTest( - url = "${VALID_CALL_URL_WITH_PARAM}#?otherParam=maybe", + url = "$VALID_CALL_URL_WITH_PARAM#?otherParam=maybe", expectedResult = "$VALID_CALL_URL_WITH_PARAM#?otherParam=maybe&$EXTRA_PARAMS" ) } @@ -189,7 +188,7 @@ class CallIntentDataParserTest { @Test fun `Element Call url with empty fragment`() { doTest( - url = "${VALID_CALL_URL_WITH_PARAM}#", + url = "$VALID_CALL_URL_WITH_PARAM#", expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS" ) } @@ -197,7 +196,7 @@ class CallIntentDataParserTest { @Test fun `Element Call url with empty fragment query`() { doTest( - url = "${VALID_CALL_URL_WITH_PARAM}#?", + url = "$VALID_CALL_URL_WITH_PARAM#?", expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS" ) } diff --git a/features/call/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt b/features/call/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt index f7f17d794d..50d8c54998 100644 --- a/features/call/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt +++ b/features/call/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt @@ -32,7 +32,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class DefaultCallWidgetProviderTest { - @Test fun `getWidget - fails if the session does not exist`() = runTest { val provider = createProvider(matrixClientProvider = FakeMatrixClientProvider { Result.failure(Exception("Session not found")) }) diff --git a/features/call/src/test/kotlin/io/element/android/features/call/utils/FakeCallWidgetProvider.kt b/features/call/src/test/kotlin/io/element/android/features/call/utils/FakeCallWidgetProvider.kt index 69ae340648..27e47ee708 100644 --- a/features/call/src/test/kotlin/io/element/android/features/call/utils/FakeCallWidgetProvider.kt +++ b/features/call/src/test/kotlin/io/element/android/features/call/utils/FakeCallWidgetProvider.kt @@ -24,19 +24,18 @@ import io.element.android.libraries.matrix.test.widget.FakeWidgetDriver class FakeCallWidgetProvider( private val widgetDriver: FakeWidgetDriver = FakeWidgetDriver(), private val url: String = "https://call.element.io", - ) : CallWidgetProvider { +) : CallWidgetProvider { + var getWidgetCalled = false + private set - var getWidgetCalled = false - private set - - override suspend fun getWidget( - sessionId: SessionId, - roomId: RoomId, - clientId: String, - languageTag: String?, - theme: String? - ): Result> { - getWidgetCalled = true - return Result.success(widgetDriver to url) - } + override suspend fun getWidget( + sessionId: SessionId, + roomId: RoomId, + clientId: String, + languageTag: String?, + theme: String? + ): Result> { + getWidgetCalled = true + return Result.success(widgetDriver to url) } +} diff --git a/features/call/src/test/kotlin/io/element/android/features/call/utils/FakeWidgetMessageInterceptor.kt b/features/call/src/test/kotlin/io/element/android/features/call/utils/FakeWidgetMessageInterceptor.kt index 6e36dfff81..5a312250b0 100644 --- a/features/call/src/test/kotlin/io/element/android/features/call/utils/FakeWidgetMessageInterceptor.kt +++ b/features/call/src/test/kotlin/io/element/android/features/call/utils/FakeWidgetMessageInterceptor.kt @@ -19,15 +19,15 @@ package io.element.android.features.call.utils import kotlinx.coroutines.flow.MutableSharedFlow class FakeWidgetMessageInterceptor : WidgetMessageInterceptor { - val sentMessages = mutableListOf() + val sentMessages = mutableListOf() - override val interceptedMessages = MutableSharedFlow(extraBufferCapacity = 1) + override val interceptedMessages = MutableSharedFlow(extraBufferCapacity = 1) - override fun sendMessage(message: String) { - sentMessages += message - } - - fun givenInterceptedMessage(message: String) { - interceptedMessages.tryEmit(message) - } + override fun sendMessage(message: String) { + sentMessages += message } + + fun givenInterceptedMessage(message: String) { + interceptedMessages.tryEmit(message) + } +} diff --git a/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt index 18e0e4e28f..f4941f160e 100644 --- a/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt +++ b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt @@ -23,7 +23,6 @@ import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.matrix.api.core.RoomId interface CreateRoomEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder interface NodeBuilder { fun callback(callback: Callback): NodeBuilder diff --git a/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/StartDMAction.kt b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/StartDMAction.kt index ef95f15dce..baacfa79dc 100644 --- a/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/StartDMAction.kt +++ b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/StartDMAction.kt @@ -17,7 +17,7 @@ package io.element.android.features.createroom.api import androidx.compose.runtime.MutableState -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId @@ -27,5 +27,5 @@ interface StartDMAction { * @param userId The user to start a DM with. * @param actionState The state to update with the result of the action. */ - suspend fun execute(userId: UserId, actionState: MutableState>) + suspend fun execute(userId: UserId, actionState: MutableState>) } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt index 78fbc6f77a..b97f57b9a5 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt @@ -52,7 +52,6 @@ class ConfigureRoomFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins ) { - private val component by lazy { parent!!.bindings().createRoomComponentBuilder().build() } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt index f79284d082..9a7d68709b 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt @@ -33,7 +33,6 @@ import javax.inject.Inject class CreateRoomDataStore @Inject constructor( val selectedUserListDataStore: UserListDataStore, ) { - private val createRoomConfigFlow: MutableStateFlow = MutableStateFlow(CreateRoomConfig()) private var cachedAvatarUri: Uri? = null set(value) { diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt index 643987e0a3..825ef9f025 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt @@ -50,7 +50,6 @@ class CreateRoomFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins ) { - sealed interface NavTarget : Parcelable { @Parcelize data object Root : NavTarget diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt index 34e514be3e..4c234a4fa3 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt @@ -27,13 +27,10 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultCreateRoomEntryPoint @Inject constructor() : CreateRoomEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): CreateRoomEntryPoint.NodeBuilder { - val plugins = ArrayList() return object : CreateRoomEntryPoint.NodeBuilder { - override fun callback(callback: CreateRoomEntryPoint.Callback): CreateRoomEntryPoint.NodeBuilder { plugins += callback return this diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt index 7145ac671e..d3b6e91db6 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt @@ -20,7 +20,7 @@ import androidx.compose.runtime.MutableState import com.squareup.anvil.annotations.ContributesBinding import im.vector.app.features.analytics.plan.CreatedRoom import io.element.android.features.createroom.api.StartDMAction -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId @@ -35,18 +35,17 @@ class DefaultStartDMAction @Inject constructor( private val matrixClient: MatrixClient, private val analyticsService: AnalyticsService, ) : StartDMAction { - - override suspend fun execute(userId: UserId, actionState: MutableState>) { - actionState.value = Async.Loading() + override suspend fun execute(userId: UserId, actionState: MutableState>) { + actionState.value = AsyncAction.Loading when (val result = matrixClient.startDM(userId)) { is StartDMResult.Success -> { if (result.isNew) { analyticsService.capture(CreatedRoom(isDM = true)) } - actionState.value = Async.Success(result.roomId) + actionState.value = AsyncAction.Success(result.roomId) } is StartDMResult.Failure -> { - actionState.value = Async.Failure(result.throwable) + actionState.value = AsyncAction.Failure(result.throwable) } } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleNode.kt index 1b4bd9ac8d..f00f10905d 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleNode.kt @@ -33,7 +33,6 @@ class AddPeopleNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: AddPeoplePresenter, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun onContinue() } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenter.kt index e3f90943c1..1bc8eb12f9 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenter.kt @@ -31,7 +31,6 @@ class AddPeoplePresenter @Inject constructor( userRepository: UserRepository, dataStore: CreateRoomDataStore, ) : Presenter { - private val userListPresenter = userListPresenterFactory.create( UserListPresenterArgs( selectionMode = SelectionMode.Multiple, @@ -45,4 +44,3 @@ class AddPeoplePresenter @Inject constructor( return userListPresenter.present() } } - diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleUserListStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleUserListStateProvider.kt index 79969eeac7..9c7991de6b 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleUserListStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleUserListStateProvider.kt @@ -37,11 +37,13 @@ open class AddPeopleUserListStateProvider : PreviewParameterProvider - UserSearchResult(matrixUser, index % 2 == 0) - } - .toImmutableList()), + searchResults = SearchBarResultState.Results( + aMatrixUserList() + .mapIndexed { index, matrixUser -> + UserSearchResult(matrixUser, index % 2 == 0) + } + .toImmutableList() + ), selectedUsers = aListOfSelectedUsers(), isSearchActive = true, selectionMode = SelectionMode.Multiple, diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt index 1a8540cd0e..147020fddb 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt @@ -17,7 +17,6 @@ package io.element.android.features.createroom.impl.addpeople import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -27,6 +26,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.compound.theme.ElementTheme import io.element.android.features.createroom.impl.R import io.element.android.features.createroom.impl.components.UserListView import io.element.android.features.createroom.impl.userlist.UserListEvents @@ -39,10 +39,8 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TopAppBar -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings -@OptIn(ExperimentalLayoutApi::class) @Composable fun AddPeopleView( state: UserListState, diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomPrivacyOption.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomPrivacyOption.kt index a7f7c80912..1bfd1567c2 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomPrivacyOption.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomPrivacyOption.kt @@ -29,14 +29,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.features.createroom.impl.configureroom.RoomPrivacyItem import io.element.android.features.createroom.impl.configureroom.roomPrivacyItems -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.RadioButton import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.ElementTheme @Composable fun RoomPrivacyOption( @@ -85,7 +85,8 @@ fun RoomPrivacyOption( .align(Alignment.CenterVertically) .size(48.dp), selected = isSelected, - onClick = null // null recommended for accessibility with screenreaders + // null recommended for accessibility with screenreaders + onClick = null ) } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchMultipleUsersResultItem.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchMultipleUsersResultItem.kt index 20f2c78d8a..b53c8a7170 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchMultipleUsersResultItem.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchMultipleUsersResultItem.kt @@ -57,10 +57,7 @@ fun SearchMultipleUsersResultItem( @Preview @Composable -internal fun SearchMultipleUsersResultItemPreview() = ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun SearchMultipleUsersResultItemPreview() = ElementThemedPreview { Column { SearchMultipleUsersResultItem( searchResult = UserSearchResult( diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchSingleUserResultItem.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchSingleUserResultItem.kt index dc4f936acb..c71ef06b99 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchSingleUserResultItem.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchSingleUserResultItem.kt @@ -53,10 +53,7 @@ fun SearchSingleUserResultItem( @Preview @Composable -internal fun SearchSingleUserResultItemPreview() = ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun SearchSingleUserResultItemPreview() = ElementThemedPreview { Column { SearchSingleUserResultItem( searchResult = UserSearchResult(aMatrixUser(), isUnresolved = false), diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchUserBar.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchUserBar.kt index bc562627cc..c047a60018 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchUserBar.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchUserBar.kt @@ -35,6 +35,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.components.async.AsyncLoading import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.SearchBar import io.element.android.libraries.designsystem.theme.components.SearchBarResultState @@ -49,6 +50,7 @@ import kotlinx.collections.immutable.ImmutableList fun SearchUserBar( query: String, state: SearchBarResultState>, + showLoader: Boolean, selectedUsers: ImmutableList, active: Boolean, isMultiSelectionEnabled: Boolean, @@ -99,6 +101,11 @@ fun SearchUserBar( ) } }, + contentSuffix = { + if (showLoader) { + AsyncLoading() + } + }, resultState = state, resultHandler = { users -> LazyColumn(state = columnState) { diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/UserListView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/UserListView.kt index 45d898577f..b13d682250 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/UserListView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/UserListView.kt @@ -48,6 +48,7 @@ fun UserListView( state = state.searchResults, selectedUsers = state.selectedUsers, active = state.isSearchActive, + showLoader = state.showSearchLoader, isMultiSelectionEnabled = state.isMultiSelectionEnabled, showBackButton = showBackButton, onActiveChanged = { state.eventSink(UserListEvents.OnSearchActiveChanged(it)) }, diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt index b09863b205..1d3499f743 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt @@ -38,7 +38,6 @@ class ConfigureRoomNode @AssistedInject constructor( private val presenter: ConfigureRoomPresenter, private val analyticsService: AnalyticsService, ) : Node(buildContext, plugins = plugins) { - init { lifecycle.subscribe( onResume = { diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt index 4cefa82a31..dbb8f1775a 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt @@ -29,7 +29,7 @@ import androidx.compose.runtime.rememberCoroutineScope import im.vector.app.features.analytics.plan.CreatedRoom import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.createroom.impl.CreateRoomDataStore -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.mimetype.MimeTypes @@ -57,7 +57,6 @@ class ConfigureRoomPresenter @Inject constructor( private val analyticsService: AnalyticsService, permissionsPresenterFactory: PermissionsPresenter.Factory, ) : Presenter { - private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA) private var pendingPermissionRequest = false @@ -91,10 +90,10 @@ class ConfigureRoomPresenter @Inject constructor( } val localCoroutineScope = rememberCoroutineScope() - val createRoomAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + val createRoomAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } fun createRoom(config: CreateRoomConfig) { - createRoomAction.value = Async.Uninitialized + createRoomAction.value = AsyncAction.Uninitialized localCoroutineScope.createRoom(config, createRoomAction) } @@ -118,7 +117,7 @@ class ConfigureRoomPresenter @Inject constructor( } } - ConfigureRoomEvents.CancelCreateRoom -> createRoomAction.value = Async.Uninitialized + ConfigureRoomEvents.CancelCreateRoom -> createRoomAction.value = AsyncAction.Uninitialized } } @@ -133,7 +132,7 @@ class ConfigureRoomPresenter @Inject constructor( private fun CoroutineScope.createRoom( config: CreateRoomConfig, - createRoomAction: MutableState> + createRoomAction: MutableState> ) = launch { suspend { val avatarUrl = config.avatarUri?.let { uploadAvatar(it) } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt index 7e16cedaa7..f275a0e217 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt @@ -16,17 +16,17 @@ package io.element.android.features.createroom.impl.configureroom -import io.element.android.libraries.matrix.ui.media.AvatarAction import io.element.android.features.createroom.impl.CreateRoomConfig -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.ui.media.AvatarAction import io.element.android.libraries.permissions.api.PermissionsState import kotlinx.collections.immutable.ImmutableList data class ConfigureRoomState( val config: CreateRoomConfig, val avatarActions: ImmutableList, - val createRoomAction: Async, + val createRoomAction: AsyncAction, val cameraPermissionState: PermissionsState, val eventSink: (ConfigureRoomEvents) -> Unit ) { diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt index eafbe6915b..1065f746e0 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt @@ -19,7 +19,7 @@ package io.element.android.features.createroom.impl.configureroom import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.createroom.impl.userlist.aListOfSelectedUsers -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.permissions.api.aPermissionsState import kotlinx.collections.immutable.persistentListOf @@ -41,7 +41,7 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider().forEach { it.onCreateNewRoom() } - } + private fun onCreateNewRoom() { + plugins().forEach { it.onCreateNewRoom() } + } - override fun onStartChatSuccess(roomId: RoomId) { - plugins().forEach { it.onStartChatSuccess(roomId) } - } + private fun onStartChatSuccess(roomId: RoomId) { + plugins().forEach { it.onStartChatSuccess(roomId) } } init { @@ -72,8 +69,8 @@ class CreateRoomRootNode @AssistedInject constructor( state = state, modifier = modifier, onClosePressed = this::navigateUp, - onNewRoomClicked = callback::onCreateNewRoom, - onOpenDM = callback::onStartChatSuccess, + onNewRoomClicked = ::onCreateNewRoom, + onOpenDM = ::onStartChatSuccess, onInviteFriendsClicked = { invitePeople(activity) } ) } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt index 9b60924ca4..594a87c327 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt @@ -26,7 +26,7 @@ import io.element.android.features.createroom.impl.userlist.SelectionMode import io.element.android.features.createroom.impl.userlist.UserListDataStore import io.element.android.features.createroom.impl.userlist.UserListPresenter import io.element.android.features.createroom.impl.userlist.UserListPresenterArgs -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.core.RoomId @@ -41,7 +41,6 @@ class CreateRoomRootPresenter @Inject constructor( private val startDMAction: StartDMAction, private val buildMeta: BuildMeta, ) : Presenter { - private val presenter = presenterFactory.create( UserListPresenterArgs( selectionMode = SelectionMode.Single, @@ -55,14 +54,14 @@ class CreateRoomRootPresenter @Inject constructor( val userListState = presenter.present() val localCoroutineScope = rememberCoroutineScope() - val startDmActionState: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + val startDmActionState: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } fun handleEvents(event: CreateRoomRootEvents) { when (event) { is CreateRoomRootEvents.StartDM -> localCoroutineScope.launch { startDMAction.execute(event.matrixUser.userId, startDmActionState) } - CreateRoomRootEvents.CancelStartDM -> startDmActionState.value = Async.Uninitialized + CreateRoomRootEvents.CancelStartDM -> startDmActionState.value = AsyncAction.Uninitialized } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootState.kt index 02f64a6c86..185c08c131 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootState.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootState.kt @@ -17,12 +17,12 @@ package io.element.android.features.createroom.impl.root import io.element.android.features.createroom.impl.userlist.UserListState -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId data class CreateRoomRootState( val applicationName: String, val userListState: UserListState, - val startDmAction: Async, + val startDmAction: AsyncAction, val eventSink: (CreateRoomRootEvents) -> Unit, ) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt index 7a3bf6ef03..723c650793 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt @@ -18,8 +18,7 @@ package io.element.android.features.createroom.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.createroom.impl.userlist.aUserListState - -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.usersearch.api.UserSearchResult @@ -30,7 +29,7 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider> by remember { - mutableStateOf(SearchBarResultState.NotSearching()) + mutableStateOf(SearchBarResultState.Initial()) } + var showSearchLoader by remember { mutableStateOf(false) } LaunchedEffect(searchQuery) { - searchResults = SearchBarResultState.NotSearching() - - userRepository.search(searchQuery).collect { + searchResults = SearchBarResultState.Initial() + showSearchLoader = false + userRepository.search(searchQuery).onEach { state -> + showSearchLoader = state.isSearching searchResults = when { - it.isEmpty() -> SearchBarResultState.NoResults() - else -> SearchBarResultState.Results(it.toImmutableList()) + state.results.isEmpty() && state.isSearching -> SearchBarResultState.Initial() + state.results.isEmpty() && !state.isSearching -> SearchBarResultState.NoResultsFound() + else -> SearchBarResultState.Results(state.results.toImmutableList()) } - } + }.launchIn(this) } return UserListState( @@ -76,6 +80,7 @@ class DefaultUserListPresenter @AssistedInject constructor( searchResults = searchResults, selectedUsers = selectedUsers.toImmutableList(), isSearchActive = isSearchActive, + showSearchLoader = showSearchLoader, selectionMode = args.selectionMode, eventSink = { event -> when (event) { diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListDataStore.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListDataStore.kt index 8de7be3114..5aae1a5a14 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListDataStore.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListDataStore.kt @@ -22,7 +22,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import javax.inject.Inject class UserListDataStore @Inject constructor() { - private val selectedUsers: MutableStateFlow> = MutableStateFlow(emptyList()) fun selectUser(user: MatrixUser) { diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListPresenter.kt index e5d68a2e28..613d64ede7 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListPresenter.kt @@ -20,7 +20,6 @@ import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.usersearch.api.UserRepository interface UserListPresenter : Presenter { - interface Factory { fun create( args: UserListPresenterArgs, diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListState.kt index 60a5bea506..a27191881e 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListState.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListState.kt @@ -24,6 +24,7 @@ import kotlinx.collections.immutable.ImmutableList data class UserListState( val searchQuery: String, val searchResults: SearchBarResultState>, + val showSearchLoader: Boolean, val selectedUsers: ImmutableList, val isSearchActive: Boolean, val selectionMode: SelectionMode, diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListStateProvider.kt index 31d1f6953a..4685bb47e3 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListStateProvider.kt @@ -51,17 +51,19 @@ open class UserListStateProvider : PreviewParameterProvider { aUserListState().copy( isSearchActive = true, searchQuery = "something-with-no-results", - searchResults = SearchBarResultState.NoResults() + searchResults = SearchBarResultState.NoResultsFound() ), + aUserListState().copy(isSearchActive = true, searchQuery = "someone", selectionMode = SelectionMode.Single), ) } fun aUserListState() = UserListState( isSearchActive = false, searchQuery = "", - searchResults = SearchBarResultState.NotSearching(), + searchResults = SearchBarResultState.Initial(), selectedUsers = persistentListOf(), selectionMode = SelectionMode.Single, + showSearchLoader = false, eventSink = {} ) diff --git a/features/createroom/impl/src/main/res/values-de/translations.xml b/features/createroom/impl/src/main/res/values-de/translations.xml index 1a027c9d2d..59ebe82ff1 100644 --- a/features/createroom/impl/src/main/res/values-de/translations.xml +++ b/features/createroom/impl/src/main/res/values-de/translations.xml @@ -1,7 +1,7 @@ "Neuer Raum" - "Freunde zu Element einladen" + "Zu Element einladen" "Personen einladen" "Beim Erstellen des Raums ist ein Fehler aufgetreten" "Die Nachrichten in diesem Raum sind verschlüsselt. Die Verschlüsselung kann nicht nachträglich deaktiviert werden." diff --git a/features/createroom/impl/src/main/res/values-es/translations.xml b/features/createroom/impl/src/main/res/values-es/translations.xml index 9a5d672fd4..1c4c687d31 100644 --- a/features/createroom/impl/src/main/res/values-es/translations.xml +++ b/features/createroom/impl/src/main/res/values-es/translations.xml @@ -1,8 +1,15 @@ "Nueva sala" - "Invitar gente" - "Añadir personas" + "Invita a alguien a Element" + "Invitar personas" + "Se ha producido un error al crear la sala" + "Los mensajes de esta sala están cifrados. La encriptación no se puede desactivar después." + "Sala privada (sólo con invitación)" + "Los mensajes no están cifrados y cualquiera puede leerlos. Puedes activar la encriptación más adelante." + "Sala pública (cualquiera)" + "Nombre de la sala" + "Tema (opcional)" "Se ha producido un error al intentar iniciar un chat" "Crear una sala" diff --git a/features/createroom/impl/src/main/res/values-in/translations.xml b/features/createroom/impl/src/main/res/values-in/translations.xml index aff7eab8b7..775eef7fda 100644 --- a/features/createroom/impl/src/main/res/values-in/translations.xml +++ b/features/createroom/impl/src/main/res/values-in/translations.xml @@ -1,7 +1,7 @@ "Ruangan baru" - "Undang teman ke Element" + "Undang orang-orang ke Element" "Undang seseorang" "Terjadi kesalahan saat membuat ruangan" "Pesan di ruangan ini dienkripsi. Enkripsi tidak dapat dinonaktifkan setelahnya." diff --git a/features/createroom/impl/src/main/res/values-it/translations.xml b/features/createroom/impl/src/main/res/values-it/translations.xml index ceddb71154..836cff3452 100644 --- a/features/createroom/impl/src/main/res/values-it/translations.xml +++ b/features/createroom/impl/src/main/res/values-it/translations.xml @@ -1,8 +1,15 @@ "Nuova stanza" - "Invita persone" - "Aggiungi persone" + "Invita persone su Element" + "Invita persone" + "Si è verificato un errore durante la creazione della stanza" + "I messaggi in questa stanza sono cifrati. La crittografia non può essere disattivata in seguito." + "Stanza privata (solo su invito)" + "I messaggi non sono cifrati e chiunque può leggerli. Puoi attivare la crittografia in un secondo momento." + "Stanza pubblica (chiunque)" + "Nome stanza" + "Argomento (facoltativo)" "Si è verificato un errore durante il tentativo di avviare una chat" "Crea una stanza" diff --git a/features/createroom/impl/src/main/res/values-ru/translations.xml b/features/createroom/impl/src/main/res/values-ru/translations.xml index 7837f9e7de..1557931e85 100644 --- a/features/createroom/impl/src/main/res/values-ru/translations.xml +++ b/features/createroom/impl/src/main/res/values-ru/translations.xml @@ -4,9 +4,9 @@ "Пригласите друзей в Element" "Пригласить людей" "Произошла ошибка при создании комнаты" - "Сообщения в этой комнате зашифрованы. Отключить шифрование впоследствии невозможно." + "Сообщения в этой комнате зашифрованы. Отключить шифрование позже будет невозможно." "Приватная комната (только по приглашению)" - "Сообщения не зашифрованы, и каждый может их прочитать. Вы можете включить шифрование позже." + "Сообщения не зашифрованы, каждый может их прочитать. Вы можете включить шифрование позже." "Публичная комната (любой)" "Название комнаты" "Тема (необязательно)" diff --git a/features/createroom/impl/src/main/res/values-sk/translations.xml b/features/createroom/impl/src/main/res/values-sk/translations.xml index 831ac67369..15fa779609 100644 --- a/features/createroom/impl/src/main/res/values-sk/translations.xml +++ b/features/createroom/impl/src/main/res/values-sk/translations.xml @@ -1,7 +1,7 @@ "Nová miestnosť" - "Pozvať priateľov na Element" + "Pozvať ľudí na Element" "Pozvať ľudí" "Pri vytváraní miestnosti došlo k chybe" "Správy v tejto miestnosti sú šifrované. Šifrovanie už potom nie je možné vypnúť." diff --git a/features/createroom/impl/src/main/res/values/localazy.xml b/features/createroom/impl/src/main/res/values/localazy.xml index 68f318d385..eadcf49f34 100644 --- a/features/createroom/impl/src/main/res/values/localazy.xml +++ b/features/createroom/impl/src/main/res/values/localazy.xml @@ -1,7 +1,7 @@ "New room" - "Invite friends to Element" + "Invite people to Element" "Invite people" "An error occurred when creating the room" "Messages in this room are encrypted. Encryption can’t be disabled afterwards." diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt index f2ed30c58d..1285cf37b6 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt @@ -19,7 +19,7 @@ package io.element.android.features.createroom.impl import androidx.compose.runtime.mutableStateOf import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.CreatedRoom -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.test.A_ROOM_ID @@ -32,16 +32,15 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class DefaultStartDMActionTests { - @Test fun `when dm is found, assert state is updated with given room id`() = runTest { val matrixClient = FakeMatrixClient().apply { givenFindDmResult(A_ROOM_ID) } val action = createStartDMAction(matrixClient) - val state = mutableStateOf>(Async.Uninitialized) + val state = mutableStateOf>(AsyncAction.Uninitialized) action.execute(A_USER_ID, state) - assertThat(state.value).isEqualTo(Async.Success(A_ROOM_ID)) + assertThat(state.value).isEqualTo(AsyncAction.Success(A_ROOM_ID)) } @Test @@ -52,9 +51,9 @@ class DefaultStartDMActionTests { } val analyticsService = FakeAnalyticsService() val action = createStartDMAction(matrixClient, analyticsService) - val state = mutableStateOf>(Async.Uninitialized) + val state = mutableStateOf>(AsyncAction.Uninitialized) action.execute(A_USER_ID, state) - assertThat(state.value).isEqualTo(Async.Success(A_ROOM_ID)) + assertThat(state.value).isEqualTo(AsyncAction.Success(A_ROOM_ID)) assertThat(analyticsService.capturedEvents).containsExactly(CreatedRoom(isDM = true)) } @@ -65,9 +64,9 @@ class DefaultStartDMActionTests { givenCreateDmResult(Result.failure(A_THROWABLE)) } val action = createStartDMAction(matrixClient) - val state = mutableStateOf>(Async.Uninitialized) + val state = mutableStateOf>(AsyncAction.Uninitialized) action.execute(A_USER_ID, state) - assertThat(state.value).isEqualTo(Async.Failure(A_THROWABLE)) + assertThat(state.value).isEqualTo(AsyncAction.Failure(A_THROWABLE)) } private fun createStartDMAction( diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTests.kt index 968b2a1b57..0fa04f685f 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTests.kt @@ -31,7 +31,6 @@ import org.junit.Rule import org.junit.Test class AddPeoplePresenterTests { - @get:Rule val warmUpRule = WarmUpRule() @@ -57,4 +56,3 @@ class AddPeoplePresenterTests { } } } - diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt index 96935ed598..6baf998fcf 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt @@ -25,7 +25,7 @@ import im.vector.app.features.analytics.plan.CreatedRoom import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.createroom.impl.CreateRoomDataStore import io.element.android.features.createroom.impl.userlist.UserListDataStore -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.A_MESSAGE @@ -62,7 +62,6 @@ private const val AN_URI_FROM_GALLERY = "content://uri_from_gallery" @RunWith(RobolectricTestRunner::class) class ConfigureRoomPresenterTests { - @get:Rule val warmUpRule = WarmUpRule() @@ -234,9 +233,9 @@ class ConfigureRoomPresenterTests { fakeMatrixClient.givenCreateRoomResult(createRoomResult) initialState.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config)) - assertThat(awaitItem().createRoomAction).isInstanceOf(Async.Loading::class.java) + assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) val stateAfterCreateRoom = awaitItem() - assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(Async.Success::class.java) + assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Success::class.java) assertThat(stateAfterCreateRoom.createRoomAction.dataOrNull()).isEqualTo(createRoomResult.getOrNull()) } } @@ -272,16 +271,16 @@ class ConfigureRoomPresenterTests { val initialState = awaitItem() initialState.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config)) - assertThat(awaitItem().createRoomAction).isInstanceOf(Async.Loading::class.java) + assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) val stateAfterCreateRoom = awaitItem() - assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(Async.Failure::class.java) + assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Failure::class.java) assertThat(fakeAnalyticsService.capturedEvents.filterIsInstance()).isEmpty() fakeMatrixClient.givenUploadMediaResult(Result.success(AN_AVATAR_URL)) stateAfterCreateRoom.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config)) - assertThat(awaitItem().createRoomAction).isInstanceOf(Async.Uninitialized::class.java) - assertThat(awaitItem().createRoomAction).isInstanceOf(Async.Loading::class.java) - assertThat(awaitItem().createRoomAction).isInstanceOf(Async.Success::class.java) + assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) + assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Success::class.java) } } @@ -297,23 +296,22 @@ class ConfigureRoomPresenterTests { // Create initialState.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config)) - assertThat(awaitItem().createRoomAction).isInstanceOf(Async.Loading::class.java) + assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) val stateAfterCreateRoom = awaitItem() - assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(Async.Failure::class.java) - assertThat((stateAfterCreateRoom.createRoomAction as? Async.Failure)?.error).isEqualTo(createRoomResult.exceptionOrNull()) + assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Failure::class.java) + assertThat((stateAfterCreateRoom.createRoomAction as? AsyncAction.Failure)?.error).isEqualTo(createRoomResult.exceptionOrNull()) // Retry stateAfterCreateRoom.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config)) - assertThat(awaitItem().createRoomAction).isInstanceOf(Async.Uninitialized::class.java) - assertThat(awaitItem().createRoomAction).isInstanceOf(Async.Loading::class.java) + assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) val stateAfterRetry = awaitItem() - assertThat(stateAfterRetry.createRoomAction).isInstanceOf(Async.Failure::class.java) - assertThat((stateAfterRetry.createRoomAction as? Async.Failure)?.error).isEqualTo(createRoomResult.exceptionOrNull()) + assertThat(stateAfterRetry.createRoomAction).isInstanceOf(AsyncAction.Failure::class.java) + assertThat((stateAfterRetry.createRoomAction as? AsyncAction.Failure)?.error).isEqualTo(createRoomResult.exceptionOrNull()) // Cancel stateAfterRetry.eventSink(ConfigureRoomEvents.CancelCreateRoom) - assertThat(awaitItem().createRoomAction).isInstanceOf(Async.Uninitialized::class.java) + assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java) } } } - diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt index a0fb71dd31..2c48300d3c 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt @@ -25,8 +25,7 @@ import io.element.android.features.createroom.impl.userlist.FakeUserListPresente import io.element.android.features.createroom.impl.userlist.FakeUserListPresenterFactory import io.element.android.features.createroom.impl.userlist.UserListDataStore import io.element.android.features.createroom.test.FakeStartDMAction -import io.element.android.libraries.architecture.Async -import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.A_ROOM_ID @@ -39,7 +38,6 @@ import org.junit.Rule import org.junit.Test class CreateRoomRootPresenterTests { - @get:Rule val warmUpRule = WarmUpRule() @@ -52,20 +50,20 @@ class CreateRoomRootPresenterTests { }.test { val initialState = awaitItem() - assertThat(initialState.startDmAction).isInstanceOf(Async.Uninitialized::class.java) + assertThat(initialState.startDmAction).isInstanceOf(AsyncAction.Uninitialized::class.java) assertThat(initialState.applicationName).isEqualTo(aBuildMeta().applicationName) assertThat(initialState.userListState.selectedUsers).isEmpty() assertThat(initialState.userListState.isSearchActive).isFalse() assertThat(initialState.userListState.isMultiSelectionEnabled).isFalse() val matrixUser = MatrixUser(UserId("@name:domain")) - val startDMSuccessResult = Async.Success(A_ROOM_ID) - val startDMFailureResult = Async.Failure(A_THROWABLE) + val startDMSuccessResult = AsyncAction.Success(A_ROOM_ID) + val startDMFailureResult = AsyncAction.Failure(A_THROWABLE) // Failure startDMAction.givenExecuteResult(startDMFailureResult) initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) - assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java) + assertThat(awaitItem().startDmAction).isInstanceOf(AsyncAction.Loading::class.java) awaitItem().also { state -> assertThat(state.startDmAction).isEqualTo(startDMFailureResult) state.eventSink(CreateRoomRootEvents.CancelStartDM) @@ -74,14 +72,13 @@ class CreateRoomRootPresenterTests { // Success startDMAction.givenExecuteResult(startDMSuccessResult) awaitItem().also { state -> - assertThat(state.startDmAction).isEqualTo(Async.Uninitialized) + assertThat(state.startDmAction).isEqualTo(AsyncAction.Uninitialized) state.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) } - assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java) + assertThat(awaitItem().startDmAction).isInstanceOf(AsyncAction.Loading::class.java) awaitItem().also { state -> assertThat(state.startDmAction).isEqualTo(startDMSuccessResult) } - } } diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenterTests.kt index a591a7a8e9..579bd175f5 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenterTests.kt @@ -24,6 +24,7 @@ import io.element.android.libraries.designsystem.theme.components.SearchBarResul import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.libraries.usersearch.api.UserSearchResult +import io.element.android.libraries.usersearch.api.UserSearchResultState import io.element.android.libraries.usersearch.test.FakeUserRepository import io.element.android.tests.testutils.WarmUpRule import kotlinx.collections.immutable.persistentListOf @@ -32,7 +33,6 @@ import org.junit.Rule import org.junit.Test class DefaultUserListPresenterTests { - @get:Rule val warmUpRule = WarmUpRule() @@ -55,7 +55,7 @@ class DefaultUserListPresenterTests { assertThat(initialState.isMultiSelectionEnabled).isFalse() assertThat(initialState.isSearchActive).isFalse() assertThat(initialState.selectedUsers).isEmpty() - assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java) + assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) } } @@ -76,7 +76,7 @@ class DefaultUserListPresenterTests { assertThat(initialState.isMultiSelectionEnabled).isTrue() assertThat(initialState.isSearchActive).isFalse() assertThat(initialState.selectedUsers).isEmpty() - assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java) + assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) } } @@ -131,25 +131,38 @@ class DefaultUserListPresenterTests { val initialState = awaitItem() initialState.eventSink(UserListEvents.UpdateSearchQuery("alice")) - assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java) + assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) assertThat(userRepository.providedQuery).isEqualTo("alice") skipItems(2) // When the user repository emits a result, it's copied to the state - userRepository.emitResult(listOf(UserSearchResult(aMatrixUser()))) - assertThat(awaitItem().searchResults).isEqualTo( - SearchBarResultState.Results( - persistentListOf(UserSearchResult(aMatrixUser())) - ) + val result = UserSearchResultState( + results = listOf(UserSearchResult(aMatrixUser())), + isSearching = false, ) - + userRepository.emitState(result) + awaitItem().also { state -> + assertThat(state.searchResults).isEqualTo( + SearchBarResultState.Results( + persistentListOf(UserSearchResult(aMatrixUser())) + ) + ) + assertThat(state.showSearchLoader).isFalse() + } // When the user repository emits another result, it replaces the previous value - userRepository.emitResult(aMatrixUserList().map { UserSearchResult(it) }) - assertThat(awaitItem().searchResults).isEqualTo( - SearchBarResultState.Results( - aMatrixUserList().map { UserSearchResult(it) } - ) + val newResult = UserSearchResultState( + results = aMatrixUserList().map { UserSearchResult(it) }, + isSearching = false, ) + userRepository.emitState(newResult) + awaitItem().also { state -> + assertThat(state.searchResults).isEqualTo( + SearchBarResultState.Results( + aMatrixUserList().map { UserSearchResult(it) } + ) + ) + assertThat(state.showSearchLoader).isFalse() + } } } @@ -170,13 +183,13 @@ class DefaultUserListPresenterTests { val initialState = awaitItem() initialState.eventSink(UserListEvents.UpdateSearchQuery("alice")) - assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java) + assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) assertThat(userRepository.providedQuery).isEqualTo("alice") skipItems(2) // When the results list is empty, the state is set to NoResults - userRepository.emitResult(emptyList()) - assertThat(awaitItem().searchResults).isInstanceOf(SearchBarResultState.NoResults::class.java) + userRepository.emitState(UserSearchResultState(results = emptyList(), isSearching = false)) + assertThat(awaitItem().searchResults).isInstanceOf(SearchBarResultState.NoResultsFound::class.java) } } diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/FakeUserListPresenter.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/FakeUserListPresenter.kt index 45eb712dc1..b9e440f25e 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/FakeUserListPresenter.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/FakeUserListPresenter.kt @@ -19,7 +19,6 @@ package io.element.android.features.createroom.impl.userlist import androidx.compose.runtime.Composable class FakeUserListPresenter : UserListPresenter { - private var state = aUserListState() fun givenState(state: UserListState) { diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/FakeUserListPresenterFactory.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/FakeUserListPresenterFactory.kt index 07697ce458..aae4c297f8 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/FakeUserListPresenterFactory.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/FakeUserListPresenterFactory.kt @@ -21,7 +21,6 @@ import io.element.android.libraries.usersearch.api.UserRepository class FakeUserListPresenterFactory( private val fakeUserListPresenter: FakeUserListPresenter = FakeUserListPresenter() ) : UserListPresenter.Factory { - override fun create( args: UserListPresenterArgs, userRepository: UserRepository, diff --git a/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt b/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt index c1888dc53f..2fec589da8 100644 --- a/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt +++ b/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt @@ -18,22 +18,21 @@ package io.element.android.features.createroom.test import androidx.compose.runtime.MutableState import io.element.android.features.createroom.api.StartDMAction -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.test.A_ROOM_ID import kotlinx.coroutines.delay class FakeStartDMAction : StartDMAction { + private var executeResult: AsyncAction = AsyncAction.Success(A_ROOM_ID) - private var executeResult: Async = Async.Success(A_ROOM_ID) - - fun givenExecuteResult(result: Async) { + fun givenExecuteResult(result: AsyncAction) { executeResult = result } - override suspend fun execute(userId: UserId, actionState: MutableState>) { - actionState.value = Async.Loading() + override suspend fun execute(userId: UserId, actionState: MutableState>) { + actionState.value = AsyncAction.Loading delay(1) actionState.value = executeResult } diff --git a/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/FtueEntryPoint.kt b/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/FtueEntryPoint.kt index 649a327f6e..186f2fe026 100644 --- a/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/FtueEntryPoint.kt +++ b/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/FtueEntryPoint.kt @@ -22,7 +22,6 @@ import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint interface FtueEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder interface NodeBuilder { diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/DefaultFtueEntryPoint.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/DefaultFtueEntryPoint.kt index 9c2f74f072..c89b1840af 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/DefaultFtueEntryPoint.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/DefaultFtueEntryPoint.kt @@ -27,12 +27,10 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultFtueEntryPoint @Inject constructor() : FtueEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): FtueEntryPoint.NodeBuilder { val plugins = ArrayList() return object : FtueEntryPoint.NodeBuilder { - override fun callback(callback: FtueEntryPoint.Callback): FtueEntryPoint.NodeBuilder { plugins += callback return this diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index 5a47e196df..5840671b6f 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -70,7 +70,6 @@ class FtueFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins, ) { - sealed interface NavTarget : Parcelable { @Parcelize data object Placeholder : NavTarget diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenNode.kt index a4c5d16bc4..7c4257c422 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenNode.kt @@ -32,7 +32,6 @@ class MigrationScreenNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: MigrationScreenPresenter, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun onMigrationFinished() } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenView.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenView.kt index bf93aa2272..d2674e3f98 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenView.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenView.kt @@ -50,5 +50,6 @@ fun MigrationScreenView( internal fun MigrationViewPreview() = ElementPreview { MigrationScreenView( migrationState = MigrationScreenState(isMigrating = true), - onMigrationFinished = {}) + onMigrationFinished = {} + ) } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt index c39a535e7d..8ece45c08c 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt @@ -29,7 +29,6 @@ import javax.inject.Inject class SharedPrefsMigrationScreenStore @Inject constructor( @DefaultPreferences private val sharedPreferences: SharedPreferences, ) : MigrationScreenStore { - override fun isMigrationScreenNeeded(sessionId: SessionId): Boolean { return sharedPreferences.getBoolean(sessionId.toKey(), false).not() } @@ -58,4 +57,3 @@ class SharedPrefsMigrationScreenStore @Inject constructor( private const val IS_MIGRATION_SCREEN_SHOWN_PREFIX = "is_migration_screen_shown_" } } - diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInNode.kt index 5d514af8a9..6135d350dc 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInNode.kt @@ -34,7 +34,6 @@ class NotificationsOptInNode @AssistedInject constructor( @Assisted plugins: List, presenterFactory: NotificationsOptInPresenter.Factory, ) : Node(buildContext, plugins = plugins) { - interface Callback : NodeInputs { fun onNotificationsOptInFinished() } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenter.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenter.kt index 5d3ac6c88f..52a8daab81 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenter.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenter.kt @@ -40,7 +40,6 @@ class NotificationsOptInPresenter @AssistedInject constructor( private val permissionStateProvider: PermissionStateProvider, private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider, ) : Presenter { - @AssistedFactory interface Factory { fun create(callback: NotificationsOptInNode.Callback): NotificationsOptInPresenter @@ -77,8 +76,8 @@ class NotificationsOptInPresenter @AssistedInject constructor( } LaunchedEffect(notificationsPermissionsState) { - if (notificationsPermissionsState.permissionGranted - || notificationsPermissionsState.permissionAlreadyDenied) { + if (notificationsPermissionsState.permissionGranted || + notificationsPermissionsState.permissionAlreadyDenied) { callback.onNotificationsOptInFinished() } } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt index b1abee90bc..212919a691 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt @@ -40,7 +40,7 @@ import javax.inject.Inject @ContributesBinding(SessionScope::class) class DefaultFtueState @Inject constructor( private val sdkVersionProvider: BuildVersionSdkIntProvider, - private val coroutineScope: CoroutineScope, + coroutineScope: CoroutineScope, private val analyticsService: AnalyticsService, private val welcomeScreenState: WelcomeScreenState, private val migrationScreenStore: MigrationScreenStore, @@ -48,7 +48,6 @@ class DefaultFtueState @Inject constructor( private val lockScreenService: LockScreenService, private val matrixClient: MatrixClient, ) : FtueState { - override val shouldDisplayFlow = MutableStateFlow(isAnyStepIncomplete()) override suspend fun reset() { @@ -68,21 +67,31 @@ class DefaultFtueState @Inject constructor( fun getNextStep(currentStep: FtueStep? = null): FtueStep? = when (currentStep) { - null -> if (shouldDisplayMigrationScreen()) FtueStep.MigrationScreen else getNextStep( + null -> if (shouldDisplayMigrationScreen()) { FtueStep.MigrationScreen - ) - FtueStep.MigrationScreen -> if (shouldDisplayWelcomeScreen()) FtueStep.WelcomeScreen else getNextStep( + } else { + getNextStep(FtueStep.MigrationScreen) + } + FtueStep.MigrationScreen -> if (shouldDisplayWelcomeScreen()) { FtueStep.WelcomeScreen - ) - FtueStep.WelcomeScreen -> if (shouldAskNotificationPermissions()) FtueStep.NotificationsOptIn else getNextStep( + } else { + getNextStep(FtueStep.WelcomeScreen) + } + FtueStep.WelcomeScreen -> if (shouldAskNotificationPermissions()) { FtueStep.NotificationsOptIn - ) - FtueStep.NotificationsOptIn -> if (shouldDisplayLockscreenSetup()) FtueStep.LockscreenSetup else getNextStep( + } else { + getNextStep(FtueStep.NotificationsOptIn) + } + FtueStep.NotificationsOptIn -> if (shouldDisplayLockscreenSetup()) { FtueStep.LockscreenSetup - ) - FtueStep.LockscreenSetup -> if (needsAnalyticsOptIn()) FtueStep.AnalyticsOptIn else getNextStep( + } else { + getNextStep(FtueStep.LockscreenSetup) + } + FtueStep.LockscreenSetup -> if (needsAnalyticsOptIn()) { FtueStep.AnalyticsOptIn - ) + } else { + getNextStep(FtueStep.AnalyticsOptIn) + } FtueStep.AnalyticsOptIn -> null } @@ -115,7 +124,9 @@ class DefaultFtueState @Inject constructor( val isPermissionDenied = runBlocking { permissionStateProvider.isPermissionDenied(permission).first() } val isPermissionGranted = permissionStateProvider.isPermissionGranted(permission) !isPermissionGranted && !isPermissionDenied - } else false + } else { + false + } } private fun shouldDisplayLockscreenSetup(): Boolean { diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/welcome/WelcomeNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/welcome/WelcomeNode.kt index f4e0d9f640..2f2d838269 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/welcome/WelcomeNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/welcome/WelcomeNode.kt @@ -33,7 +33,6 @@ class WelcomeNode @AssistedInject constructor( @Assisted plugins: List, private val buildMeta: BuildMeta, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun onContinueClicked() } @@ -50,5 +49,4 @@ class WelcomeNode @AssistedInject constructor( modifier = modifier ) } - } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/welcome/state/AndroidWelcomeScreenState.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/welcome/state/AndroidWelcomeScreenState.kt index 6dbef47285..00629d7d03 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/welcome/state/AndroidWelcomeScreenState.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/welcome/state/AndroidWelcomeScreenState.kt @@ -29,7 +29,6 @@ import javax.inject.Inject class AndroidWelcomeScreenState @Inject constructor( @DefaultPreferences private val sharedPreferences: SharedPreferences, ) : WelcomeScreenState { - companion object { private const val IS_WELCOME_SCREEN_SHOWN = "is_welcome_screen_shown" } diff --git a/features/ftue/impl/src/main/res/values-de/translations.xml b/features/ftue/impl/src/main/res/values-de/translations.xml index d9a411b04f..d2a71e98e5 100644 --- a/features/ftue/impl/src/main/res/values-de/translations.xml +++ b/features/ftue/impl/src/main/res/values-de/translations.xml @@ -1,7 +1,7 @@ "Dies ist ein einmaliger Vorgang, danke fürs Warten." - "Richte dein Konto ein." + "Dein Konto wird eingerichtet." "Du kannst deine Einstellungen später ändern." "Erlaube Benachrichtigungen und verpasse keine Nachricht" "Anrufe, Umfragen, Suchfunktionen und mehr werden im Laufe des Jahres hinzugefügt." diff --git a/features/ftue/impl/src/main/res/values-es/translations.xml b/features/ftue/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..b5bac7f6a1 --- /dev/null +++ b/features/ftue/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,13 @@ + + + "Este proceso solo se hace una vez, gracias por esperar." + "Configura tu cuenta" + "Puedes cambiar la configuración más tarde." + "Activa las notificaciones y nunca te pierdas un mensaje" + "Las llamadas, las encuestas, la búsqueda y más se agregarán más adelante este año." + "El historial de mensajes de las salas cifradas aún no está disponible." + "Nos encantaría saber de ti, haznos saber lo que piensas a través de la página de configuración." + "¡Vamos!" + "Esto es lo que necesitas saber:" + "¡Bienvenido a %1$s!" + diff --git a/features/ftue/impl/src/main/res/values-it/translations.xml b/features/ftue/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..69013ebdb8 --- /dev/null +++ b/features/ftue/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,13 @@ + + + "Si tratta di una procedura che si effettua una sola volta, grazie per l\'attesa." + "Configurazione del tuo account." + "Potrai modificare le tue impostazioni in seguito." + "Consenti le notifiche e non perdere mai un messaggio" + "Chiamate, sondaggi, ricerche e altro ancora saranno aggiunti nel corso dell\'anno." + "La cronologia dei messaggi per le stanze crittografate non è ancora disponibile." + "Ci piacerebbe sentire il tuo parere, facci sapere cosa ne pensi tramite la pagina delle impostazioni." + "Andiamo!" + "Ecco cosa c\'è da sapere:" + "Benvenuti in %1$s!" + diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt index 37e61ebf55..44df35ca05 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt @@ -39,7 +39,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class DefaultFtueStateTests { - @Test fun `given any check being false, should display flow is true`() = runTest { val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) @@ -130,7 +129,8 @@ class DefaultFtueStateTests { FtueStep.NotificationsOptIn, FtueStep.LockscreenSetup, FtueStep.AnalyticsOptIn, - null, // Final state + // Final state + null, ) // Cleanup @@ -204,7 +204,8 @@ class DefaultFtueStateTests { permissionStateProvider: FakePermissionStateProvider = FakePermissionStateProvider(permissionGranted = false), matrixClient: MatrixClient = FakeMatrixClient(), lockScreenService: LockScreenService = FakeLockScreenService(), - sdkIntVersion: Int = Build.VERSION_CODES.TIRAMISU, // First version where notification permission is required + // First version where notification permission is required + sdkIntVersion: Int = Build.VERSION_CODES.TIRAMISU, ) = DefaultFtueState( sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkIntVersion), coroutineScope = coroutineScope, diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenterTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenterTest.kt index ac56718e06..44dcba6e00 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenterTest.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenterTest.kt @@ -31,7 +31,6 @@ import org.junit.Rule import org.junit.Test class MigrationScreenPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenterTests.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenterTests.kt index e5c4d37423..4d8ae554d7 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenterTests.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenterTests.kt @@ -39,7 +39,6 @@ import org.junit.Rule import org.junit.Test class NotificationsOptInPresenterTests { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/welcome/state/FakeWelcomeState.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/welcome/state/FakeWelcomeState.kt index e38d49db1c..6b4d4b2287 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/welcome/state/FakeWelcomeState.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/welcome/state/FakeWelcomeState.kt @@ -17,7 +17,6 @@ package io.element.android.features.ftue.impl.welcome.state class FakeWelcomeState : WelcomeScreenState { - private var isWelcomeScreenNeeded = true override fun isWelcomeScreenNeeded(): Boolean { diff --git a/features/invitelist/api/src/main/kotlin/io/element/android/features/invitelist/api/InviteListEntryPoint.kt b/features/invitelist/api/src/main/kotlin/io/element/android/features/invitelist/api/InviteListEntryPoint.kt index 790aac39be..95d2c94f50 100644 --- a/features/invitelist/api/src/main/kotlin/io/element/android/features/invitelist/api/InviteListEntryPoint.kt +++ b/features/invitelist/api/src/main/kotlin/io/element/android/features/invitelist/api/InviteListEntryPoint.kt @@ -23,7 +23,6 @@ import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.matrix.api.core.RoomId interface InviteListEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder interface NodeBuilder { @@ -37,4 +36,3 @@ interface InviteListEntryPoint : FeatureEntryPoint { fun onInviteAccepted(roomId: RoomId) } } - diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/DefaultInviteListEntryPoint.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/DefaultInviteListEntryPoint.kt index ef98bc0019..5c2fc780d7 100644 --- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/DefaultInviteListEntryPoint.kt +++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/DefaultInviteListEntryPoint.kt @@ -27,12 +27,10 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultInviteListEntryPoint @Inject constructor() : InviteListEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): InviteListEntryPoint.NodeBuilder { val plugins = ArrayList() return object : InviteListEntryPoint.NodeBuilder { - override fun callback(callback: InviteListEntryPoint.Callback): InviteListEntryPoint.NodeBuilder { plugins += callback return this diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/DefaultSeenInvitesStore.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/DefaultSeenInvitesStore.kt index 848a4e2ba7..1564b2fa83 100644 --- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/DefaultSeenInvitesStore.kt +++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/DefaultSeenInvitesStore.kt @@ -34,12 +34,10 @@ import javax.inject.Inject private val Context.dataStore: DataStore by preferencesDataStore(name = "elementx_seeninvites") private val seenInvitesKey = stringSetPreferencesKey("seenInvites") - @ContributesBinding(SessionScope::class) class DefaultSeenInvitesStore @Inject constructor( @ApplicationContext context: Context ) : SeenInvitesStore { - private val store = context.dataStore override fun seenRoomIds(): Flow> = @@ -55,5 +53,4 @@ class DefaultSeenInvitesStore @Inject constructor( prefs[seenInvitesKey] = roomIds.map { it.value }.toSet() } } - } diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListEvents.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListEvents.kt index 38055b7090..ae4b74bb2c 100644 --- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListEvents.kt +++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListEvents.kt @@ -22,9 +22,9 @@ sealed interface InviteListEvents { data class AcceptInvite(val invite: InviteListInviteSummary) : InviteListEvents data class DeclineInvite(val invite: InviteListInviteSummary) : InviteListEvents - data object ConfirmDeclineInvite: InviteListEvents - data object CancelDeclineInvite: InviteListEvents + data object ConfirmDeclineInvite : InviteListEvents + data object CancelDeclineInvite : InviteListEvents - data object DismissAcceptError: InviteListEvents - data object DismissDeclineError: InviteListEvents + data object DismissAcceptError : InviteListEvents + data object DismissDeclineError : InviteListEvents } diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListNode.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListNode.kt index 2f67f83994..31d742e4e7 100644 --- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListNode.kt +++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListNode.kt @@ -35,7 +35,6 @@ class InviteListNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: InviteListPresenter, ) : Node(buildContext, plugins = plugins) { - private fun onBackClicked() { plugins().forEach { it.onBackClicked() } } diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt index 8bc752d629..f2dd87ff05 100644 --- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt +++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt @@ -29,7 +29,7 @@ import im.vector.app.features.analytics.plan.JoinedRoom import io.element.android.features.invitelist.api.SeenInvitesStore import io.element.android.features.invitelist.impl.model.InviteListInviteSummary import io.element.android.features.invitelist.impl.model.InviteSender -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.designsystem.components.avatar.AvatarData @@ -52,7 +52,6 @@ class InviteListPresenter @Inject constructor( private val analyticsService: AnalyticsService, private val notificationDrawerManager: NotificationDrawerManager, ) : Presenter { - @Composable override fun present(): InviteListState { val invites by client @@ -77,14 +76,14 @@ class InviteListPresenter @Inject constructor( } val localCoroutineScope = rememberCoroutineScope() - val acceptedAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } - val declinedAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + val acceptedAction: MutableState> = remember { mutableStateOf(AsyncData.Uninitialized) } + val declinedAction: MutableState> = remember { mutableStateOf(AsyncData.Uninitialized) } val decliningInvite: MutableState = remember { mutableStateOf(null) } fun handleEvent(event: InviteListEvents) { when (event) { is InviteListEvents.AcceptInvite -> { - acceptedAction.value = Async.Uninitialized + acceptedAction.value = AsyncData.Uninitialized localCoroutineScope.acceptInvite(event.invite.roomId, acceptedAction) } @@ -93,7 +92,7 @@ class InviteListPresenter @Inject constructor( } is InviteListEvents.ConfirmDeclineInvite -> { - declinedAction.value = Async.Uninitialized + declinedAction.value = AsyncData.Uninitialized decliningInvite.value?.let { localCoroutineScope.declineInvite(it.roomId, declinedAction) } @@ -105,11 +104,11 @@ class InviteListPresenter @Inject constructor( } is InviteListEvents.DismissAcceptError -> { - acceptedAction.value = Async.Uninitialized + acceptedAction.value = AsyncData.Uninitialized } is InviteListEvents.DismissDeclineError -> { - declinedAction.value = Async.Uninitialized + declinedAction.value = AsyncData.Uninitialized } } } @@ -137,7 +136,7 @@ class InviteListPresenter @Inject constructor( ) } - private fun CoroutineScope.acceptInvite(roomId: RoomId, acceptedAction: MutableState>) = launch { + private fun CoroutineScope.acceptInvite(roomId: RoomId, acceptedAction: MutableState>) = launch { suspend { client.getRoom(roomId)?.use { it.join().getOrThrow() @@ -148,7 +147,7 @@ class InviteListPresenter @Inject constructor( }.runCatchingUpdatingState(acceptedAction) } - private fun CoroutineScope.declineInvite(roomId: RoomId, declinedAction: MutableState>) = launch { + private fun CoroutineScope.declineInvite(roomId: RoomId, declinedAction: MutableState>) = launch { suspend { client.getRoom(roomId)?.use { it.leave().getOrThrow() @@ -159,25 +158,27 @@ class InviteListPresenter @Inject constructor( private fun RoomSummary.Filled.toInviteSummary(seen: Boolean) = details.run { val i = inviter - val avatarData = if (isDirect && i != null) + val avatarData = if (isDirect && i != null) { AvatarData( id = i.userId.value, name = i.displayName, url = i.avatarUrl, size = AvatarSize.RoomInviteItem, ) - else + } else { AvatarData( id = roomId.value, name = name, url = avatarURLString, size = AvatarSize.RoomInviteItem, ) + } - val alias = if (isDirect) + val alias = if (isDirect) { inviter?.userId?.value - else + } else { canonicalAlias + } InviteListInviteSummary( roomId = roomId, @@ -186,18 +187,20 @@ class InviteListPresenter @Inject constructor( roomAvatarData = avatarData, isDirect = isDirect, isNew = !seen, - sender = if (isDirect) null else inviter?.run { - InviteSender( - userId = userId, - displayName = displayName ?: "", - avatarData = AvatarData( - id = userId.value, - name = displayName, - url = avatarUrl, - size = AvatarSize.InviteSender, - ), - ) - }, + sender = inviter + ?.takeIf { !isDirect } + ?.run { + InviteSender( + userId = userId, + displayName = displayName ?: "", + avatarData = AvatarData( + id = userId.value, + name = displayName, + url = avatarUrl, + size = AvatarSize.InviteSender, + ), + ) + }, ) } } diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListState.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListState.kt index 43644cd6aa..b576861475 100644 --- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListState.kt +++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListState.kt @@ -18,7 +18,7 @@ package io.element.android.features.invitelist.impl import androidx.compose.runtime.Immutable import io.element.android.features.invitelist.impl.model.InviteListInviteSummary -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.collections.immutable.ImmutableList @@ -26,8 +26,8 @@ import kotlinx.collections.immutable.ImmutableList data class InviteListState( val inviteList: ImmutableList, val declineConfirmationDialog: InviteDeclineConfirmationDialog, - val acceptedAction: Async, - val declinedAction: Async, + val acceptedAction: AsyncData, + val declinedAction: AsyncData, val eventSink: (InviteListEvents) -> Unit ) diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListStateProvider.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListStateProvider.kt index f187398689..e61bd9ff93 100644 --- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListStateProvider.kt +++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListStateProvider.kt @@ -19,7 +19,7 @@ package io.element.android.features.invitelist.impl import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.invitelist.impl.model.InviteListInviteSummary import io.element.android.features.invitelist.impl.model.InviteSender -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import kotlinx.collections.immutable.ImmutableList @@ -32,16 +32,16 @@ open class InviteListStateProvider : PreviewParameterProvider { aInviteListState().copy(inviteList = persistentListOf()), aInviteListState().copy(declineConfirmationDialog = InviteDeclineConfirmationDialog.Visible(true, "Alice")), aInviteListState().copy(declineConfirmationDialog = InviteDeclineConfirmationDialog.Visible(false, "Some Room")), - aInviteListState().copy(acceptedAction = Async.Failure(Throwable("Whoops"))), - aInviteListState().copy(declinedAction = Async.Failure(Throwable("Whoops"))), + aInviteListState().copy(acceptedAction = AsyncData.Failure(Throwable("Whoops"))), + aInviteListState().copy(declinedAction = AsyncData.Failure(Throwable("Whoops"))), ) } internal fun aInviteListState() = InviteListState( inviteList = aInviteListInviteSummaryList(), declineConfirmationDialog = InviteDeclineConfirmationDialog.Hidden, - acceptedAction = Async.Uninitialized, - declinedAction = Async.Uninitialized, + acceptedAction = AsyncData.Uninitialized, + declinedAction = AsyncData.Uninitialized, eventSink = {}, ) diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListView.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListView.kt index e661b121c9..28ecf4938d 100644 --- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListView.kt +++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListView.kt @@ -17,7 +17,6 @@ package io.element.android.features.invitelist.impl import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxWidth @@ -34,20 +33,20 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.features.invitelist.impl.components.InviteSummaryRow -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.aliasScreenTitle import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -57,7 +56,7 @@ fun InviteListView( onInviteAccepted: (RoomId) -> Unit, modifier: Modifier = Modifier, ) { - if (state.acceptedAction is Async.Success) { + if (state.acceptedAction is AsyncData.Success) { LaunchedEffect(state.acceptedAction) { onInviteAccepted(state.acceptedAction.data) } @@ -70,15 +69,17 @@ fun InviteListView( ) if (state.declineConfirmationDialog is InviteDeclineConfirmationDialog.Visible) { - val contentResource = if (state.declineConfirmationDialog.isDirect) + val contentResource = if (state.declineConfirmationDialog.isDirect) { R.string.screen_invites_decline_direct_chat_message - else + } else { R.string.screen_invites_decline_chat_message + } - val titleResource = if (state.declineConfirmationDialog.isDirect) + val titleResource = if (state.declineConfirmationDialog.isDirect) { R.string.screen_invites_decline_direct_chat_title - else + } else { R.string.screen_invites_decline_chat_title + } ConfirmationDialog( content = stringResource(contentResource, state.declineConfirmationDialog.name), @@ -90,7 +91,7 @@ fun InviteListView( ) } - if (state.acceptedAction is Async.Failure) { + if (state.acceptedAction is AsyncData.Failure) { ErrorDialog( content = stringResource(CommonStrings.error_unknown), title = stringResource(CommonStrings.common_error), @@ -99,7 +100,7 @@ fun InviteListView( ) } - if (state.declinedAction is Async.Failure) { + if (state.declinedAction is AsyncData.Failure) { ErrorDialog( content = stringResource(CommonStrings.error_unknown), title = stringResource(CommonStrings.common_error), @@ -109,7 +110,7 @@ fun InviteListView( } } -@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun InviteListContent( state: InviteListState, diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/components/InviteSummaryRow.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/components/InviteSummaryRow.kt index 193b78f594..7c3c1ad1db 100644 --- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/components/InviteSummaryRow.kt +++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/components/InviteSummaryRow.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.features.invitelist.impl.R import io.element.android.features.invitelist.impl.model.InviteListInviteSummary import io.element.android.features.invitelist.impl.model.InviteListInviteSummaryProvider @@ -51,7 +52,6 @@ import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.ButtonSize import io.element.android.libraries.designsystem.theme.components.OutlinedButton import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings private val minHeight = 72.dp diff --git a/features/invitelist/impl/src/main/res/values-de/translations.xml b/features/invitelist/impl/src/main/res/values-de/translations.xml index 97f4aa91f7..bb38811a7a 100644 --- a/features/invitelist/impl/src/main/res/values-de/translations.xml +++ b/features/invitelist/impl/src/main/res/values-de/translations.xml @@ -2,8 +2,8 @@ "Möchtest du die Einladung zum Betreten von %1$s wirklich ablehnen?" "Einladung ablehnen" - "Bist du sicher, dass du diesen privaten Chat mit %1$s ablehnen möchtest?" - "Chat ablehnen" + "Bist du sicher, dass du diese Direktnachricht von %1$s ablehnen möchtest?" + "Einladung ablehnen" "Keine Einladungen" "%1$s (%2$s) hat dich eingeladen" diff --git a/features/invitelist/impl/src/main/res/values-es/translations.xml b/features/invitelist/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..890df29447 --- /dev/null +++ b/features/invitelist/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,9 @@ + + + "¿Estás seguro de que quieres rechazar la invitación a unirte a %1$s?" + "Rechazar la invitación" + "¿Estás seguro de que quieres rechazar este chat privado con%1$s?" + "Rechazar el chat" + "Sin invitaciones" + "%1$s (%2$s) te invitó" + diff --git a/features/invitelist/impl/src/main/res/values-it/translations.xml b/features/invitelist/impl/src/main/res/values-it/translations.xml index 5f31ef01ba..e5d25b10cf 100644 --- a/features/invitelist/impl/src/main/res/values-it/translations.xml +++ b/features/invitelist/impl/src/main/res/values-it/translations.xml @@ -1,4 +1,9 @@ + "Vuoi davvero rifiutare l\'invito ad entrare in %1$s?" + "Rifiuta l\'invito" + "Vuoi davvero rifiutare questa chat privata con %1$s?" + "Rifiuta la chat" + "Nessun invito" "%1$s (%2$s) ti ha invitato" diff --git a/features/invitelist/impl/src/main/res/values-ru/translations.xml b/features/invitelist/impl/src/main/res/values-ru/translations.xml index 0f1c30cb09..b13ca06a9d 100644 --- a/features/invitelist/impl/src/main/res/values-ru/translations.xml +++ b/features/invitelist/impl/src/main/res/values-ru/translations.xml @@ -2,7 +2,7 @@ "Вы уверены, что хотите отклонить приглашение в %1$s?" "Отклонить приглашение" - "Вы уверены, что хотите отказаться от приватного общения с %1$s?" + "Вы уверены, что хотите отказаться от личного общения с %1$s?" "Отклонить чат" "Нет приглашений" "%1$s (%2$s) пригласил вас" diff --git a/features/invitelist/impl/src/main/res/values-zh-rTW/translations.xml b/features/invitelist/impl/src/main/res/values-zh-rTW/translations.xml index eb6d87524c..79557c1e8c 100644 --- a/features/invitelist/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/invitelist/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,4 +1,5 @@ + "沒有邀請" "%1$s(%2$s)邀請您" diff --git a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt index 70908941a6..a3bafdb034 100644 --- a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt +++ b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt @@ -22,7 +22,7 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.invitelist.api.SeenInvitesStore import io.element.android.features.invitelist.test.FakeSeenInvitesStore -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.MatrixClient @@ -245,7 +245,7 @@ class InviteListPresenterTests { val newState = awaitItem() - assertThat(newState.declinedAction).isEqualTo(Async.Failure(ex)) + assertThat(newState.declinedAction).isEqualTo(AsyncData.Failure(ex)) } } @@ -277,7 +277,7 @@ class InviteListPresenterTests { val newState = awaitItem() - assertThat(newState.declinedAction).isEqualTo(Async.Uninitialized) + assertThat(newState.declinedAction).isEqualTo(AsyncData.Uninitialized) } } @@ -300,7 +300,7 @@ class InviteListPresenterTests { val newState = awaitItem() - assertThat(newState.acceptedAction).isEqualTo(Async.Success(A_ROOM_ID)) + assertThat(newState.acceptedAction).isEqualTo(AsyncData.Success(A_ROOM_ID)) assertThat(fakeNotificationDrawerManager.getClearMembershipNotificationForRoomCount(client.sessionId, A_ROOM_ID)).isEqualTo(1) } } @@ -323,7 +323,7 @@ class InviteListPresenterTests { val originalState = awaitItem() originalState.eventSink(InviteListEvents.AcceptInvite(originalState.inviteList[0])) - assertThat(awaitItem().acceptedAction).isEqualTo(Async.Failure(ex)) + assertThat(awaitItem().acceptedAction).isEqualTo(AsyncData.Failure(ex)) } } @@ -350,7 +350,7 @@ class InviteListPresenterTests { originalState.eventSink(InviteListEvents.DismissAcceptError) val newState = awaitItem() - assertThat(newState.acceptedAction).isEqualTo(Async.Uninitialized) + assertThat(newState.acceptedAction).isEqualTo(AsyncData.Uninitialized) } } 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 486d3fb4a8..94b232b2ac 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 @@ -22,7 +22,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow class FakeSeenInvitesStore : SeenInvitesStore { - private val existing = MutableStateFlow(emptySet()) private var provided: Set? = null diff --git a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt index df6d44df4d..f069533d1a 100644 --- a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt +++ b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt @@ -26,6 +26,7 @@ data class LeaveRoomState( ) { sealed interface Confirmation { data object Hidden : Confirmation + data class Dm(val roomId: RoomId) : Confirmation data class Generic(val roomId: RoomId) : Confirmation data class PrivateRoom(val roomId: RoomId) : Confirmation data class LastUserInRoom(val roomId: RoomId) : Confirmation diff --git a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomStateProvider.kt b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomStateProvider.kt index f82b58cba3..8c966b1a13 100644 --- a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomStateProvider.kt +++ b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomStateProvider.kt @@ -28,17 +28,17 @@ class LeaveRoomStateProvider : PreviewParameterProvider { error = LeaveRoomState.Error.Hidden, ), aLeaveRoomState( - confirmation = LeaveRoomState.Confirmation.Generic(A_ROOM_ID), + confirmation = LeaveRoomState.Confirmation.Generic(roomId = A_ROOM_ID), progress = LeaveRoomState.Progress.Hidden, error = LeaveRoomState.Error.Hidden, ), aLeaveRoomState( - confirmation = LeaveRoomState.Confirmation.PrivateRoom(A_ROOM_ID), + confirmation = LeaveRoomState.Confirmation.PrivateRoom(roomId = A_ROOM_ID), progress = LeaveRoomState.Progress.Hidden, error = LeaveRoomState.Error.Hidden, ), aLeaveRoomState( - confirmation = LeaveRoomState.Confirmation.LastUserInRoom(A_ROOM_ID), + confirmation = LeaveRoomState.Confirmation.LastUserInRoom(roomId = A_ROOM_ID), progress = LeaveRoomState.Progress.Hidden, error = LeaveRoomState.Error.Hidden, ), @@ -52,6 +52,11 @@ class LeaveRoomStateProvider : PreviewParameterProvider { progress = LeaveRoomState.Progress.Hidden, error = LeaveRoomState.Error.Shown, ), + aLeaveRoomState( + confirmation = LeaveRoomState.Confirmation.Dm(roomId = A_ROOM_ID), + progress = LeaveRoomState.Progress.Hidden, + error = LeaveRoomState.Error.Hidden, + ), ) } diff --git a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomView.kt b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomView.kt index 22b5fcfd03..5168ee9936 100644 --- a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomView.kt +++ b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomView.kt @@ -27,8 +27,8 @@ import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.ui.strings.CommonStrings @@ -47,21 +47,32 @@ private fun LeaveRoomConfirmationDialog( ) { when (state.confirmation) { is LeaveRoomState.Confirmation.Hidden -> {} + + is LeaveRoomState.Confirmation.Dm -> LeaveRoomConfirmationDialog( + text = R.string.leave_conversation_alert_subtitle, + roomId = state.confirmation.roomId, + isDm = true, + eventSink = state.eventSink, + ) + is LeaveRoomState.Confirmation.PrivateRoom -> LeaveRoomConfirmationDialog( text = R.string.leave_room_alert_private_subtitle, roomId = state.confirmation.roomId, + isDm = false, eventSink = state.eventSink, ) is LeaveRoomState.Confirmation.LastUserInRoom -> LeaveRoomConfirmationDialog( text = R.string.leave_room_alert_empty_subtitle, roomId = state.confirmation.roomId, + isDm = false, eventSink = state.eventSink, ) is LeaveRoomState.Confirmation.Generic -> LeaveRoomConfirmationDialog( text = R.string.leave_room_alert_subtitle, roomId = state.confirmation.roomId, + isDm = false, eventSink = state.eventSink, ) } @@ -71,10 +82,11 @@ private fun LeaveRoomConfirmationDialog( private fun LeaveRoomConfirmationDialog( @StringRes text: Int, roomId: RoomId, + isDm: Boolean, eventSink: (LeaveRoomEvent) -> Unit, ) { ConfirmationDialog( - title = stringResource(CommonStrings.action_leave_room), + title = stringResource(if (isDm) CommonStrings.action_leave_conversation else CommonStrings.action_leave_room), content = stringResource(text), submitText = stringResource(CommonStrings.action_leave), onSubmitClicked = { eventSink(LeaveRoomEvent.LeaveRoom(roomId)) }, diff --git a/features/leaveroom/api/src/main/res/values-hu/translations.xml b/features/leaveroom/api/src/main/res/values-hu/translations.xml index 824c088cdb..0fb52a61aa 100644 --- a/features/leaveroom/api/src/main/res/values-hu/translations.xml +++ b/features/leaveroom/api/src/main/res/values-hu/translations.xml @@ -1,6 +1,6 @@ "Biztos, hogy elhagyja ezt a szobát? Ön az egyedüli ember itt. Ha kilép, akkor senki sem fog tudni csatlakozni a jövőben, Önt is beleértve." - "Biztos, hogy elhagyja ezt a szobát? Ez a szoba nem nyilvános, és meghívó nélkül nem fog tudni újra belépni." - "Biztos, hogy elhagyja a szobát?" + "Biztos, hogy elhagyod ezt a szobát? Ez a szoba nem nyilvános, és meghívó nélkül nem fogsz tudni újra belépni." + "Biztos, hogy elhagyod a szobát?" diff --git a/features/leaveroom/api/src/main/res/values/localazy.xml b/features/leaveroom/api/src/main/res/values/localazy.xml index 262369c9be..f068e772bb 100644 --- a/features/leaveroom/api/src/main/res/values/localazy.xml +++ b/features/leaveroom/api/src/main/res/values/localazy.xml @@ -1,5 +1,6 @@ + "Are you sure that you want to leave this conversation? This conversation is not public and you won\'t be able to rejoin without an invite." "Are you sure that you want to leave this room? You\'re the only person here. If you leave, no one will be able to join in the future, including you." "Are you sure that you want to leave this room? This room is not public and you won\'t be able to rejoin without an invite." "Are you sure that you want to leave the room?" diff --git a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImpl.kt b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImpl.kt index 2ad35b38c9..56c2f9b8fd 100644 --- a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImpl.kt +++ b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImpl.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.rememberCoroutineScope import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomPresenter import io.element.android.features.leaveroom.api.LeaveRoomState +import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.Dm import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.Generic import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.LastUserInRoom import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.PrivateRoom @@ -85,6 +86,7 @@ private suspend fun showLeaveRoomAlert( ) { matrixClient.getRoom(roomId)?.use { room -> confirmation.value = when { + room.isDm -> Dm(roomId) !room.isPublic -> PrivateRoom(roomId) room.joinedMemberCount == 1L -> LastUserInRoom(roomId) else -> Generic(roomId) diff --git a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt index a508b4f448..8bc8157c05 100644 --- a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt +++ b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt @@ -38,7 +38,6 @@ import org.junit.Rule import org.junit.Test class LeaveRoomPresenterImplTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -115,6 +114,26 @@ class LeaveRoomPresenterImplTest { } } + @Test + fun `present - show DM confirmation`() = runTest { + val presenter = createLeaveRoomPresenter( + client = FakeMatrixClient().apply { + givenGetRoomResult( + roomId = A_ROOM_ID, + result = FakeMatrixRoom(activeMemberCount = 2, isDirect = true, isOneToOne = true), + ) + } + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(LeaveRoomEvent.ShowConfirmation(A_ROOM_ID)) + val confirmationState = awaitItem() + assertThat(confirmationState.confirmation).isEqualTo(LeaveRoomState.Confirmation.Dm(A_ROOM_ID)) + } + } + @Test fun `present - leaving a room leaves the room`() = runTest { val roomMembershipObserver = RoomMembershipObserver() diff --git a/features/leaveroom/test/src/main/kotlin/io/element/android/features/leaveroom/fake/FakeLeaveRoomPresenter.kt b/features/leaveroom/test/src/main/kotlin/io/element/android/features/leaveroom/fake/FakeLeaveRoomPresenter.kt index 0aac5a0d74..297522825c 100644 --- a/features/leaveroom/test/src/main/kotlin/io/element/android/features/leaveroom/fake/FakeLeaveRoomPresenter.kt +++ b/features/leaveroom/test/src/main/kotlin/io/element/android/features/leaveroom/fake/FakeLeaveRoomPresenter.kt @@ -22,7 +22,6 @@ import io.element.android.features.leaveroom.api.LeaveRoomPresenter import io.element.android.features.leaveroom.api.LeaveRoomState class FakeLeaveRoomPresenter : LeaveRoomPresenter { - val events = mutableListOf() private fun handleEvent(event: LeaveRoomEvent) { diff --git a/features/location/api/build.gradle.kts b/features/location/api/build.gradle.kts index bef0e77b67..4f1d588069 100644 --- a/features/location/api/build.gradle.kts +++ b/features/location/api/build.gradle.kts @@ -45,14 +45,16 @@ android { name = "maptiler_light_map_id", value = System.getenv("ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID") ?: readLocalProperty("services.maptiler.lightMapId") - ?: "basic-v2" // fall back to maptiler's default light map. + // fall back to maptiler's default light map. + ?: "basic-v2" ) resValue( type = "string", name = "maptiler_dark_map_id", value = System.getenv("ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID") ?: readLocalProperty("services.maptiler.darkMapId") - ?: "basic-v2-dark" // fall back to maptiler's default dark map. + // fall back to maptiler's default dark map. + ?: "basic-v2-dark" ) } } diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/ShowLocationEntryPoint.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/ShowLocationEntryPoint.kt index 3c429dfa63..7553033084 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/ShowLocationEntryPoint.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/ShowLocationEntryPoint.kt @@ -22,7 +22,6 @@ import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.architecture.NodeInputs interface ShowLocationEntryPoint : FeatureEntryPoint { - data class Inputs(val location: Location, val description: String?) : NodeInputs fun createNode(parentNode: Node, buildContext: BuildContext, inputs: Inputs): Node diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt index 927e890248..14a97ff086 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt @@ -29,7 +29,6 @@ internal class MapTilerStaticMapUrlBuilder( private val lightMapId: String, private val darkMapId: String, ) : StaticMapUrlBuilder { - constructor(context: Context) : this( apiKey = context.apiKey, lightMapId = context.mapId(darkMode = false), @@ -65,7 +64,7 @@ internal class MapTilerStaticMapUrlBuilder( // image smaller than the available space in pixels. // The resulting image will have to be scaled to fit the available space in order // to keep the perceived content size constant at the expense of sharpness. - return "$MAPTILER_BASE_URL/${mapId}/static/${lon},${lat},${finalZoom}/${finalWidth}x${finalHeight}${scale}.webp?key=${apiKey}&attribution=bottomleft" + return "$MAPTILER_BASE_URL/$mapId/static/$lon,$lat,$finalZoom/${finalWidth}x${finalHeight}$scale.webp?key=$apiKey&attribution=bottomleft" } } diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt index 6972e45330..a3d86850c5 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt @@ -25,7 +25,6 @@ internal class MapTilerTileServerStyleUriBuilder( private val lightMapId: String, private val darkMapId: String, ) : TileServerStyleUriBuilder { - constructor(context: Context) : this( apiKey = context.apiKey, lightMapId = context.mapId(darkMode = false), @@ -34,6 +33,6 @@ internal class MapTilerTileServerStyleUriBuilder( override fun build(darkMode: Boolean): String { val mapId = if (darkMode) darkMapId else lightMapId - return "${MAPTILER_BASE_URL}/${mapId}/style.json?key=${apiKey}" + return "$MAPTILER_BASE_URL/$mapId/style.json?key=$apiKey" } } diff --git a/features/location/api/src/test/kotlin/io/element/android/features/location/api/LocationKtTest.kt b/features/location/api/src/test/kotlin/io/element/android/features/location/api/LocationKtTest.kt index f3d1f72f22..4bbf7f89fa 100644 --- a/features/location/api/src/test/kotlin/io/element/android/features/location/api/LocationKtTest.kt +++ b/features/location/api/src/test/kotlin/io/element/android/features/location/api/LocationKtTest.kt @@ -20,7 +20,6 @@ import com.google.common.truth.Truth.assertThat import org.junit.Test internal class LocationKtTest { - @Test fun `parseGeoUri - returns null for invalid urls`() { assertThat(Location.fromGeoUri("")).isNull() @@ -78,7 +77,7 @@ internal class LocationKtTest { @Test fun `encode geoUri - returns geoUri from a Location`() { - assertThat(Location(1.0,2.0,3.0f).toGeoUri()) + assertThat(Location(1.0, 2.0, 3.0f).toGeoUri()) .isEqualTo("geo:1.0,2.0;u=3.0") } } diff --git a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt index c359777ee7..ad17677909 100644 --- a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt +++ b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt @@ -20,7 +20,6 @@ import com.google.common.truth.Truth.assertThat import org.junit.Test class MapTilerStaticMapUrlBuilderTest { - private val builder = MapTilerStaticMapUrlBuilder( apiKey = "anApiKey", lightMapId = "aLightMapId", diff --git a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt index abff83c582..045bbd0206 100644 --- a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt +++ b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt @@ -20,7 +20,6 @@ import com.google.common.truth.Truth.assertThat import org.junit.Test class MapTilerTileServerStyleUriBuilderTest { - private val builder = MapTilerTileServerStyleUriBuilder( apiKey = "anApiKey", lightMapId = "aLightMapId", diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsPresenterImpl.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsPresenterImpl.kt index 1a334212a2..7966640231 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsPresenterImpl.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsPresenterImpl.kt @@ -29,7 +29,6 @@ import io.element.android.libraries.di.AppScope class PermissionsPresenterImpl @AssistedInject constructor( @Assisted private val permissions: List ) : PermissionsPresenter { - @AssistedFactory @ContributesBinding(AppScope::class) interface Factory : PermissionsPresenter.Factory { diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationEntryPointImpl.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationEntryPointImpl.kt index 9edf195e28..fb5ff1c1c7 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationEntryPointImpl.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationEntryPointImpl.kt @@ -27,6 +27,7 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class SendLocationEntryPointImpl @Inject constructor() : SendLocationEntryPoint { override fun createNode( - parentNode: Node, buildContext: BuildContext + parentNode: Node, + buildContext: BuildContext ): SendLocationNode = parentNode.createNode(buildContext) } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationNode.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationNode.kt index be4d3f0764..806c6f9e78 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationNode.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationNode.kt @@ -36,7 +36,6 @@ class SendLocationNode @AssistedInject constructor( private val presenter: SendLocationPresenter, analyticsService: AnalyticsService, ) : Node(buildContext, plugins = plugins) { - init { lifecycle.subscribe( onResume = { diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationPresenter.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationPresenter.kt index 94af55c01c..4268738e48 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationPresenter.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationPresenter.kt @@ -47,7 +47,6 @@ class SendLocationPresenter @Inject constructor( private val locationActions: LocationActions, private val buildMeta: BuildMeta, ) : Presenter { - private val permissionsPresenter = permissionsPresenterFactory.create(MapDefaults.permissions) @Composable @@ -55,8 +54,11 @@ class SendLocationPresenter @Inject constructor( val permissionsState: PermissionsState = permissionsPresenter.present() var mode: SendLocationState.Mode by remember { mutableStateOf( - if (permissionsState.isAnyGranted) SendLocationState.Mode.SenderLocation - else SendLocationState.Mode.PinLocation + if (permissionsState.isAnyGranted) { + SendLocationState.Mode.SenderLocation + } else { + SendLocationState.Mode.PinLocation + } ) } val appName by remember { derivedStateOf { buildMeta.applicationName } } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationNode.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationNode.kt index 24094b03ca..610f0b9079 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationNode.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationNode.kt @@ -38,7 +38,6 @@ class ShowLocationNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, ) : Node(buildContext, plugins = plugins) { - init { lifecycle.subscribe( onResume = { diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt index 150a314ce7..669731a27e 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt @@ -42,7 +42,6 @@ class ShowLocationPresenter @AssistedInject constructor( @Assisted private val location: Location, @Assisted private val description: String? ) : Presenter { - @AssistedFactory interface Factory { fun create(location: Location, description: String?): ShowLocationPresenter diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationView.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationView.kt index 999c2ee36a..85575197ed 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationView.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationView.kt @@ -17,7 +17,6 @@ package io.element.android.features.location.impl.show import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -64,7 +63,7 @@ import io.element.android.libraries.maplibre.compose.rememberSymbolState import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.toImmutableMap -@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ShowLocationView( state: ShowLocationState, diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt index a010d383a0..6cd7cf82ce 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt @@ -22,7 +22,6 @@ import org.junit.Test import java.net.URLEncoder internal class AndroidLocationActionsTest { - // We use an Android-native encoder in the actual app, switch to an equivalent JVM one for the tests private fun urlEncoder(input: String) = URLEncoder.encode(input, "US-ASCII") diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/FakeLocationActions.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/FakeLocationActions.kt index 6fc3d51ae1..0c902c521e 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/FakeLocationActions.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/FakeLocationActions.kt @@ -19,7 +19,6 @@ package io.element.android.features.location.impl.common.actions import io.element.android.features.location.api.Location class FakeLocationActions : LocationActions { - var sharedLocation: Location? = null private set diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsPresenterFake.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsPresenterFake.kt index dfeb18d4db..e79ac9e453 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsPresenterFake.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsPresenterFake.kt @@ -19,7 +19,6 @@ package io.element.android.features.location.impl.common.permissions import androidx.compose.runtime.Composable class PermissionsPresenterFake : PermissionsPresenter { - val events = mutableListOf() private fun handleEvent(event: PermissionsEvents) { diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt index cfa52c4269..b6b469c44a 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt @@ -42,7 +42,6 @@ import org.junit.Rule import org.junit.Test class SendLocationPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -75,7 +74,6 @@ class SendLocationPresenterTest { moleculeFlow(RecompositionMode.Immediate) { sendLocationPresenter.present() }.test { - val initialState = awaitItem() assertThat(initialState.permissionDialog).isEqualTo(SendLocationState.Dialog.None) assertThat(initialState.mode).isEqualTo(SendLocationState.Mode.SenderLocation) @@ -102,7 +100,6 @@ class SendLocationPresenterTest { moleculeFlow(RecompositionMode.Immediate) { sendLocationPresenter.present() }.test { - val initialState = awaitItem() assertThat(initialState.permissionDialog).isEqualTo(SendLocationState.Dialog.None) assertThat(initialState.mode).isEqualTo(SendLocationState.Mode.SenderLocation) @@ -381,7 +378,9 @@ class SendLocationPresenterTest { ) fakeMessageComposerContext.apply { composerMode = MessageComposerMode.Edit( - eventId = null, defaultContent = "", transactionId = null + eventId = null, + defaultContent = "", + transactionId = null ) } @@ -427,7 +426,9 @@ class SendLocationPresenterTest { ) fakeMessageComposerContext.apply { composerMode = MessageComposerMode.Edit( - eventId = null, defaultContent = "", transactionId = null + eventId = null, + defaultContent = "", + transactionId = null ) } diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt index 4db3755fbd..ff80a3935d 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt @@ -35,7 +35,6 @@ import org.junit.Rule import org.junit.Test class ShowLocationPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenEntryPoint.kt b/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenEntryPoint.kt index fb96895721..6a2fb0c72d 100644 --- a/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenEntryPoint.kt +++ b/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenEntryPoint.kt @@ -22,7 +22,6 @@ import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint interface LockScreenEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder interface NodeBuilder { diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPoint.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPoint.kt index 67182e4fff..5065cdc7f2 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPoint.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPoint.kt @@ -26,14 +26,11 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultLockScreenEntryPoint @Inject constructor() : LockScreenEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): LockScreenEntryPoint.NodeBuilder { - var innerTarget: LockScreenEntryPoint.Target = LockScreenEntryPoint.Target.Unlock val callbacks = mutableListOf() return object : LockScreenEntryPoint.NodeBuilder { - override fun callback(callback: LockScreenEntryPoint.Callback): LockScreenEntryPoint.NodeBuilder { callbacks += callback return this diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt index 96cf7a655b..b0b7ab75b3 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt @@ -57,25 +57,24 @@ class DefaultLockScreenService @Inject constructor( private val appForegroundStateService: AppForegroundStateService, private val biometricUnlockManager: BiometricUnlockManager, ) : LockScreenService { - - private val _lockScreenState = MutableStateFlow(LockScreenLockState.Unlocked) - override val lockState: StateFlow = _lockScreenState + private val _lockState = MutableStateFlow(LockScreenLockState.Unlocked) + override val lockState: StateFlow = _lockState private var lockJob: Job? = null init { pinCodeManager.addCallback(object : DefaultPinCodeManagerCallback() { override fun onPinCodeVerified() { - _lockScreenState.value = LockScreenLockState.Unlocked + _lockState.value = LockScreenLockState.Unlocked } override fun onPinCodeRemoved() { - _lockScreenState.value = LockScreenLockState.Unlocked + _lockState.value = LockScreenLockState.Unlocked } }) biometricUnlockManager.addCallback(object : DefaultBiometricUnlockCallback() { override fun onBiometricUnlockSuccess() { - _lockScreenState.value = LockScreenLockState.Unlocked + _lockState.value = LockScreenLockState.Unlocked coroutineScope.launch { lockScreenStore.resetCounter() } @@ -91,11 +90,10 @@ class DefaultLockScreenService @Inject constructor( */ private fun observeSessionsState() { sessionObserver.addListener(object : SessionListener { - override suspend fun onSessionCreated(userId: String) = Unit override suspend fun onSessionDeleted(userId: String) { - //TODO handle multi session at some point + // TODO handle multi session at some point pinCodeManager.deletePinCode() } }) @@ -135,7 +133,7 @@ class DefaultLockScreenService @Inject constructor( private fun CoroutineScope.lockIfNeeded(gracePeriod: Duration = Duration.ZERO) = launch { if (isPinSetup().first()) { delay(gracePeriod) - _lockScreenState.value = LockScreenLockState.Locked + _lockState.value = LockScreenLockState.Locked } } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenFlowNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenFlowNode.kt index 76a967dfd7..8dd75fc65d 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenFlowNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenFlowNode.kt @@ -50,7 +50,6 @@ class LockScreenFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins, ) { - data class Inputs( val initialNavTarget: NavTarget = NavTarget.Unlock, ) : NodeInputs diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlock.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlock.kt index c7029421e8..3679840b9e 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlock.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlock.kt @@ -31,7 +31,6 @@ import java.security.InvalidKeyException import javax.crypto.Cipher interface BiometricUnlock { - interface Callback { fun onBiometricSetupError() fun onBiometricUnlockSuccess() @@ -62,7 +61,6 @@ class DefaultBiometricUnlock( private val keyAlias: String, private val callbacks: List ) : BiometricUnlock { - override val isActive: Boolean = true private lateinit var cryptoObject: CryptoObject @@ -105,7 +103,6 @@ private class AuthenticationCallback( private val callbacks: List, private val deferredAuthenticationResult: CompletableDeferred, ) : BiometricPrompt.AuthenticationCallback() { - override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { super.onAuthenticationError(errorCode, errString) val biometricUnlockError = BiometricUnlockError(errorCode, errString.toString()) diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlockManager.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlockManager.kt index f7fe416f23..24cca2d46e 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlockManager.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlockManager.kt @@ -19,7 +19,6 @@ package io.element.android.features.lockscreen.impl.biometric import androidx.compose.runtime.Composable interface BiometricUnlockManager { - /** * If the device is secured for example with a pin, pattern or password. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockManager.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockManager.kt index 2d3dd7146b..95951866f3 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockManager.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockManager.kt @@ -57,7 +57,6 @@ class DefaultBiometricUnlockManager @Inject constructor( private val secretKeyRepository: SecretKeyRepository, private val coroutineScope: CoroutineScope, ) : BiometricUnlockManager { - private val callbacks = CopyOnWriteArrayList() private val biometricManager = BiometricManager.from(context) private val keyguardManager: KeyguardManager = context.getSystemService()!! @@ -66,15 +65,15 @@ class DefaultBiometricUnlockManager @Inject constructor( * Returns true if a weak biometric method (i.e.: some face or iris unlock implementations) can be used. */ private val canUseWeakBiometricAuth: Boolean - get() = lockScreenConfig.isWeakBiometricsEnabled - && biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS + get() = lockScreenConfig.isWeakBiometricsEnabled && + biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS /** * Returns true if a strong biometric method (i.e.: fingerprint, some face or iris unlock implementations) can be used. */ private val canUseStrongBiometricAuth: Boolean - get() = lockScreenConfig.isStrongBiometricsEnabled - && biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS + get() = lockScreenConfig.isStrongBiometricsEnabled && + biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS /** * Returns true if any biometric method (weak or strong) can be used. diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/components/PinEntryTextField.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/components/PinEntryTextField.kt index f0d5942542..3c7a451bfc 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/components/PinEntryTextField.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/components/PinEntryTextField.kt @@ -34,12 +34,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.features.lockscreen.impl.pin.model.PinDigit import io.element.android.features.lockscreen.impl.pin.model.PinEntry import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.pinDigitBg -import io.element.android.compound.theme.ElementTheme @Composable fun PinEntryTextField( @@ -99,8 +99,7 @@ private fun PinDigitView( .size(48.dp) .then(appearanceModifier), contentAlignment = Alignment.Center, - - ) { + ) { if (digit is PinDigit.Filled) { val text = if (isSecured) { "•" @@ -112,7 +111,6 @@ private fun PinDigitView( style = ElementTheme.typography.fontHeadingMdBold ) } - } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt index c08513c537..ee5b0ede5e 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt @@ -36,7 +36,6 @@ class DefaultPinCodeManager @Inject constructor( private val encryptionDecryptionService: EncryptionDecryptionService, private val lockScreenStore: LockScreenStore, ) : PinCodeManager { - private val callbacks = CopyOnWriteArrayList() override fun addCallback(callback: PinCodeManager.Callback) { diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/PinCodeManager.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/PinCodeManager.kt index ae3519759f..b197fa45ed 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/PinCodeManager.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/PinCodeManager.kt @@ -23,7 +23,6 @@ import kotlinx.coroutines.flow.Flow * Implementation should take care of encrypting the pin code and storing it. */ interface PinCodeManager { - /** * Callbacks for pin code management events. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntry.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntry.kt index 96c3bec3ad..9be238c7ef 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntry.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntry.kt @@ -22,7 +22,6 @@ import kotlinx.collections.immutable.toPersistentList data class PinEntry( val digits: ImmutableList, ) { - companion object { fun createEmpty(size: Int): PinEntry { val digits = List(size) { PinDigit.Empty } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt index 1008ebdcf0..0890f38d1e 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt @@ -56,7 +56,6 @@ class LockScreenSettingsFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins, ) { - sealed interface NavTarget : Parcelable { @Parcelize data object Unknown : NavTarget @@ -72,7 +71,6 @@ class LockScreenSettingsFlowNode @AssistedInject constructor( } private val pinCodeManagerCallback = object : DefaultPinCodeManagerCallback() { - override fun onPinCodeRemoved() { navigateUp() } @@ -125,7 +123,6 @@ class LockScreenSettingsFlowNode @AssistedInject constructor( createNode(buildContext, plugins = listOf(callback)) } NavTarget.Unknown -> node(buildContext) { } - } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsNode.kt index 96f5393483..edc50df50e 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsNode.kt @@ -33,7 +33,6 @@ class LockScreenSettingsNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: LockScreenSettingsPresenter, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun onChangePinClicked() } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt index b60b472f72..fbbe0442c1 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt @@ -39,7 +39,6 @@ class LockScreenSettingsPresenter @Inject constructor( private val biometricUnlockManager: BiometricUnlockManager, private val coroutineScope: CoroutineScope, ) : Presenter { - @Composable override fun present(): LockScreenSettingsState { val showRemovePinOption by produceState(initialValue = false) { diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsView.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsView.kt index 72ad733f4f..7c8634a078 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsView.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsView.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.compound.theme.ElementTheme import io.element.android.features.lockscreen.impl.R import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory @@ -29,7 +30,6 @@ import io.element.android.libraries.designsystem.components.preferences.Preferen import io.element.android.libraries.designsystem.components.preferences.PreferenceText import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.compound.theme.ElementTheme @Composable fun LockScreenSettingsView( @@ -79,7 +79,8 @@ fun LockScreenSettingsView( }, onDismiss = { state.eventSink(LockScreenSettingsEvents.CancelRemovePin) - }) + } + ) } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/LockScreenSetupFlowNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/LockScreenSetupFlowNode.kt index c9b96d76f2..430fc75805 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/LockScreenSetupFlowNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/LockScreenSetupFlowNode.kt @@ -52,7 +52,6 @@ class LockScreenSetupFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins, ) { - interface Callback : Plugin { fun onSetupDone() } @@ -70,7 +69,6 @@ class LockScreenSetupFlowNode @AssistedInject constructor( } private val pinCodeManagerCallback = object : DefaultPinCodeManagerCallback() { - override fun onPinCodeCreated() { backstack.newRoot(NavTarget.Biometric) } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricNode.kt index ae99730a0e..5d9f3742f7 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricNode.kt @@ -34,7 +34,6 @@ class SetupBiometricNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: SetupBiometricPresenter, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun onBiometricSetupDone() } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenter.kt index ff65a2c7aa..693a908d56 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenter.kt @@ -30,10 +30,8 @@ import javax.inject.Inject class SetupBiometricPresenter @Inject constructor( private val lockScreenStore: LockScreenStore, ) : Presenter { - @Composable override fun present(): SetupBiometricState { - var isBiometricSetupDone by remember { mutableStateOf(false) } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricView.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricView.kt index 8b2a4fce4f..8e7a326c90 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricView.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricView.kt @@ -97,4 +97,3 @@ internal fun SetupBiometricViewPreview(@PreviewParameter(SetupBiometricStateProv ) } } - diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinNode.kt index c159c4cc58..ddd12f65df 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinNode.kt @@ -32,7 +32,6 @@ class SetupPinNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: SetupPinPresenter, ) : Node(buildContext, plugins = plugins) { - @Composable override fun View(modifier: Modifier) { val state = presenter.present() diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt index d48e418a59..ff2bdc56db 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt @@ -43,7 +43,6 @@ class SetupPinPresenter @Inject constructor( private val buildMeta: BuildMeta, private val pinCodeManager: PinCodeManager, ) : Presenter { - @Composable override fun present(): SetupPinState { var choosePinEntry by remember { diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt index 582c27b5fe..d1820ea75d 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt @@ -41,7 +41,6 @@ open class SetupPinStateProvider : PreviewParameterProvider { choosePinEntry = PinEntry.createEmpty(4).fillWith("1111"), creationFailure = SetupPinFailure.PinBlacklisted ), - ) } @@ -58,4 +57,3 @@ fun aSetupPinState( appName = "Element", eventSink = {} ) - diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt index ca01aab61f..22e6275079 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt @@ -21,7 +21,6 @@ import io.element.android.features.lockscreen.impl.pin.model.PinEntry import javax.inject.Inject class PinValidator @Inject constructor(private val lockScreenConfig: LockScreenConfig) { - sealed interface Result { data object Valid : Result data class Invalid(val failure: SetupPinFailure) : Result diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/EncryptedPinCodeStorage.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/EncryptedPinCodeStorage.kt index d2a5ba4204..7e022029dc 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/EncryptedPinCodeStorage.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/EncryptedPinCodeStorage.kt @@ -42,5 +42,4 @@ interface EncryptedPinCodeStorage { * Returns whether the PIN code is stored or not. */ fun hasPinCode(): Flow - } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/LockScreenStore.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/LockScreenStore.kt index 77ce61d190..ca1ab96d85 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/LockScreenStore.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/LockScreenStore.kt @@ -19,7 +19,6 @@ package io.element.android.features.lockscreen.impl.storage import kotlinx.coroutines.flow.Flow interface LockScreenStore : EncryptedPinCodeStorage { - /** * Returns the remaining PIN code attempts. When this reaches 0 the PIN code access won't be available for some time. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/PreferencesLockScreenStore.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/PreferencesLockScreenStore.kt index cfdf081986..b32ef8fd4c 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/PreferencesLockScreenStore.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/PreferencesLockScreenStore.kt @@ -42,7 +42,6 @@ class PreferencesLockScreenStore @Inject constructor( @ApplicationContext private val context: Context, private val lockScreenConfig: LockScreenConfig, ) : LockScreenStore { - private val pinCodeKey = stringPreferencesKey("encoded_pin_code") private val remainingAttemptsKey = intPreferencesKey("remaining_pin_code_attempts") private val biometricUnlockKey = booleanPreferencesKey("biometric_unlock_enabled") diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt index 72d29b4481..e1dd7f5be3 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt @@ -28,7 +28,6 @@ class PinUnlockHelper @Inject constructor( private val biometricUnlockManager: BiometricUnlockManager, private val pinCodeManager: PinCodeManager ) { - @Composable fun OnUnlockEffect(onUnlock: () -> Unit) { DisposableEffect(Unit) { diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockNode.kt index fbda73fd45..da6853a39c 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockNode.kt @@ -36,7 +36,6 @@ class PinUnlockNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: PinUnlockPresenter, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun onUnlock() } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt index 1b5ce80085..aebf95aa37 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt @@ -29,7 +29,7 @@ import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockMana import io.element.android.features.lockscreen.impl.pin.PinCodeManager import io.element.android.features.lockscreen.impl.pin.model.PinEntry import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.bool.orFalse @@ -45,15 +45,14 @@ class PinUnlockPresenter @Inject constructor( private val coroutineScope: CoroutineScope, private val pinUnlockHelper: PinUnlockHelper, ) : Presenter { - @Composable override fun present(): PinUnlockState { val pinEntryState = remember { - mutableStateOf>(Async.Uninitialized) + mutableStateOf>(AsyncData.Uninitialized) } val pinEntry by pinEntryState var remainingAttempts by remember { - mutableStateOf>(Async.Uninitialized) + mutableStateOf>(AsyncData.Uninitialized) } var showWrongPinTitle by rememberSaveable { mutableStateOf(false) @@ -62,7 +61,7 @@ class PinUnlockPresenter @Inject constructor( mutableStateOf(false) } val signOutAction = remember { - mutableStateOf>(Async.Uninitialized) + mutableStateOf>(AsyncData.Uninitialized) } var biometricUnlockResult by remember { mutableStateOf(null) @@ -91,7 +90,7 @@ class PinUnlockPresenter @Inject constructor( } } val remainingAttemptsNumber = pinCodeManager.getRemainingPinCodeAttemptsNumber() - remainingAttempts = Async.Success(remainingAttemptsNumber) + remainingAttempts = AsyncData.Success(remainingAttemptsNumber) if (remainingAttemptsNumber == 0) { showSignOutPrompt = true } @@ -139,46 +138,46 @@ class PinUnlockPresenter @Inject constructor( ) } - private fun Async.isComplete(): Boolean { + private fun AsyncData.isComplete(): Boolean { return dataOrNull()?.isComplete().orFalse() } - private fun Async.toText(): String { + private fun AsyncData.toText(): String { return dataOrNull()?.toText() ?: "" } - private fun Async.clear(): Async { + private fun AsyncData.clear(): AsyncData { return when (this) { - is Async.Success -> Async.Success(data.clear()) + is AsyncData.Success -> AsyncData.Success(data.clear()) else -> this } } - private fun Async.process(pinKeypadModel: PinKeypadModel): Async { + private fun AsyncData.process(pinKeypadModel: PinKeypadModel): AsyncData { return when (this) { - is Async.Success -> { + is AsyncData.Success -> { val pinEntry = when (pinKeypadModel) { PinKeypadModel.Back -> data.deleteLast() is PinKeypadModel.Number -> data.addDigit(pinKeypadModel.number) PinKeypadModel.Empty -> data } - Async.Success(pinEntry) + AsyncData.Success(pinEntry) } else -> this } } - private fun Async.process(pinEntryAsText: String): Async { + private fun AsyncData.process(pinEntryAsText: String): AsyncData { return when (this) { - is Async.Success -> { + is AsyncData.Success -> { val pinEntry = data.fillWith(pinEntryAsText) - Async.Success(pinEntry) + AsyncData.Success(pinEntry) } else -> this } } - private fun CoroutineScope.signOut(signOutAction: MutableState>) = launch { + private fun CoroutineScope.signOut(signOutAction: MutableState>) = launch { suspend { matrixClient.logout(ignoreSdkError = true) }.runCatchingUpdatingState(signOutAction) diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt index 006234270c..cfe371eb3f 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt @@ -19,28 +19,28 @@ package io.element.android.features.lockscreen.impl.unlock import io.element.android.features.lockscreen.impl.biometric.BiometricUnlock import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockError import io.element.android.features.lockscreen.impl.pin.model.PinEntry -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData data class PinUnlockState( - val pinEntry: Async, + val pinEntry: AsyncData, val showWrongPinTitle: Boolean, - val remainingAttempts: Async, + val remainingAttempts: AsyncData, val showSignOutPrompt: Boolean, - val signOutAction: Async, + val signOutAction: AsyncData, val showBiometricUnlock: Boolean, val isUnlocked: Boolean, val biometricUnlockResult: BiometricUnlock.AuthenticationResult?, val eventSink: (PinUnlockEvents) -> Unit ) { val isSignOutPromptCancellable = when (remainingAttempts) { - is Async.Success -> remainingAttempts.data > 0 + is AsyncData.Success -> remainingAttempts.data > 0 else -> true } val biometricUnlockErrorMessage = when { - biometricUnlockResult is BiometricUnlock.AuthenticationResult.Failure - && biometricUnlockResult.error is BiometricUnlockError - && biometricUnlockResult.error.isAuthDisabledError -> { + biometricUnlockResult is BiometricUnlock.AuthenticationResult.Failure && + biometricUnlockResult.error is BiometricUnlockError && + biometricUnlockResult.error.isAuthDisabledError -> { biometricUnlockResult.error.message } else -> null diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt index 02da0e0350..151c3079ee 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt @@ -19,7 +19,7 @@ package io.element.android.features.lockscreen.impl.unlock import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.lockscreen.impl.biometric.BiometricUnlock import io.element.android.features.lockscreen.impl.pin.model.PinEntry -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData open class PinUnlockStateProvider : PreviewParameterProvider { override val values: Sequence @@ -30,7 +30,7 @@ open class PinUnlockStateProvider : PreviewParameterProvider { aPinUnlockState(showSignOutPrompt = true), aPinUnlockState(showBiometricUnlock = false), aPinUnlockState(showSignOutPrompt = true, remainingAttempts = 0), - aPinUnlockState(signOutAction = Async.Loading()), + aPinUnlockState(signOutAction = AsyncData.Loading()), ) } @@ -42,11 +42,11 @@ fun aPinUnlockState( showBiometricUnlock: Boolean = true, biometricUnlockResult: BiometricUnlock.AuthenticationResult? = null, isUnlocked: Boolean = false, - signOutAction: Async = Async.Uninitialized, + signOutAction: AsyncData = AsyncData.Uninitialized, ) = PinUnlockState( - pinEntry = Async.Success(pinEntry), + pinEntry = AsyncData.Success(pinEntry), showWrongPinTitle = showWrongPinTitle, - remainingAttempts = Async.Success(remainingAttempts), + remainingAttempts = AsyncData.Success(remainingAttempts), showSignOutPrompt = showSignOutPrompt, showBiometricUnlock = showBiometricUnlock, signOutAction = signOutAction, diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockView.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockView.kt index 325a41c393..ad4dc5da99 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockView.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockView.kt @@ -14,7 +14,6 @@ * limitations under the License. */ - package io.element.android.features.lockscreen.impl.unlock import androidx.compose.foundation.background @@ -51,12 +50,13 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle +import io.element.android.compound.theme.ElementTheme import io.element.android.features.lockscreen.impl.R import io.element.android.features.lockscreen.impl.components.PinEntryTextField import io.element.android.features.lockscreen.impl.pin.model.PinDigit import io.element.android.features.lockscreen.impl.pin.model.PinEntry import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypad -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog @@ -68,7 +68,6 @@ import io.element.android.libraries.designsystem.theme.components.Surface import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.utils.OnLifecycleEvent -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -92,7 +91,7 @@ fun PinUnlockView( onDismiss = { state.eventSink(PinUnlockEvents.ClearSignOutPrompt) }, ) } - if (state.signOutAction is Async.Loading) { + if (state.signOutAction is AsyncData.Loading) { ProgressDialog(text = stringResource(id = R.string.screen_signout_in_progress_dialog_content)) } if (state.showBiometricUnlockError) { @@ -335,7 +334,7 @@ private fun PinUnlockHeader( style = ElementTheme.typography.fontBodyMdRegular, color = subtitleColor, ) - if (!isInAppUnlock && state.pinEntry is Async.Success) { + if (!isInAppUnlock && state.pinEntry is AsyncData.Success) { Spacer(Modifier.height(24.dp)) PinDotsRow(state.pinEntry.data) } @@ -378,4 +377,3 @@ internal fun PinUnlockDefaultViewPreview(@PreviewParameter(PinUnlockStateProvide ) } } - diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt index a72483b13e..6c51b10979 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt @@ -41,11 +41,11 @@ import androidx.compose.ui.unit.coerceAtMost import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.times +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.text.toSp import io.element.android.libraries.designsystem.theme.components.Icon -import io.element.android.compound.theme.ElementTheme import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -210,5 +210,3 @@ internal fun PinKeypadPreview() { } } } - - diff --git a/features/lockscreen/impl/src/main/res/values-de/translations.xml b/features/lockscreen/impl/src/main/res/values-de/translations.xml index 5ece7d2914..a6159ede0d 100644 --- a/features/lockscreen/impl/src/main/res/values-de/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-de/translations.xml @@ -19,7 +19,7 @@ "PIN entfernen?" "%1$s zulassen" "Ich möchte diese PIN verwenden." - "Spare dir etwas Zeit und benutze %1$s, um die App jedes Mal zu entsperren" + "Spare dir etwas Zeit und benutze %1$s, um die App zu entsperren" "PIN wählen" "PIN bestätigen" "Aus Sicherheitsgründen kann dieser PIN-Code nicht verwendet werden." diff --git a/features/lockscreen/impl/src/main/res/values-es/translations.xml b/features/lockscreen/impl/src/main/res/values-es/translations.xml index 5f7393df00..8e2a9ab7a2 100644 --- a/features/lockscreen/impl/src/main/res/values-es/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-es/translations.xml @@ -1,4 +1,37 @@ + + "Tienes %1$d intento de desbloqueo" + "Tienes %1$d intentos de desbloqueo" + + + "PIN incorrecto. Tienes %1$d oportunidad más" + "PIN incorrecto. Tienes %1$d oportunidades más" + + "autenticación biométrica" + "desbloqueo biométrico" + "Desbloquear con biométrico" + "¿Olvidaste el PIN?" + "Cambiar código PIN" + "Permitir desbloqueo biométrico" + "Eliminar PIN" + "¿Estás seguro de que quieres eliminar el PIN?" + "¿Eliminar el PIN?" + "Permitir %1$s" + "Prefiero usar el PIN" + "Ahorra algo de tiempo y usa %1$s para desbloquear la aplicación cada vez" + "Elegir PIN" + "Confirmar PIN" + "No puedes usar este código PIN por motivos de seguridad" + "Elige un PIN diferente" + "Añade un bloqueo a %1$s para añadir seguridad adicional a tus chats. + +Elige algo que puedas recordar. Si olvidas este PIN, se cerrará la sesión de la aplicación." + "Por favor ingresa el mismo PIN dos veces" + "Los PINs no coinciden" + "Tendrás que volver a iniciar sesión y crear un nuevo PIN para continuar" + "Se está cerrando tu sesión" + "Usar desbloqueo biométrico" + "Usar PIN" "Cerrando sesión…" diff --git a/features/lockscreen/impl/src/main/res/values-it/translations.xml b/features/lockscreen/impl/src/main/res/values-it/translations.xml index 579346ed6e..bce8dc02fc 100644 --- a/features/lockscreen/impl/src/main/res/values-it/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-it/translations.xml @@ -1,4 +1,37 @@ + + "Hai %1$d tentativo di sblocco" + "Hai %1$d tentativi di sblocco" + + + "PIN sbagliato. Hai %1$d altro tentativo" + "PIN sbagliato. Hai altri %1$d tentativi" + + "autenticazione biometrica" + "sblocco biometrico" + "Sblocca con la biometria" + "PIN dimenticato?" + "Modifica il codice PIN" + "Consenti lo sblocco biometrico" + "Rimuovi PIN" + "Vuoi davvero rimuovere il PIN?" + "Rimuovere il PIN?" + "Consenti %1$s" + "Preferisco usare il PIN" + "Risparmia un po\' di tempo e usa %1$s per sbloccare l\'app ogni volta" + "Scegli il PIN" + "Conferma il PIN" + "Non puoi scegliere questo codice PIN per motivi di sicurezza" + "Scegli un PIN diverso" + "Blocca %1$s per aggiungere ulteriore sicurezza alle tue chat. + +Scegli qualcosa che puoi ricordare. Se dimentichi questo PIN, verrai disconnesso dall\'app." + "Inserisci lo stesso PIN due volte" + "I PIN non corrispondono" + "Dovrai effettuare nuovamente l\'accesso e creare un nuovo PIN per procedere" + "Stai per essere disconnesso" + "Usa la biometria" + "Usa il PIN" "Uscita in corso…" diff --git a/features/lockscreen/impl/src/main/res/values-ru/translations.xml b/features/lockscreen/impl/src/main/res/values-ru/translations.xml index 7b2c61176e..3ead0e4604 100644 --- a/features/lockscreen/impl/src/main/res/values-ru/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-ru/translations.xml @@ -11,11 +11,11 @@ "Неверный PIN-код. У вас остался %1$d шанса" "биометрическая идентификация" - "биометрическая разблокировать" + "биометрическая разблокировка" "Разблокировать с помощью биометрии" "Забыли PIN-код?" "Измените PIN-код" - "Разрешить биометрическую разблокировать" + "Разрешить биометрическую разблокировку" "Удалить PIN-код" "Вы действительно хотите удалить PIN-код?" "Удалить PIN-код?" diff --git a/features/lockscreen/impl/src/main/res/values-zh-rTW/translations.xml b/features/lockscreen/impl/src/main/res/values-zh-rTW/translations.xml index d01b56268e..5e079f1ae8 100644 --- a/features/lockscreen/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-zh-rTW/translations.xml @@ -5,6 +5,7 @@ "生物辨識認證" "生物辨識解鎖" + "使用生物辨識解鎖" "忘記 PIN 碼?" "變更 PIN 碼" "允許生物辨識解鎖" @@ -19,5 +20,7 @@ "請輸入相同的 PIN 碼兩次" "PIN 碼不一樣" "您即將登出" + "使用生物辨識" + "使用 PIN 碼" "正在登出…" diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricUnlockManager.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricUnlockManager.kt index d26a90d305..c95752a6ec 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricUnlockManager.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricUnlockManager.kt @@ -20,7 +20,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember class FakeBiometricUnlockManager : BiometricUnlockManager { - override var isDeviceSecured: Boolean = true override var hasAvailableAuthenticator: Boolean = false diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerTest.kt index d3af36d820..194d3e41c9 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerTest.kt @@ -25,7 +25,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class DefaultPinCodeManagerTest { - private val lockScreenStore = InMemoryLockScreenStore() private val secretKeyRepository = SimpleSecretKeyRepository() private val encryptionDecryptionService = AESEncryptionDecryptionService() diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntryTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntryTest.kt index 38abb2d295..9276ce754f 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntryTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntryTest.kt @@ -20,7 +20,6 @@ import com.google.common.truth.Truth.assertThat import org.junit.Test class PinEntryTest { - @Test fun `when using fillWith with empty string ensure pin is empty`() { val pinEntry = PinEntry.createEmpty(4) diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/storage/InMemoryLockScreenStore.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/storage/InMemoryLockScreenStore.kt index 8f622acfe4..b888642e9b 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/storage/InMemoryLockScreenStore.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/storage/InMemoryLockScreenStore.kt @@ -23,7 +23,6 @@ import kotlinx.coroutines.flow.MutableStateFlow private const val DEFAULT_REMAINING_ATTEMPTS = 3 class InMemoryLockScreenStore : LockScreenStore { - private val hasPinCode = MutableStateFlow(false) private var pinCode: String? = null set(value) { diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt index bd99c09bbe..c3358c471c 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt @@ -32,7 +32,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class LockScreenSettingsPresenterTest { - @Test fun `present - remove pin flow`() = runTest { val presenter = createLockScreenSettingsPresenter(this) diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenterTest.kt index 3db9d246f9..ab4f99e5d7 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenterTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenterTest.kt @@ -27,7 +27,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class SetupBiometricPresenterTest { - @Test fun `present - allow flow`() = runTest { val lockScreenStore = InMemoryLockScreenStore() diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt index 7ec73116c2..36774d9ef4 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt @@ -37,7 +37,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class SetupPinPresenterTest { - private val blacklistedPin = "1234" private val halfCompletePin = "12" private val completePin = "1235" @@ -116,7 +115,7 @@ class SetupPinPresenterTest { } } - private fun SetupPinState.onPinEntryChanged(pinEntry: String){ + private fun SetupPinState.onPinEntryChanged(pinEntry: String) { eventSink(SetupPinEvents.OnPinEntryChanged(pinEntry, isConfirmationStep)) } diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt index 87d673066d..c81f4f773d 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt @@ -28,7 +28,7 @@ import io.element.android.features.lockscreen.impl.pin.PinCodeManager import io.element.android.features.lockscreen.impl.pin.model.PinEntry import io.element.android.features.lockscreen.impl.pin.model.assertText import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.tests.testutils.awaitLastSequentialItem import io.element.android.tests.testutils.consumeItemsUntilPredicate @@ -37,7 +37,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class PinUnlockPresenterTest { - private val halfCompletePin = "12" private val completePin = "1235" @@ -48,15 +47,15 @@ class PinUnlockPresenterTest { presenter.present() }.test { awaitItem().also { state -> - assertThat(state.pinEntry).isInstanceOf(Async.Uninitialized::class.java) + assertThat(state.pinEntry).isInstanceOf(AsyncData.Uninitialized::class.java) assertThat(state.showWrongPinTitle).isFalse() assertThat(state.showSignOutPrompt).isFalse() assertThat(state.isUnlocked).isFalse() - assertThat(state.signOutAction).isInstanceOf(Async.Uninitialized::class.java) - assertThat(state.remainingAttempts).isInstanceOf(Async.Uninitialized::class.java) + assertThat(state.signOutAction).isInstanceOf(AsyncData.Uninitialized::class.java) + assertThat(state.remainingAttempts).isInstanceOf(AsyncData.Uninitialized::class.java) } consumeItemsUntilPredicate { - it.pinEntry is Async.Success && it.remainingAttempts is Async.Success + it.pinEntry is AsyncData.Success && it.remainingAttempts is AsyncData.Success }.last().also { state -> state.eventSink(PinUnlockEvents.OnPinKeypadPressed(PinKeypadModel.Number('1'))) state.eventSink(PinUnlockEvents.OnPinKeypadPressed(PinKeypadModel.Number('2'))) @@ -83,7 +82,7 @@ class PinUnlockPresenterTest { presenter.present() }.test { val initialState = consumeItemsUntilPredicate { - it.pinEntry is Async.Success && it.remainingAttempts is Async.Success + it.pinEntry is AsyncData.Success && it.remainingAttempts is AsyncData.Success }.last() val numberOfAttempts = initialState.remainingAttempts.dataOrNull() ?: 0 repeat(numberOfAttempts) { @@ -107,7 +106,7 @@ class PinUnlockPresenterTest { presenter.present() }.test { consumeItemsUntilPredicate { - it.pinEntry is Async.Success && it.remainingAttempts is Async.Success + it.pinEntry is AsyncData.Success && it.remainingAttempts is AsyncData.Success }.last().also { state -> state.eventSink(PinUnlockEvents.OnForgetPin) } @@ -125,12 +124,12 @@ class PinUnlockPresenterTest { state.eventSink(PinUnlockEvents.SignOut) } consumeItemsUntilPredicate { state -> - state.signOutAction is Async.Success + state.signOutAction is AsyncData.Success } } } - private fun Async.assertText(text: String) { + private fun AsyncData.assertText(text: String) { dataOrNull()?.assertText(text) } diff --git a/features/lockscreen/test/src/main/kotlin/io/element/android/features/lockscreen/test/FakeLockScreenService.kt b/features/lockscreen/test/src/main/kotlin/io/element/android/features/lockscreen/test/FakeLockScreenService.kt index 11dc4ee20f..0e75b401a2 100644 --- a/features/lockscreen/test/src/main/kotlin/io/element/android/features/lockscreen/test/FakeLockScreenService.kt +++ b/features/lockscreen/test/src/main/kotlin/io/element/android/features/lockscreen/test/FakeLockScreenService.kt @@ -24,7 +24,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map class FakeLockScreenService : LockScreenService { - private var isPinSetup = MutableStateFlow(false) private val _lockState: MutableStateFlow = MutableStateFlow(LockScreenLockState.Locked) override val lockState: StateFlow = _lockState diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt index a4290825fb..633fc43b5c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt @@ -31,7 +31,6 @@ class DefaultLoginEntryPoint @Inject constructor() : LoginEntryPoint { val plugins = ArrayList() return object : LoginEntryPoint.NodeBuilder { - override fun params(params: LoginEntryPoint.Params): LoginEntryPoint.NodeBuilder { plugins += LoginFlowNode.Inputs(isAccountCreation = params.isAccountCreation) return this diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt index ea541285df..3fb4985c8d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt @@ -25,8 +25,7 @@ import kotlinx.coroutines.flow.asStateFlow import javax.inject.Inject @SingleIn(AppScope::class) -class AccountProviderDataSource @Inject constructor( -) { +class AccountProviderDataSource @Inject constructor() { private val accountProvider: MutableStateFlow = MutableStateFlow( defaultAccountProvider ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt index 7184b2503c..6780d75774 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt @@ -50,9 +50,11 @@ fun AccountProviderView( modifier: Modifier = Modifier, onClick: () -> Unit, ) { - Column(modifier = modifier - .fillMaxWidth() - .clickable { onClick() }) { + Column( + modifier = modifier + .fillMaxWidth() + .clickable { onClick() } + ) { HorizontalDivider() Column( modifier = Modifier diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt index 726e241253..685c04b41f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt @@ -24,7 +24,7 @@ import androidx.compose.runtime.rememberCoroutineScope import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.error.ChangeServerError -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService @@ -36,19 +36,18 @@ class ChangeServerPresenter @Inject constructor( private val authenticationService: MatrixAuthenticationService, private val accountProviderDataSource: AccountProviderDataSource, ) : Presenter { - @Composable override fun present(): ChangeServerState { val localCoroutineScope = rememberCoroutineScope() - val changeServerAction: MutableState> = remember { - mutableStateOf(Async.Uninitialized) + val changeServerAction: MutableState> = remember { + mutableStateOf(AsyncData.Uninitialized) } fun handleEvents(event: ChangeServerEvents) { when (event) { is ChangeServerEvents.ChangeServer -> localCoroutineScope.changeServer(event.accountProvider, changeServerAction) - ChangeServerEvents.ClearError -> changeServerAction.value = Async.Uninitialized + ChangeServerEvents.ClearError -> changeServerAction.value = AsyncData.Uninitialized } } @@ -60,7 +59,7 @@ class ChangeServerPresenter @Inject constructor( private fun CoroutineScope.changeServer( data: AccountProvider, - changeServerAction: MutableState>, + changeServerAction: MutableState>, ) = launch { suspend { authenticationService.setHomeserver(data.url).map { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt index e49fa1f2fe..c5c8ba5417 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt @@ -16,9 +16,9 @@ package io.element.android.features.login.impl.changeserver -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData data class ChangeServerState( - val changeServerAction: Async, + val changeServerAction: AsyncData, val eventSink: (ChangeServerEvents) -> Unit ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt index 90c2bff455..014c3352ce 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt @@ -17,7 +17,7 @@ package io.element.android.features.login.impl.changeserver import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData open class ChangeServerStateProvider : PreviewParameterProvider { override val values: Sequence @@ -27,6 +27,6 @@ open class ChangeServerStateProvider : PreviewParameterProvider { + is AsyncData.Failure -> { when (val error = state.changeServerAction.error) { is ChangeServerError.Error -> { ErrorDialog( @@ -54,17 +54,19 @@ fun ChangeServerView( onLearnMoreClicked = { onLearnMoreClicked() eventSink.invoke(ChangeServerEvents.ClearError) - }, onDismiss = { - eventSink.invoke(ChangeServerEvents.ClearError) - }) + }, + onDismiss = { + eventSink.invoke(ChangeServerEvents.ClearError) + } + ) } } } - is Async.Loading -> ProgressDialog() - is Async.Success -> LaunchedEffect(state.changeServerAction) { + is AsyncData.Loading -> ProgressDialog() + is AsyncData.Success -> LaunchedEffect(state.changeServerAction) { onDone() } - Async.Uninitialized -> Unit + AsyncData.Uninitialized -> Unit } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/OidcUrlParser.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/OidcUrlParser.kt index 487df70253..8c8c895acd 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/OidcUrlParser.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/OidcUrlParser.kt @@ -25,16 +25,16 @@ import javax.inject.Inject * TODO Find documentation about the format. */ class OidcUrlParser @Inject constructor() { - - // When user press button "Cancel", we get the url: - // `io.element:/callback?error=access_denied&state=IFF1UETGye2ZA8pO` - // On success, we get: - // `io.element:/callback?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB` /** * Return a OidcAction, or null if the url is not a OidcUrl. + * Note: + * When user press button "Cancel", we get the url: + * `io.element:/callback?error=access_denied&state=IFF1UETGye2ZA8pO` + * On success, we get: + * `io.element:/callback?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB` */ fun parse(url: String): OidcAction? { - if (url.startsWith(OidcConfig.redirectUri).not()) return null + if (url.startsWith(OidcConfig.REDIRECT_URI).not()) return null if (url.contains("error=access_denied")) return OidcAction.GoBack if (url.contains("code=")) return OidcAction.Success(url) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcEvents.kt index ae0a912ba6..9d2f945e25 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcEvents.kt @@ -20,6 +20,6 @@ import io.element.android.features.login.api.oidc.OidcAction sealed interface OidcEvents { data object Cancel : OidcEvents - data class OidcActionEvent(val oidcAction: OidcAction): OidcEvents + data class OidcActionEvent(val oidcAction: OidcAction) : OidcEvents data object ClearError : OidcEvents } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcNode.kt index dd16b5e57b..5cd7cf0c3b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcNode.kt @@ -35,7 +35,6 @@ class OidcNode @AssistedInject constructor( @Assisted plugins: List, presenterFactory: OidcPresenter.Factory, ) : Node(buildContext, plugins = plugins) { - data class Inputs( val oidcDetails: OidcDetails, ) : NodeInputs diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcPresenter.kt index 66926b3734..3f10ba256a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcPresenter.kt @@ -26,7 +26,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.element.android.features.login.api.oidc.OidcAction -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.OidcDetails @@ -36,7 +36,6 @@ class OidcPresenter @AssistedInject constructor( @Assisted private val oidcDetails: OidcDetails, private val authenticationService: MatrixAuthenticationService, ) : Presenter { - @AssistedFactory interface Factory { fun create(oidcDetails: OidcDetails): OidcPresenter @@ -44,33 +43,33 @@ class OidcPresenter @AssistedInject constructor( @Composable override fun present(): OidcState { - var requestState: Async by remember { - mutableStateOf(Async.Uninitialized) + var requestState: AsyncAction by remember { + mutableStateOf(AsyncAction.Uninitialized) } val localCoroutineScope = rememberCoroutineScope() fun handleCancel() { - requestState = Async.Loading() + requestState = AsyncAction.Loading localCoroutineScope.launch { authenticationService.cancelOidcLogin() .fold( onSuccess = { // Then go back - requestState = Async.Success(Unit) + requestState = AsyncAction.Success(Unit) }, onFailure = { - requestState = Async.Failure(it) + requestState = AsyncAction.Failure(it) } ) } } fun handleSuccess(url: String) { - requestState = Async.Loading() + requestState = AsyncAction.Loading localCoroutineScope.launch { authenticationService.loginWithOidc(url) .onFailure { - requestState = Async.Failure(it) + requestState = AsyncAction.Failure(it) } // On success, the node tree will be updated, there is nothing to do } @@ -87,7 +86,7 @@ class OidcPresenter @AssistedInject constructor( when (event) { OidcEvents.Cancel -> handleCancel() is OidcEvents.OidcActionEvent -> handleAction(event.oidcAction) - OidcEvents.ClearError -> requestState = Async.Uninitialized + OidcEvents.ClearError -> requestState = AsyncAction.Uninitialized } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcState.kt index fc9507a89d..dc4f26d51e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcState.kt @@ -16,11 +16,11 @@ package io.element.android.features.login.impl.oidc.webview -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.auth.OidcDetails data class OidcState( val oidcDetails: OidcDetails, - val requestState: Async, + val requestState: AsyncAction, val eventSink: (OidcEvents) -> Unit ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcStateProvider.kt index 80878cf8f8..5d44657462 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcStateProvider.kt @@ -17,20 +17,20 @@ package io.element.android.features.login.impl.oidc.webview import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.auth.OidcDetails open class OidcStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aOidcState(), - aOidcState().copy(requestState = Async.Loading()), + aOidcState().copy(requestState = AsyncAction.Loading), ) } fun aOidcState() = OidcState( oidcDetails = aOidcDetails(), - requestState = Async.Uninitialized, + requestState = AsyncAction.Uninitialized, eventSink = {} ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcView.kt index 66e3b31496..fb86ae18a5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcView.kt @@ -30,7 +30,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.viewinterop.AndroidView import io.element.android.features.login.impl.oidc.OidcUrlParser import io.element.android.libraries.core.bool.orFalse -import io.element.android.libraries.designsystem.components.async.AsyncView +import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -76,7 +76,7 @@ fun OidcView( } ) - AsyncView( + AsyncActionView( async = state.requestState, onSuccess = { onNavigateBack() }, onErrorDismiss = { state.eventSink(OidcEvents.ClearError) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt index b1ccd8e684..2504d9675e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt @@ -38,7 +38,6 @@ class HomeserverResolver @Inject constructor( private val dispatchers: CoroutineDispatchers, private val wellknownRequest: WellknownRequest, ) { - suspend fun resolve(userInput: String): Flow> = flow { val flowContext = currentCoroutineContext() val trimmedUserInput = userInput.trim() @@ -90,9 +89,9 @@ class HomeserverResolver @Inject constructor( if (data.contains(".")) { // TLD detected? } else { - add("${data}.org") - add("${data}.com") - add("${data}.io") + add("$data.org") + add("$data.com") + add("$data.io") } // Always try what the user has entered add(data) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnown.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnown.kt index 63b6e7d189..44a3d7858e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnown.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnown.kt @@ -41,10 +41,8 @@ import kotlinx.serialization.Serializable data class WellKnown( @SerialName("m.homeserver") val homeServer: WellKnownBaseConfig? = null, - @SerialName("m.identity_server") val identityServer: WellKnownBaseConfig? = null, - @SerialName("org.matrix.msc3575.proxy") val slidingSyncProxy: WellKnownSlidingSyncConfig? = null, ) { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownBaseConfig.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownBaseConfig.kt index 87b86736fa..e7eb1d983f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownBaseConfig.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownBaseConfig.kt @@ -30,6 +30,6 @@ import kotlinx.serialization.Serializable */ @Serializable data class WellKnownBaseConfig( - @SerialName("base_url") - val baseURL: String? = null + @SerialName("base_url") + val baseURL: String? = null ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownSlidingSyncConfig.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownSlidingSyncConfig.kt index 98c712d9ac..063dc6f7b4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownSlidingSyncConfig.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownSlidingSyncConfig.kt @@ -21,6 +21,6 @@ import kotlinx.serialization.Serializable @Serializable data class WellKnownSlidingSyncConfig( - @SerialName("url") - val url: String? = null, + @SerialName("url") + val url: String? = null, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderNode.kt index 45bf4489b5..66025a2f1e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderNode.kt @@ -35,7 +35,6 @@ class ChangeAccountProviderNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: ChangeAccountProviderPresenter, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun onDone() fun onOtherClicked() diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenter.kt index 96fc115cfa..54ed4c65c0 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenter.kt @@ -26,7 +26,6 @@ import javax.inject.Inject class ChangeAccountProviderPresenter @Inject constructor( private val changeServerPresenter: ChangeServerPresenter, ) : Presenter { - @Composable override fun present(): ChangeAccountProviderState { val changeServerState = changeServerPresenter.present() diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt index 8f5292b5a6..5daf4e8f1c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt @@ -14,13 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) +@file:OptIn(ExperimentalMaterial3Api::class) package io.element.android.features.login.impl.screens.changeaccountprovider import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt index 7cef986013..8ba488d9f4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt @@ -38,7 +38,6 @@ class ConfirmAccountProviderNode @AssistedInject constructor( @Assisted plugins: List, presenterFactory: ConfirmAccountProviderPresenter.Factory, ) : Node(buildContext, plugins = plugins) { - data class Inputs( val isAccountCreation: Boolean, ) : NodeInputs diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt index 21896e4408..27aec75739 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt @@ -32,7 +32,7 @@ import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService @@ -46,7 +46,6 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( private val defaultOidcActionFlow: DefaultOidcActionFlow, private val defaultLoginUserStory: DefaultLoginUserStory, ) : Presenter { - data class Params( val isAccountCreation: Boolean, ) @@ -61,8 +60,8 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( val accountProvider by accountProviderDataSource.flow().collectAsState() val localCoroutineScope = rememberCoroutineScope() - val loginFlowAction: MutableState> = remember { - mutableStateOf(Async.Uninitialized) + val loginFlowAction: MutableState> = remember { + mutableStateOf(AsyncData.Uninitialized) } LaunchedEffect(Unit) { @@ -78,7 +77,7 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( ConfirmAccountProviderEvents.Continue -> { localCoroutineScope.submit(accountProvider.url, loginFlowAction) } - ConfirmAccountProviderEvents.ClearError -> loginFlowAction.value = Async.Uninitialized + ConfirmAccountProviderEvents.ClearError -> loginFlowAction.value = AsyncData.Uninitialized } } @@ -92,7 +91,7 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( private fun CoroutineScope.submit( homeserverUrl: String, - loginFlowAction: MutableState>, + loginFlowAction: MutableState>, ) = launch { suspend { authenticationService.setHomeserver(homeserverUrl).map { @@ -111,17 +110,17 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( private suspend fun onOidcAction( oidcAction: OidcAction, - loginFlowAction: MutableState>, + loginFlowAction: MutableState>, ) { - loginFlowAction.value = Async.Loading() + loginFlowAction.value = AsyncData.Loading() when (oidcAction) { OidcAction.GoBack -> { authenticationService.cancelOidcLogin() .onSuccess { - loginFlowAction.value = Async.Uninitialized + loginFlowAction.value = AsyncData.Uninitialized } .onFailure { failure -> - loginFlowAction.value = Async.Failure(failure) + loginFlowAction.value = AsyncData.Failure(failure) } } is OidcAction.Success -> { @@ -130,7 +129,7 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( defaultLoginUserStory.setLoginFlowIsDone(true) } .onFailure { failure -> - loginFlowAction.value = Async.Failure(failure) + loginFlowAction.value = AsyncData.Failure(failure) } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt index 2fcb57af94..2074b7b5ce 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt @@ -17,17 +17,17 @@ package io.element.android.features.login.impl.screens.confirmaccountprovider import io.element.android.features.login.impl.accountprovider.AccountProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.auth.OidcDetails // Do not use default value, so no member get forgotten in the presenters. data class ConfirmAccountProviderState( val accountProvider: AccountProvider, val isAccountCreation: Boolean, - val loginFlow: Async, + val loginFlow: AsyncData, val eventSink: (ConfirmAccountProviderEvents) -> Unit ) { - val submitEnabled: Boolean get() = accountProvider.url.isNotEmpty() && (loginFlow is Async.Uninitialized || loginFlow is Async.Loading) + val submitEnabled: Boolean get() = accountProvider.url.isNotEmpty() && (loginFlow is AsyncData.Uninitialized || loginFlow is AsyncData.Loading) } sealed interface LoginFlow { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt index d5f98f5716..3f7bf89278 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt @@ -18,7 +18,7 @@ package io.element.android.features.login.impl.screens.confirmaccountprovider import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.login.impl.accountprovider.anAccountProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData open class ConfirmAccountProviderStateProvider : PreviewParameterProvider { override val values: Sequence @@ -31,6 +31,6 @@ open class ConfirmAccountProviderStateProvider : PreviewParameterProvider { + is AsyncData.Failure -> { when (val error = state.loginFlow.error) { is ChangeServerError.Error -> { ErrorDialog( @@ -127,14 +127,14 @@ fun ConfirmAccountProviderView( } } } - is Async.Loading -> Unit // The Continue button shows the loading state - is Async.Success -> { + is AsyncData.Loading -> Unit // The Continue button shows the loading state + is AsyncData.Success -> { when (val loginFlowState = state.loginFlow.data) { is LoginFlow.OidcFlow -> onOidcDetails(loginFlowState.oidcDetails) LoginFlow.PasswordLogin -> onLoginPasswordNeeded() } } - Async.Uninitialized -> Unit + AsyncData.Uninitialized -> Unit } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordNode.kt index cb5542d2eb..f444c7a7dc 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordNode.kt @@ -33,7 +33,6 @@ class LoginPasswordNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: LoginPasswordPresenter, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun onWaitListError(loginFormState: LoginFormState) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt index b2ea5fb985..dd109d5ec4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt @@ -26,7 +26,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.core.SessionId @@ -39,12 +39,11 @@ class LoginPasswordPresenter @Inject constructor( private val accountProviderDataSource: AccountProviderDataSource, private val defaultLoginUserStory: DefaultLoginUserStory, ) : Presenter { - @Composable override fun present(): LoginPasswordState { val localCoroutineScope = rememberCoroutineScope() - val loginAction: MutableState> = remember { - mutableStateOf(Async.Uninitialized) + val loginAction: MutableState> = remember { + mutableStateOf(AsyncData.Uninitialized) } val formState = rememberSaveable { @@ -63,7 +62,7 @@ class LoginPasswordPresenter @Inject constructor( LoginPasswordEvents.Submit -> { localCoroutineScope.submit(formState.value, loginAction) } - LoginPasswordEvents.ClearError -> loginAction.value = Async.Uninitialized + LoginPasswordEvents.ClearError -> loginAction.value = AsyncData.Uninitialized } } @@ -75,16 +74,16 @@ class LoginPasswordPresenter @Inject constructor( ) } - private fun CoroutineScope.submit(formState: LoginFormState, loggedInState: MutableState>) = launch { - loggedInState.value = Async.Loading() + private fun CoroutineScope.submit(formState: LoginFormState, loggedInState: MutableState>) = launch { + loggedInState.value = AsyncData.Loading() authenticationService.login(formState.login.trim(), formState.password) .onSuccess { sessionId -> // We will not navigate to the WaitList screen, so the login user story is done defaultLoginUserStory.setLoginFlowIsDone(true) - loggedInState.value = Async.Success(sessionId) + loggedInState.value = AsyncData.Success(sessionId) } .onFailure { failure -> - loggedInState.value = Async.Failure(failure) + loggedInState.value = AsyncData.Failure(failure) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt index cf4e0d8ee5..503e73706f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt @@ -18,18 +18,18 @@ package io.element.android.features.login.impl.screens.loginpassword import android.os.Parcelable import io.element.android.features.login.impl.accountprovider.AccountProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.SessionId import kotlinx.parcelize.Parcelize data class LoginPasswordState( val accountProvider: AccountProvider, val formState: LoginFormState, - val loginAction: Async, + val loginAction: AsyncData, val eventSink: (LoginPasswordEvents) -> Unit ) { val submitEnabled: Boolean - get() = loginAction !is Async.Failure && + get() = loginAction !is AsyncData.Failure && formState.login.isNotEmpty() && formState.password.isNotEmpty() } @@ -39,7 +39,6 @@ data class LoginFormState( val login: String, val password: String ) : Parcelable { - companion object { val Default = LoginFormState("", "") } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStateProvider.kt index b4f5a84691..9b501ac238 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStateProvider.kt @@ -18,22 +18,22 @@ package io.element.android.features.login.impl.screens.loginpassword import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.login.impl.accountprovider.anAccountProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData open class LoginPasswordStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aLoginPasswordState(), // Loading - aLoginPasswordState().copy(loginAction = Async.Loading()), + aLoginPasswordState().copy(loginAction = AsyncData.Loading()), // Error - aLoginPasswordState().copy(loginAction = Async.Failure(Exception("An error occurred"))), + aLoginPasswordState().copy(loginAction = AsyncData.Failure(Exception("An error occurred"))), ) } fun aLoginPasswordState() = LoginPasswordState( accountProvider = anAccountProvider(), formState = LoginFormState.Default, - loginAction = Async.Uninitialized, + loginAction = AsyncData.Uninitialized, eventSink = {} ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt index 303ffdae52..2a6a752154 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt @@ -17,7 +17,6 @@ package io.element.android.features.login.impl.screens.loginpassword import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize @@ -55,7 +54,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.login.impl.R import io.element.android.features.login.impl.error.isWaitListError import io.element.android.features.login.impl.error.loginError -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog @@ -75,7 +74,7 @@ import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.testTag import io.element.android.libraries.ui.strings.CommonStrings -@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun LoginPasswordView( state: LoginPasswordState, @@ -85,7 +84,7 @@ fun LoginPasswordView( ) { val isLoading by remember(state.loginAction) { derivedStateOf { - state.loginAction is Async.Loading + state.loginAction is AsyncData.Loading } } val focusManager = LocalFocusManager.current @@ -149,7 +148,7 @@ fun LoginPasswordView( ) Spacer(modifier = Modifier.height(60.dp)) - if (state.loginAction is Async.Failure) { + if (state.loginAction is AsyncData.Failure) { when { state.loginAction.error.isWaitListError() -> { onWaitListError(state.formState) @@ -221,11 +220,13 @@ private fun LoginForm( Icon(imageVector = CompoundIcons.Close, contentDescription = stringResource(CommonStrings.action_clear)) } } - } else null, + } else { + null + }, ) var passwordVisible by remember { mutableStateOf(false) } - if (state.loginAction is Async.Loading) { + if (state.loginAction is AsyncData.Loading) { // Ensure password is hidden when user submits the form passwordVisible = false } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderNode.kt index 7178a105f6..834d0400da 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderNode.kt @@ -35,7 +35,6 @@ class SearchAccountProviderNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: SearchAccountProviderPresenter, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun onDone() } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt index 1d8271e394..e9f177d62a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt @@ -27,7 +27,7 @@ import androidx.compose.runtime.setValue import io.element.android.features.login.impl.changeserver.ChangeServerPresenter import io.element.android.features.login.impl.resolver.HomeserverData import io.element.android.features.login.impl.resolver.HomeserverResolver -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay @@ -38,7 +38,6 @@ class SearchAccountProviderPresenter @Inject constructor( private val homeserverResolver: HomeserverResolver, private val changeServerPresenter: ChangeServerPresenter, ) : Presenter { - @Composable override fun present(): SearchAccountProviderState { var userInput by rememberSaveable { @@ -46,8 +45,8 @@ class SearchAccountProviderPresenter @Inject constructor( } val changeServerState = changeServerPresenter.present() - val data: MutableState>> = remember { - mutableStateOf(Async.Uninitialized) + val data: MutableState>> = remember { + mutableStateOf(AsyncData.Uninitialized) } LaunchedEffect(userInput) { @@ -70,16 +69,16 @@ class SearchAccountProviderPresenter @Inject constructor( ) } - private fun CoroutineScope.onUserInput(userInput: String, data: MutableState>>) = launch { - data.value = Async.Uninitialized + private fun CoroutineScope.onUserInput(userInput: String, data: MutableState>>) = launch { + data.value = AsyncData.Uninitialized // Debounce delay(300) - data.value = Async.Loading() + data.value = AsyncData.Loading() homeserverResolver.resolve(userInput).collect { - data.value = Async.Success(it) + data.value = AsyncData.Success(it) } - if (data.value !is Async.Success) { - data.value = Async.Uninitialized + if (data.value !is AsyncData.Success) { + data.value = AsyncData.Uninitialized } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderState.kt index 15859afde1..825befa356 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderState.kt @@ -18,12 +18,12 @@ package io.element.android.features.login.impl.screens.searchaccountprovider import io.element.android.features.login.impl.changeserver.ChangeServerState import io.element.android.features.login.impl.resolver.HomeserverData -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData // Do not use default value, so no member get forgotten in the presenters. data class SearchAccountProviderState( val userInput: String, - val userInputResult: Async>, + val userInputResult: AsyncData>, val changeServerState: ChangeServerState, val eventSink: (SearchAccountProviderEvents) -> Unit ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderStateProvider.kt index 50b24b3964..2df65afee4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderStateProvider.kt @@ -20,20 +20,20 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.appconfig.AuthenticationConfig import io.element.android.features.login.impl.changeserver.aChangeServerState import io.element.android.features.login.impl.resolver.HomeserverData -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData open class SearchAccountProviderStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aSearchAccountProviderState(), - aSearchAccountProviderState(userInputResult = Async.Success(aHomeserverDataList())), + aSearchAccountProviderState(userInputResult = AsyncData.Success(aHomeserverDataList())), // Add other state here ) } fun aSearchAccountProviderState( userInput: String = "", - userInputResult: Async> = Async.Uninitialized, + userInputResult: AsyncData> = AsyncData.Uninitialized, ) = SearchAccountProviderState( userInput = userInput, userInputResult = userInputResult, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt index 09e8661b77..2a39680300 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt @@ -55,7 +55,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProviderVie import io.element.android.features.login.impl.changeserver.ChangeServerEvents import io.element.android.features.login.impl.changeserver.ChangeServerView import io.element.android.features.login.impl.resolver.HomeserverData -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.form.textFieldState @@ -144,7 +144,9 @@ fun SearchAccountProviderView( ) } } - } else null, + } else { + null + }, supportingText = { Text(text = stringResource(id = R.string.screen_account_provider_form_notice), color = MaterialTheme.colorScheme.secondary) } @@ -152,10 +154,10 @@ fun SearchAccountProviderView( } when (state.userInputResult) { - is Async.Failure -> { + is AsyncData.Failure -> { // Ignore errors (let the user type more chars) } - is Async.Loading -> { + is AsyncData.Loading -> { item { Box( modifier = Modifier @@ -167,7 +169,7 @@ fun SearchAccountProviderView( } } } - is Async.Success -> { + is AsyncData.Success -> { items(state.userInputResult.data) { homeserverData -> val item = homeserverData.toAccountProvider() AccountProviderView( @@ -178,7 +180,7 @@ fun SearchAccountProviderView( ) } } - Async.Uninitialized -> Unit + AsyncData.Uninitialized -> Unit } item { Spacer(Modifier.height(32.dp)) @@ -199,7 +201,8 @@ private fun HomeserverData.toAccountProvider(): AccountProvider { return AccountProvider( url = homeserverUrl, subtitle = if (isMatrixOrg) stringResource(id = R.string.screen_change_account_provider_matrix_org_subtitle) else null, - isPublic = isMatrixOrg, // There is no need to know for other servers right now + // There is no need to know for other servers right now + isPublic = isMatrixOrg, isMatrixOrg = isMatrixOrg, isValid = isWellknownValid, supportSlidingSync = supportSlidingSync, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListNode.kt index 24b5f271a0..e27d5f5608 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListNode.kt @@ -36,7 +36,6 @@ class WaitListNode @AssistedInject constructor( @Assisted plugins: List, presenterFactory: WaitListPresenter.Factory, ) : Node(buildContext, plugins = plugins) { - data class Inputs(val loginFormState: LoginFormState) : NodeInputs private val inputs: Inputs = inputs() diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt index 039986343b..16549a8741 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt @@ -27,7 +27,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.screens.loginpassword.LoginFormState -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService @@ -42,7 +42,6 @@ class WaitListPresenter @AssistedInject constructor( private val authenticationService: MatrixAuthenticationService, private val defaultLoginUserStory: DefaultLoginUserStory, ) : Presenter { - @AssistedFactory interface Factory { fun create(loginFormState: LoginFormState): WaitListPresenter @@ -55,8 +54,8 @@ class WaitListPresenter @AssistedInject constructor( authenticationService.getHomeserverDetails().value?.url ?: "server" } - val loginAction: MutableState> = remember { - mutableStateOf(Async.Uninitialized) + val loginAction: MutableState> = remember { + mutableStateOf(AsyncData.Uninitialized) } val attemptNumber = remember { mutableIntStateOf(0) } @@ -70,7 +69,7 @@ class WaitListPresenter @AssistedInject constructor( coroutineScope.loginAttempt(formState, loginAction) } } - WaitListEvents.ClearError -> loginAction.value = Async.Uninitialized + WaitListEvents.ClearError -> loginAction.value = AsyncData.Uninitialized WaitListEvents.Continue -> defaultLoginUserStory.setLoginFlowIsDone(true) } } @@ -83,15 +82,15 @@ class WaitListPresenter @AssistedInject constructor( ) } - private fun CoroutineScope.loginAttempt(formState: LoginFormState, loggedInState: MutableState>) = launch { + private fun CoroutineScope.loginAttempt(formState: LoginFormState, loggedInState: MutableState>) = launch { Timber.w("Attempt to login...") - loggedInState.value = Async.Loading() + loggedInState.value = AsyncData.Loading() authenticationService.login(formState.login.trim(), formState.password) .onSuccess { sessionId -> - loggedInState.value = Async.Success(sessionId) + loggedInState.value = AsyncData.Success(sessionId) } .onFailure { failure -> - loggedInState.value = Async.Failure(failure) + loggedInState.value = AsyncData.Failure(failure) } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListState.kt index f50de7e194..fc85f6637a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListState.kt @@ -16,13 +16,13 @@ package io.element.android.features.login.impl.screens.waitlistscreen -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.SessionId // Do not use default value, so no member get forgotten in the presenters. data class WaitListState( val appName: String, val serverName: String, - val loginAction: Async, + val loginAction: AsyncData, val eventSink: (WaitListEvents) -> Unit ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListStateProvider.kt index 94a38fa406..61550a230d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListStateProvider.kt @@ -17,17 +17,17 @@ package io.element.android.features.login.impl.screens.waitlistscreen import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.SessionId open class WaitListStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aWaitListState(loginAction = Async.Uninitialized), - aWaitListState(loginAction = Async.Loading()), - aWaitListState(loginAction = Async.Failure(Throwable("error"))), - aWaitListState(loginAction = Async.Failure(Throwable(message = "IO_ELEMENT_X_WAIT_LIST"))), - aWaitListState(loginAction = Async.Success(SessionId("@alice:element.io"))), + aWaitListState(loginAction = AsyncData.Uninitialized), + aWaitListState(loginAction = AsyncData.Loading()), + aWaitListState(loginAction = AsyncData.Failure(Throwable("error"))), + aWaitListState(loginAction = AsyncData.Failure(Throwable(message = "IO_ELEMENT_X_WAIT_LIST"))), + aWaitListState(loginAction = AsyncData.Success(SessionId("@alice:element.io"))), // Add other state here ) } @@ -35,7 +35,7 @@ open class WaitListStateProvider : PreviewParameterProvider { fun aWaitListState( appName: String = "Element X", serverName: String = "server.org", - loginAction: Async = Async.Uninitialized, + loginAction: AsyncData = AsyncData.Uninitialized, ) = WaitListState( appName = appName, serverName = serverName, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt index 0e40a4827a..e8a3bddc43 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt @@ -33,7 +33,7 @@ import androidx.lifecycle.Lifecycle import io.element.android.features.login.impl.R import io.element.android.features.login.impl.error.isWaitListError import io.element.android.features.login.impl.error.loginError -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.atomic.pages.SunsetPage import io.element.android.libraries.designsystem.components.dialogs.RetryDialog import io.element.android.libraries.designsystem.preview.ElementPreview @@ -89,12 +89,12 @@ private fun WaitListContent( ) { val title = stringResource( when (state.loginAction) { - is Async.Success -> R.string.screen_waitlist_title_success + is AsyncData.Success -> R.string.screen_waitlist_title_success else -> R.string.screen_waitlist_title } ) val subtitle = when (state.loginAction) { - is Async.Success -> stringResource( + is AsyncData.Success -> stringResource( id = R.string.screen_waitlist_message_success, state.appName, ) @@ -122,7 +122,7 @@ private fun OverallContent( modifier: Modifier = Modifier, ) { Box(modifier = modifier.fillMaxSize()) { - if (state.loginAction !is Async.Success) { + if (state.loginAction !is AsyncData.Success) { CompositionLocalProvider(LocalContentColor provides Color.Black) { TextButton( text = stringResource(CommonStrings.action_cancel), @@ -130,7 +130,7 @@ private fun OverallContent( ) } } - if (state.loginAction is Async.Success) { + if (state.loginAction is AsyncData.Success) { Button( text = stringResource(id = CommonStrings.action_continue), onClick = { state.eventSink.invoke(WaitListEvents.Continue) }, @@ -140,7 +140,6 @@ private fun OverallContent( .padding(bottom = 8.dp), ) } - } } diff --git a/features/login/impl/src/main/res/values-es/translations.xml b/features/login/impl/src/main/res/values-es/translations.xml index 6f023f6373..a9020cbd22 100644 --- a/features/login/impl/src/main/res/values-es/translations.xml +++ b/features/login/impl/src/main/res/values-es/translations.xml @@ -1,14 +1,42 @@ + "Cambiar el proveedor de la cuenta" + "Dirección del servidor principal" + "Introduzca un término de búsqueda o una dirección de dominio." + "Busca una empresa, comunidad o servidor privado." + "Encontrar un proveedor de cuenta" + "Aquí es donde se alojarán tus conversaciones — justo como si utilizaras un proveedor de correo electrónico para guardar tus correos electrónicos." + "Estás a punto de iniciar sesión en %s" + "Aquí es donde se alojarán tus conversaciones — justo como si utilizaras un proveedor de correo electrónico para guardar tus correos electrónicos." + "Estás a punto de crear una cuenta en %s" + "Matrix.org es un servidor grande y gratuito en la red pública Matrix para una comunicación segura y descentralizada, administrado por la Fundación Matrix.org." + "Otro" + "Usa un proveedor de cuenta diferente, como tu propio servidor privado o una cuenta de trabajo." + "Cambiar el proveedor de la cuenta" "No hemos podido acceder a este servidor. Comprueba que has introducido correctamente la dirección del servidor. Si la dirección es correcta, ponte en contacto con el administrador del servidor para obtener más ayuda." "Este servidor no soporta sliding sync." "Dirección del homeserver" "Solo puedes conectarte a un servidor que soporte sliding sync. El administrador de tu servidor tendrá que configurarlo. %1$s" "¿Cuál es la dirección de tu servidor?" + "Selecciona tu servidor" "Esta cuenta ha sido desactivada." "Usuario y/o contraseña incorrectos" "Este no es un id de usuario válido. Formato esperado: \'@user:homeserver.org\'" "El servidor seleccionado no admite contraseñas ni inicio de sesión OIDC. Póngase en contacto con su administrador o elija otro homeserver." "Introduce tus datos" "¡Hola de nuevo!" + "Iniciar sesión en %1$s" + "Cambiar el proveedor de la cuenta" + "Un servidor privado para los empleados de Element." + "Matrix es una red abierta para una comunicación segura y descentralizada." + "Aquí es donde se alojarán tus conversaciones — justo como utilizarías un proveedor de correo electrónico para guardar tus correos electrónicos." + "Estás a punto de iniciar sesión en %1$s" + "Estás a punto de crear una cuenta en %1$s" + "Hay una gran demanda para %1$s en %2$s en este momento. Vuelve a la aplicación en unos días e inténtalo de nuevo. + +¡Gracias por tu paciencia!" + "Ya casi has terminado." + "Estás dentro." + "Matrix es una red abierta para una comunicación segura y descentralizada." + "¡Bienvenido a %1$s!" diff --git a/features/login/impl/src/main/res/values-hu/translations.xml b/features/login/impl/src/main/res/values-hu/translations.xml index defe5e41a4..e41ff77592 100644 --- a/features/login/impl/src/main/res/values-hu/translations.xml +++ b/features/login/impl/src/main/res/values-hu/translations.xml @@ -7,7 +7,7 @@ "Fiókszolgáltató keresése" "Itt lesznek a beszélgetései – ahogyan egy e-mail-szolgáltatást is használna a levelei kezeléséhez." "Hamarosan bejelentkezik ide: %s" - "Itt lesznek a beszélgetései – ahogyan egy e-mail-szolgáltatást is használna a levelei kezeléséhez." + "Itt lesznek a beszélgetéseid – ahogyan egy e-mail-szolgáltatást is használnál a leveleid kezeléséhez." "Hamarosan létrehoz egy fiókot itt: %s" "A Matrix.org egy nagy, ingyenes kiszolgáló a nyilvános Matrix-hálózaton, a biztonságos, decentralizált kommunikáció érdekében, amelyet a Matrix.org Alapítvány üzemeltet." "Egyéb" diff --git a/features/login/impl/src/main/res/values-it/translations.xml b/features/login/impl/src/main/res/values-it/translations.xml index 227b85bed7..c3e43e5933 100644 --- a/features/login/impl/src/main/res/values-it/translations.xml +++ b/features/login/impl/src/main/res/values-it/translations.xml @@ -1,14 +1,42 @@ + "Cambia fornitore dell\'account" + "Indirizzo dell\'homeserver" + "Inserisci un termine di ricerca o un indirizzo di dominio." + "Cerca un\' azienda, una comunità o un server privato." + "Trova un fornitore di account" + "Qui è dove vivranno le tue conversazioni - proprio come useresti un fornitore di posta elettronica per conservare le tue email." + "Stai per accedere a %s" + "Qui è dove vivranno le tue conversazioni - proprio come useresti un fornitore di posta elettronica per conservare le tue email." + "Stai per creare un account su %s" + "Matrix.org è un grande server gratuito nella rete pubblica Matrix per una comunicazione sicura e decentralizzata, gestito dalla Fondazione Matrix.org." + "Altro" + "Utilizza un provider di account diverso, ad esempio il tuo server privato o un account di lavoro." + "Cambia fornitore dell\'account" "Non siamo riusciti a raggiungere questo homserver. Verifica di aver inserito correttamente l\'URL del server domestico. Se l\'URL è corretto, contatta l\'amministratore del tuo server domestico per ulteriore assistenza." "Questo server attualmente non supporta la sincronizzazione scorrevole." "URL dell\'homeserver" "Puoi connetterti solo a un server esistente che supporta la sincronizzazione scorrevole. L\'amministratore del tuo server domestico dovrà configurarlo. %1$s" "Qual è l\'indirizzo del tuo server?" + "Seleziona il tuo server" "Questo profilo è stato disattivato." "Nome utente e/o password errati" "Questo non è un identificatore utente valido. Formato previsto: \'@user:homeserver.org\'" "L\'homeserver selezionato non supporta la password o l\'accesso OIDC. Contatta il tuo amministratore o scegli un altro homeserver." "Inserisci i tuoi dati" "Bentornato!" + "Accedi a %1$s" + "Cambia fornitore dell\'account" + "Un server privato per i dipendenti di Element." + "Matrix è una rete aperta per comunicazioni sicure e decentralizzate." + "Qui è dove vivranno le tue conversazioni — proprio come useresti un fornitore di posta elettronica per conservare le tue email." + "Stai per accedere a %1$s" + "Stai per creare un account su %1$s" + "Al momento c\'è una grande richiesta per %1$s su %2$s. Torna a visitare l\'app tra qualche giorno e riprova. + +Grazie per la pazienza!" + "Ci sei quasi." + "Sei dentro." + "Matrix è una rete aperta per comunicazioni sicure e decentralizzate." + "Benvenuti in %1$s!" diff --git a/features/login/impl/src/main/res/values-zh-rTW/translations.xml b/features/login/impl/src/main/res/values-zh-rTW/translations.xml index 1c3e524b2b..7fd944aac2 100644 --- a/features/login/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/login/impl/src/main/res/values-zh-rTW/translations.xml @@ -15,6 +15,8 @@ "更改帳號提供者" "此伺服器目前不支援滑動同步(sliding sync)。" "家伺服器 URL" + "您的伺服器地址?" + "選擇您的伺服器" "這個帳號已被停用。" "不正確的使用者名稱或密碼" "輸入您的詳細資料" diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt index 44c36128e2..f8518f6033 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt @@ -22,7 +22,7 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService @@ -32,7 +32,6 @@ import org.junit.Rule import org.junit.Test class ChangeServerPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -46,7 +45,7 @@ class ChangeServerPresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.changeServerAction).isEqualTo(Async.Uninitialized) + assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized) } } @@ -61,13 +60,13 @@ class ChangeServerPresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.changeServerAction).isEqualTo(Async.Uninitialized) + assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized) authenticationService.givenHomeserver(A_HOMESERVER) initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) val loadingState = awaitItem() - assertThat(loadingState.changeServerAction).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java) val successState = awaitItem() - assertThat(successState.changeServerAction).isEqualTo(Async.Success(Unit)) + assertThat(successState.changeServerAction).isEqualTo(AsyncData.Success(Unit)) } } @@ -82,16 +81,16 @@ class ChangeServerPresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.changeServerAction).isEqualTo(Async.Uninitialized) + assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized) initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) val loadingState = awaitItem() - assertThat(loadingState.changeServerAction).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java) val failureState = awaitItem() - assertThat(failureState.changeServerAction).isInstanceOf(Async.Failure::class.java) + assertThat(failureState.changeServerAction).isInstanceOf(AsyncData.Failure::class.java) // Clear error failureState.eventSink.invoke(ChangeServerEvents.ClearError) val finalState = awaitItem() - assertThat(finalState.changeServerAction).isEqualTo(Async.Uninitialized) + assertThat(finalState.changeServerAction).isEqualTo(AsyncData.Uninitialized) } } } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/error/ErrorFormatterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/error/ErrorFormatterTest.kt index 17c6ce3e4a..1c3e9782cd 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/error/ErrorFormatterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/error/ErrorFormatterTest.kt @@ -23,7 +23,6 @@ import io.element.android.libraries.ui.strings.CommonStrings import org.junit.Test class ErrorFormatterTest { - // region loginError @Test fun `loginError - invalid unknown error returns unknown error message`() { diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/oidc/OidcUrlParserTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/oidc/OidcUrlParserTest.kt index a0275f8f47..6f63673b23 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/oidc/OidcUrlParserTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/oidc/OidcUrlParserTest.kt @@ -38,21 +38,21 @@ class OidcUrlParserTest { @Test fun `test cancel url`() { val sut = OidcUrlParser() - val aCancelUrl = OidcConfig.redirectUri + "?error=access_denied&state=IFF1UETGye2ZA8pO" + val aCancelUrl = OidcConfig.REDIRECT_URI + "?error=access_denied&state=IFF1UETGye2ZA8pO" assertThat(sut.parse(aCancelUrl)).isEqualTo(OidcAction.GoBack) } @Test fun `test success url`() { val sut = OidcUrlParser() - val aSuccessUrl = OidcConfig.redirectUri + "?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB" + val aSuccessUrl = OidcConfig.REDIRECT_URI + "?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB" assertThat(sut.parse(aSuccessUrl)).isEqualTo(OidcAction.Success(aSuccessUrl)) } @Test fun `test unknown url`() { val sut = OidcUrlParser() - val anUnknownUrl = OidcConfig.redirectUri + "?state=IFF1UETGye2ZA8pO&goat=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB" + val anUnknownUrl = OidcConfig.REDIRECT_URI + "?state=IFF1UETGye2ZA8pO&goat=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB" Assert.assertThrows(IllegalStateException::class.java) { assertThat(sut.parse(anUnknownUrl)) } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/oidc/webview/OidcPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/oidc/webview/OidcPresenterTest.kt index 70d6da7783..34b497866a 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/oidc/webview/OidcPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/oidc/webview/OidcPresenterTest.kt @@ -23,7 +23,7 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.login.api.oidc.OidcAction -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.auth.A_OIDC_DATA import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService @@ -34,7 +34,6 @@ import org.junit.Rule import org.junit.Test class OidcPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -49,7 +48,7 @@ class OidcPresenterTest { }.test { val initialState = awaitItem() assertThat(initialState.oidcDetails).isEqualTo(A_OIDC_DATA) - assertThat(initialState.requestState).isEqualTo(Async.Uninitialized) + assertThat(initialState.requestState).isEqualTo(AsyncAction.Uninitialized) } } @@ -65,9 +64,9 @@ class OidcPresenterTest { val initialState = awaitItem() initialState.eventSink.invoke(OidcEvents.Cancel) val loadingState = awaitItem() - assertThat(loadingState.requestState).isEqualTo(Async.Loading()) + assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading) val finalState = awaitItem() - assertThat(finalState.requestState).isEqualTo(Async.Success(Unit)) + assertThat(finalState.requestState).isEqualTo(AsyncAction.Success(Unit)) } } @@ -85,9 +84,9 @@ class OidcPresenterTest { val initialState = awaitItem() initialState.eventSink.invoke(OidcEvents.Cancel) val loadingState = awaitItem() - assertThat(loadingState.requestState).isEqualTo(Async.Loading()) + assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading) val finalState = awaitItem() - assertThat(finalState.requestState).isEqualTo(Async.Failure(A_THROWABLE)) + assertThat(finalState.requestState).isEqualTo(AsyncAction.Failure(A_THROWABLE)) // Note: in real life I do not think this can happen, and the app should not block the user. } } @@ -104,9 +103,9 @@ class OidcPresenterTest { val initialState = awaitItem() initialState.eventSink.invoke(OidcEvents.OidcActionEvent(OidcAction.GoBack)) val loadingState = awaitItem() - assertThat(loadingState.requestState).isEqualTo(Async.Loading()) + assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading) val finalState = awaitItem() - assertThat(finalState.requestState).isEqualTo(Async.Success(Unit)) + assertThat(finalState.requestState).isEqualTo(AsyncAction.Success(Unit)) } } @@ -122,7 +121,7 @@ class OidcPresenterTest { val initialState = awaitItem() initialState.eventSink.invoke(OidcEvents.OidcActionEvent(OidcAction.Success("A_URL"))) val loadingState = awaitItem() - assertThat(loadingState.requestState).isEqualTo(Async.Loading()) + assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading) // In this case, no success, the session is created and the node get destroyed. } } @@ -141,12 +140,12 @@ class OidcPresenterTest { val initialState = awaitItem() initialState.eventSink.invoke(OidcEvents.OidcActionEvent(OidcAction.Success("A_URL"))) val loadingState = awaitItem() - assertThat(loadingState.requestState).isEqualTo(Async.Loading()) + assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading) val errorState = awaitItem() - assertThat(errorState.requestState).isEqualTo(Async.Failure(A_THROWABLE)) + assertThat(errorState.requestState).isEqualTo(AsyncAction.Failure(A_THROWABLE)) errorState.eventSink.invoke(OidcEvents.ClearError) val finalState = awaitItem() - assertThat(finalState.requestState).isEqualTo(Async.Uninitialized) + assertThat(finalState.requestState).isEqualTo(AsyncAction.Uninitialized) } } } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenterTest.kt index 4ecbe10b4e..04680ede17 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenterTest.kt @@ -30,7 +30,6 @@ import org.junit.Rule import org.junit.Test class ChangeAccountProviderPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt index f348839c97..57f18d7eb4 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt @@ -25,7 +25,7 @@ import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow import io.element.android.features.login.impl.util.defaultAccountProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_HOMESERVER_OIDC @@ -38,7 +38,6 @@ import org.junit.Rule import org.junit.Test class ConfirmAccountProviderPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -52,7 +51,7 @@ class ConfirmAccountProviderPresenterTest { assertThat(initialState.isAccountCreation).isFalse() assertThat(initialState.submitEnabled).isTrue() assertThat(initialState.accountProvider).isEqualTo(defaultAccountProvider) - assertThat(initialState.loginFlow).isEqualTo(Async.Uninitialized) + assertThat(initialState.loginFlow).isEqualTo(AsyncData.Uninitialized) } } @@ -70,10 +69,10 @@ class ConfirmAccountProviderPresenterTest { initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) val loadingState = awaitItem() assertThat(loadingState.submitEnabled).isTrue() - assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java) val successState = awaitItem() assertThat(successState.submitEnabled).isFalse() - assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java) assertThat(successState.loginFlow.dataOrNull()).isEqualTo(LoginFlow.PasswordLogin) } } @@ -92,10 +91,10 @@ class ConfirmAccountProviderPresenterTest { initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) val loadingState = awaitItem() assertThat(loadingState.submitEnabled).isTrue() - assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java) val successState = awaitItem() assertThat(successState.submitEnabled).isFalse() - assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java) assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java) } } @@ -116,15 +115,15 @@ class ConfirmAccountProviderPresenterTest { initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) val loadingState = awaitItem() assertThat(loadingState.submitEnabled).isTrue() - assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java) val successState = awaitItem() assertThat(successState.submitEnabled).isFalse() - assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java) assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java) authenticationService.givenOidcCancelError(A_THROWABLE) defaultOidcActionFlow.post(OidcAction.GoBack) val cancelFailureState = awaitItem() - assertThat(cancelFailureState.loginFlow).isInstanceOf(Async.Failure::class.java) + assertThat(cancelFailureState.loginFlow).isInstanceOf(AsyncData.Failure::class.java) } } @@ -144,14 +143,14 @@ class ConfirmAccountProviderPresenterTest { initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) val loadingState = awaitItem() assertThat(loadingState.submitEnabled).isTrue() - assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java) val successState = awaitItem() assertThat(successState.submitEnabled).isFalse() - assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java) assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java) defaultOidcActionFlow.post(OidcAction.GoBack) val cancelFinalState = awaitItem() - assertThat(cancelFinalState.loginFlow).isInstanceOf(Async.Uninitialized::class.java) + assertThat(cancelFinalState.loginFlow).isInstanceOf(AsyncData.Uninitialized::class.java) } } @@ -171,17 +170,17 @@ class ConfirmAccountProviderPresenterTest { initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) val loadingState = awaitItem() assertThat(loadingState.submitEnabled).isTrue() - assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java) val successState = awaitItem() assertThat(successState.submitEnabled).isFalse() - assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java) assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java) authenticationService.givenLoginError(A_THROWABLE) defaultOidcActionFlow.post(OidcAction.Success("aUrl")) val cancelLoadingState = awaitItem() - assertThat(cancelLoadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + assertThat(cancelLoadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java) val cancelFailureState = awaitItem() - assertThat(cancelFailureState.loginFlow).isInstanceOf(Async.Failure::class.java) + assertThat(cancelFailureState.loginFlow).isInstanceOf(AsyncData.Failure::class.java) } } @@ -205,15 +204,15 @@ class ConfirmAccountProviderPresenterTest { initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) val loadingState = awaitItem() assertThat(loadingState.submitEnabled).isTrue() - assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java) val successState = awaitItem() assertThat(successState.submitEnabled).isFalse() - assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java) assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java) assertThat(defaultLoginUserStory.loginFlowIsDone.value).isFalse() defaultOidcActionFlow.post(OidcAction.Success("aUrl")) val successSuccessState = awaitItem() - assertThat(successSuccessState.loginFlow).isInstanceOf(Async.Loading::class.java) + assertThat(successSuccessState.loginFlow).isInstanceOf(AsyncData.Loading::class.java) waitForPredicate { defaultLoginUserStory.loginFlowIsDone.value } } } @@ -233,7 +232,7 @@ class ConfirmAccountProviderPresenterTest { skipItems(1) // Loading val failureState = awaitItem() assertThat(failureState.submitEnabled).isFalse() - assertThat(failureState.loginFlow).isInstanceOf(Async.Failure::class.java) + assertThat(failureState.loginFlow).isInstanceOf(AsyncData.Failure::class.java) } } @@ -256,12 +255,12 @@ class ConfirmAccountProviderPresenterTest { // Check an error was returned val submittedState = awaitItem() - assertThat(submittedState.loginFlow).isInstanceOf(Async.Failure::class.java) + assertThat(submittedState.loginFlow).isInstanceOf(AsyncData.Failure::class.java) // Assert the error is then cleared submittedState.eventSink(ConfirmAccountProviderEvents.ClearError) val clearedState = awaitItem() - assertThat(clearedState.loginFlow).isEqualTo(Async.Uninitialized) + assertThat(clearedState.loginFlow).isEqualTo(AsyncData.Uninitialized) } } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt index a2b0fae449..4658df0ab9 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt @@ -23,7 +23,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.util.defaultAccountProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_PASSWORD @@ -37,7 +37,6 @@ import org.junit.Rule import org.junit.Test class LoginPasswordPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -57,7 +56,7 @@ class LoginPasswordPresenterTest { val initialState = awaitItem() assertThat(initialState.accountProvider).isEqualTo(defaultAccountProvider) assertThat(initialState.formState).isEqualTo(LoginFormState.Default) - assertThat(initialState.loginAction).isEqualTo(Async.Uninitialized) + assertThat(initialState.loginAction).isEqualTo(AsyncData.Uninitialized) assertThat(initialState.submitEnabled).isFalse() } } @@ -110,9 +109,9 @@ class LoginPasswordPresenterTest { val loginAndPasswordState = awaitItem() loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) val submitState = awaitItem() - assertThat(submitState.loginAction).isInstanceOf(Async.Loading::class.java) + assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java) val loggedInState = awaitItem() - assertThat(loggedInState.loginAction).isEqualTo(Async.Success(A_SESSION_ID)) + assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Success(A_SESSION_ID)) assertThat(loginUserStory.loginFlowIsDone.value).isTrue() } } @@ -139,9 +138,9 @@ class LoginPasswordPresenterTest { authenticationService.givenLoginError(A_THROWABLE) loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) val submitState = awaitItem() - assertThat(submitState.loginAction).isInstanceOf(Async.Loading::class.java) + assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java) val loggedInState = awaitItem() - assertThat(loggedInState.loginAction).isEqualTo(Async.Failure(A_THROWABLE)) + assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Failure(A_THROWABLE)) } } @@ -167,14 +166,14 @@ class LoginPasswordPresenterTest { authenticationService.givenLoginError(A_THROWABLE) loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) val submitState = awaitItem() - assertThat(submitState.loginAction).isInstanceOf(Async.Loading::class.java) + assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java) val loggedInState = awaitItem() // Check an error was returned - assertThat(loggedInState.loginAction).isEqualTo(Async.Failure(A_THROWABLE)) + assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Failure(A_THROWABLE)) // Assert the error is then cleared loggedInState.eventSink(LoginPasswordEvents.ClearError) val clearedState = awaitItem() - assertThat(clearedState.loginAction).isEqualTo(Async.Uninitialized) + assertThat(clearedState.loginAction).isEqualTo(AsyncData.Uninitialized) } } } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt index 84bd6925db..28aa1090a9 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt @@ -27,7 +27,7 @@ import io.element.android.features.login.impl.resolver.network.FakeWellknownRequ import io.element.android.features.login.impl.resolver.network.WellKnown import io.element.android.features.login.impl.resolver.network.WellKnownBaseConfig import io.element.android.features.login.impl.resolver.network.WellKnownSlidingSyncConfig -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService import io.element.android.tests.testutils.WarmUpRule @@ -37,7 +37,6 @@ import org.junit.Rule import org.junit.Test class SearchAccountProviderPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -57,7 +56,7 @@ class SearchAccountProviderPresenterTest { }.test { val initialState = awaitItem() assertThat(initialState.userInput).isEmpty() - assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) + assertThat(initialState.userInputResult).isEqualTo(AsyncData.Uninitialized) } } @@ -79,9 +78,9 @@ class SearchAccountProviderPresenterTest { initialState.eventSink.invoke(SearchAccountProviderEvents.UserInput("test")) val withInputState = awaitItem() assertThat(withInputState.userInput).isEqualTo("test") - assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) - assertThat(awaitItem().userInputResult).isInstanceOf(Async.Loading::class.java) - assertThat(awaitItem().userInputResult).isEqualTo(Async.Uninitialized) + assertThat(initialState.userInputResult).isEqualTo(AsyncData.Uninitialized) + assertThat(awaitItem().userInputResult).isInstanceOf(AsyncData.Loading::class.java) + assertThat(awaitItem().userInputResult).isEqualTo(AsyncData.Uninitialized) } } @@ -103,10 +102,10 @@ class SearchAccountProviderPresenterTest { initialState.eventSink.invoke(SearchAccountProviderEvents.UserInput("https://test.org")) val withInputState = awaitItem() assertThat(withInputState.userInput).isEqualTo("https://test.org") - assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) - assertThat(awaitItem().userInputResult).isInstanceOf(Async.Loading::class.java) + assertThat(initialState.userInputResult).isEqualTo(AsyncData.Uninitialized) + assertThat(awaitItem().userInputResult).isInstanceOf(AsyncData.Loading::class.java) assertThat(awaitItem().userInputResult).isEqualTo( - Async.Success( + AsyncData.Success( listOf( aHomeserverData(homeserverUrl = "https://test.org", isWellknownValid = false, supportSlidingSync = false) ) @@ -138,10 +137,10 @@ class SearchAccountProviderPresenterTest { initialState.eventSink.invoke(SearchAccountProviderEvents.UserInput("test")) val withInputState = awaitItem() assertThat(withInputState.userInput).isEqualTo("test") - assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) - assertThat(awaitItem().userInputResult).isInstanceOf(Async.Loading::class.java) + assertThat(initialState.userInputResult).isEqualTo(AsyncData.Uninitialized) + assertThat(awaitItem().userInputResult).isInstanceOf(AsyncData.Loading::class.java) assertThat(awaitItem().userInputResult).isEqualTo( - Async.Success( + AsyncData.Success( listOf( aHomeserverData(homeserverUrl = "https://test.org", isWellknownValid = true, supportSlidingSync = false) ) @@ -173,10 +172,10 @@ class SearchAccountProviderPresenterTest { initialState.eventSink.invoke(SearchAccountProviderEvents.UserInput("test")) val withInputState = awaitItem() assertThat(withInputState.userInput).isEqualTo("test") - assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) - assertThat(awaitItem().userInputResult).isInstanceOf(Async.Loading::class.java) + assertThat(initialState.userInputResult).isEqualTo(AsyncData.Uninitialized) + assertThat(awaitItem().userInputResult).isInstanceOf(AsyncData.Loading::class.java) assertThat(awaitItem().userInputResult).isEqualTo( - Async.Success( + AsyncData.Success( listOf( aHomeserverData(homeserverUrl = "https://test.io") ) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenterTest.kt index 590fa8efc0..f88f47ae26 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenterTest.kt @@ -22,7 +22,7 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.screens.loginpassword.LoginFormState -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_HOMESERVER_URL @@ -36,7 +36,6 @@ import org.junit.Rule import org.junit.Test class WaitListPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -58,7 +57,7 @@ class WaitListPresenterTest { val initialState = awaitItem() assertThat(initialState.appName).isEqualTo("Application Name") assertThat(initialState.serverName).isEqualTo(A_HOMESERVER_URL) - assertThat(initialState.loginAction).isEqualTo(Async.Uninitialized) + assertThat(initialState.loginAction).isEqualTo(AsyncData.Uninitialized) } } @@ -83,13 +82,13 @@ class WaitListPresenterTest { expectNoEvents() initialState.eventSink.invoke(WaitListEvents.AttemptLogin) val submitState = awaitItem() - assertThat(submitState.loginAction).isInstanceOf(Async.Loading::class.java) + assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java) val errorState = awaitItem() - assertThat(errorState.loginAction).isEqualTo(Async.Failure(A_THROWABLE)) + assertThat(errorState.loginAction).isEqualTo(AsyncData.Failure(A_THROWABLE)) // Assert the error can be cleared errorState.eventSink(WaitListEvents.ClearError) val clearedState = awaitItem() - assertThat(clearedState.loginAction).isEqualTo(Async.Uninitialized) + assertThat(clearedState.loginAction).isEqualTo(AsyncData.Uninitialized) } } @@ -113,9 +112,9 @@ class WaitListPresenterTest { expectNoEvents() initialState.eventSink.invoke(WaitListEvents.AttemptLogin) val submitState = awaitItem() - assertThat(submitState.loginAction).isInstanceOf(Async.Loading::class.java) + assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java) val successState = awaitItem() - assertThat(successState.loginAction).isEqualTo(Async.Success(A_USER_ID)) + assertThat(successState.loginAction).isEqualTo(AsyncData.Success(A_USER_ID)) assertThat(loginUserStory.loginFlowIsDone.value).isFalse() successState.eventSink.invoke(WaitListEvents.Continue) assertThat(loginUserStory.loginFlowIsDone.value).isTrue() diff --git a/libraries/coroutines/build.gradle.kts b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutEvents.kt similarity index 72% rename from libraries/coroutines/build.gradle.kts rename to features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutEvents.kt index f9a585ec95..ab5f40c321 100644 --- a/libraries/coroutines/build.gradle.kts +++ b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutEvents.kt @@ -13,16 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -plugins { - id("java-library") - alias(libs.plugins.kotlin.jvm) -} -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} +package io.element.android.features.logout.api.direct -dependencies { - implementation(libs.coroutines.core) +sealed interface DirectLogoutEvents { + data class Logout(val ignoreSdkError: Boolean) : DirectLogoutEvents + data object CloseDialogs : DirectLogoutEvents } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutPresenter.kt similarity index 77% rename from appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt rename to features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutPresenter.kt index be784ea7c9..bdd501cde6 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt +++ b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutPresenter.kt @@ -14,8 +14,8 @@ * limitations under the License. */ -package io.element.android.appnav.loggedin +package io.element.android.features.logout.api.direct -// sealed interface LoggedInEvents { -// data object MyEvent : LoggedInEvents -// } +import io.element.android.libraries.architecture.Presenter + +interface DirectLogoutPresenter : Presenter diff --git a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutState.kt b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutState.kt new file mode 100644 index 0000000000..e5b6af99e4 --- /dev/null +++ b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutState.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.logout.api.direct + +import io.element.android.libraries.architecture.AsyncAction + +data class DirectLogoutState( + val canDoDirectSignOut: Boolean, + val logoutAction: AsyncAction, + val eventSink: (DirectLogoutEvents) -> Unit, +) diff --git a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutView.kt b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutView.kt new file mode 100644 index 0000000000..0fd4c04cf2 --- /dev/null +++ b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutView.kt @@ -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. + */ + +package io.element.android.features.logout.api.direct + +import androidx.compose.runtime.Composable + +interface DirectLogoutView { + @Composable + fun Render( + state: DirectLogoutState, + onSuccessLogout: (logoutUrlResult: String?) -> Unit + ) +} diff --git a/features/logout/impl/build.gradle.kts b/features/logout/impl/build.gradle.kts index 68f7b389b3..699d6c93b8 100644 --- a/features/logout/impl/build.gradle.kts +++ b/features/logout/impl/build.gradle.kts @@ -22,6 +22,12 @@ plugins { android { namespace = "io.element.android.features.logout.impl" + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } } anvil { @@ -48,6 +54,10 @@ dependencies { testImplementation(libs.molecule.runtime) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) + testImplementation(libs.test.robolectric) + testImplementation(libs.test.junitext) + testImplementation(libs.androidx.compose.ui.test.junit) + testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.featureflag.test) testImplementation(projects.tests.testutils) diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutEntryPoint.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutEntryPoint.kt index 4928850245..c51be0895e 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutEntryPoint.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutEntryPoint.kt @@ -31,7 +31,6 @@ class DefaultLogoutEntryPoint @Inject constructor() : LogoutEntryPoint { val plugins = ArrayList() return object : LogoutEntryPoint.NodeBuilder { - override fun callback(callback: LogoutEntryPoint.Callback): LogoutEntryPoint.NodeBuilder { plugins += callback return this @@ -43,4 +42,3 @@ class DefaultLogoutEntryPoint @Inject constructor() : LogoutEntryPoint { } } } - diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutNode.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutNode.kt index ccb00b958f..14c7199416 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutNode.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutNode.kt @@ -38,7 +38,6 @@ class LogoutNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: LogoutPresenter, ) : Node(buildContext, plugins = plugins) { - private fun onChangeRecoveryKeyClicked() { plugins().forEach { it.onChangeRecoveryKeyClicked() } } diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt index b8d8772104..ed735f6afe 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt @@ -25,7 +25,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.bool.orTrue @@ -46,12 +47,11 @@ class LogoutPresenter @Inject constructor( private val encryptionService: EncryptionService, private val featureFlagService: FeatureFlagService, ) : Presenter { - @Composable override fun present(): LogoutState { val localCoroutineScope = rememberCoroutineScope() - val logoutAction: MutableState> = remember { - mutableStateOf(Async.Uninitialized) + val logoutAction: MutableState> = remember { + mutableStateOf(AsyncAction.Uninitialized) } val secureStorageFlag by featureFlagService.isFeatureEnabledFlow(FeatureFlags.SecureStorage) @@ -66,7 +66,6 @@ class LogoutPresenter @Inject constructor( } .collectAsState(initial = BackupUploadState.Unknown) - var showLogoutDialog by remember { mutableStateOf(false) } var isLastSession by remember { mutableStateOf(false) } LaunchedEffect(Unit) { isLastSession = encryptionService.isLastDevice().getOrNull() ?: false @@ -75,8 +74,8 @@ class LogoutPresenter @Inject constructor( val backupState by encryptionService.backupStateStateFlow.collectAsState() val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState() - val doesBackupExistOnServerAction: MutableState> = remember { - mutableStateOf(Async.Uninitialized) + val doesBackupExistOnServerAction: MutableState> = remember { + mutableStateOf(AsyncData.Uninitialized) } LaunchedEffect(backupState) { @@ -88,16 +87,14 @@ class LogoutPresenter @Inject constructor( fun handleEvents(event: LogoutEvents) { when (event) { is LogoutEvents.Logout -> { - if (showLogoutDialog || event.ignoreSdkError) { - showLogoutDialog = false + if (logoutAction.value.isConfirming() || event.ignoreSdkError) { localCoroutineScope.logout(logoutAction, event.ignoreSdkError) } else { - showLogoutDialog = true + logoutAction.value = AsyncAction.Confirming } } LogoutEvents.CloseDialogs -> { - logoutAction.value = Async.Uninitialized - showLogoutDialog = false + logoutAction.value = AsyncAction.Uninitialized } } } @@ -108,20 +105,19 @@ class LogoutPresenter @Inject constructor( doesBackupExistOnServer = doesBackupExistOnServerAction.value.dataOrNull().orTrue(), recoveryState = recoveryState, backupUploadState = backupUploadState, - showConfirmationDialog = showLogoutDialog, logoutAction = logoutAction.value, eventSink = ::handleEvents ) } - private fun CoroutineScope.getKeyBackupStatus(action: MutableState>) = launch { + private fun CoroutineScope.getKeyBackupStatus(action: MutableState>) = launch { suspend { encryptionService.doesBackupExistOnServer().getOrThrow() }.runCatchingUpdatingState(action) } private fun CoroutineScope.logout( - logoutAction: MutableState>, + logoutAction: MutableState>, ignoreSdkError: Boolean, ) = launch { suspend { diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt index abade32b96..6da9df8e72 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt @@ -16,7 +16,7 @@ package io.element.android.features.logout.impl -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.BackupUploadState import io.element.android.libraries.matrix.api.encryption.RecoveryState @@ -27,7 +27,6 @@ data class LogoutState( val doesBackupExistOnServer: Boolean, val recoveryState: RecoveryState, val backupUploadState: BackupUploadState, - val showConfirmationDialog: Boolean, - val logoutAction: Async, + val logoutAction: AsyncAction, val eventSink: (LogoutEvents) -> Unit, ) diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStateProvider.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStateProvider.kt index 8c9482105d..f1b11f61e9 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStateProvider.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStateProvider.kt @@ -17,7 +17,7 @@ package io.element.android.features.logout.impl import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.BackupUploadState import io.element.android.libraries.matrix.api.encryption.RecoveryState @@ -30,9 +30,9 @@ open class LogoutStateProvider : PreviewParameterProvider { aLogoutState(isLastSession = true), aLogoutState(isLastSession = false, backupUploadState = BackupUploadState.Uploading(66, 200)), aLogoutState(isLastSession = true, backupUploadState = BackupUploadState.Done), - aLogoutState(showConfirmationDialog = true), - aLogoutState(logoutAction = Async.Loading()), - aLogoutState(logoutAction = Async.Failure(Exception("Failed to logout"))), + aLogoutState(logoutAction = AsyncAction.Confirming), + aLogoutState(logoutAction = AsyncAction.Loading), + aLogoutState(logoutAction = AsyncAction.Failure(Exception("Failed to logout"))), aLogoutState(backupUploadState = BackupUploadState.SteadyException(SteadyStateException.Connection("No network"))), // Last session no recovery aLogoutState(isLastSession = true, recoveryState = RecoveryState.DISABLED), @@ -47,15 +47,14 @@ fun aLogoutState( doesBackupExistOnServer: Boolean = true, recoveryState: RecoveryState = RecoveryState.ENABLED, backupUploadState: BackupUploadState = BackupUploadState.Unknown, - showConfirmationDialog: Boolean = false, - logoutAction: Async = Async.Uninitialized, + logoutAction: AsyncAction = AsyncAction.Uninitialized, + eventSink: (LogoutEvents) -> Unit = {}, ) = LogoutState( isLastSession = isLastSession, backupState = backupState, doesBackupExistOnServer = doesBackupExistOnServer, recoveryState = recoveryState, backupUploadState = backupUploadState, - showConfirmationDialog = showConfirmationDialog, logoutAction = logoutAction, - eventSink = {} + eventSink = eventSink, ) diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt index d3d4805f82..ecec90cf06 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt @@ -22,7 +22,6 @@ import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector @@ -31,10 +30,10 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme -import io.element.android.libraries.architecture.Async +import io.element.android.features.logout.impl.tools.isBackingUp +import io.element.android.features.logout.impl.ui.LogoutActionDialog +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage -import io.element.android.libraries.designsystem.components.ProgressDialog -import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button @@ -79,43 +78,21 @@ fun LogoutView( }, ) - // Log out confirmation dialog - if (state.showConfirmationDialog) { - ConfirmationDialog( - title = stringResource(id = CommonStrings.action_signout), - content = stringResource(id = R.string.screen_signout_confirmation_dialog_content), - submitText = stringResource(id = CommonStrings.action_signout), - onSubmitClicked = { - eventSink(LogoutEvents.Logout(ignoreSdkError = false)) - }, - onDismiss = { - eventSink(LogoutEvents.CloseDialogs) - } - ) - } - - when (state.logoutAction) { - is Async.Loading -> - ProgressDialog(text = stringResource(id = R.string.screen_signout_in_progress_dialog_content)) - is Async.Failure -> - ConfirmationDialog( - title = stringResource(id = CommonStrings.dialog_title_error), - content = stringResource(id = CommonStrings.error_unknown), - submitText = stringResource(id = CommonStrings.action_signout_anyway), - onSubmitClicked = { - eventSink(LogoutEvents.Logout(ignoreSdkError = true)) - }, - onDismiss = { - eventSink(LogoutEvents.CloseDialogs) - } - ) - Async.Uninitialized -> - Unit - is Async.Success -> - LaunchedEffect(state.logoutAction) { - onSuccessLogout(state.logoutAction.data) - } - } + LogoutActionDialog( + state.logoutAction, + onConfirmClicked = { + eventSink(LogoutEvents.Logout(ignoreSdkError = false)) + }, + onForceLogoutClicked = { + eventSink(LogoutEvents.Logout(ignoreSdkError = true)) + }, + onDismissError = { + eventSink(LogoutEvents.CloseDialogs) + }, + onSuccessLogout = { + onSuccessLogout(it) + }, + ) } @Composable @@ -146,17 +123,6 @@ private fun subtitle(state: LogoutState): String? { } } -private fun BackupUploadState.isBackingUp(): Boolean { - return when (this) { - BackupUploadState.Waiting, - is BackupUploadState.Uploading -> true - is BackupUploadState.SteadyException -> exception is SteadyStateException.Connection - BackupUploadState.Unknown, - BackupUploadState.Done, - BackupUploadState.Error -> false - } -} - @Composable private fun ColumnScope.Buttons( state: LogoutState, @@ -172,13 +138,13 @@ private fun ColumnScope.Buttons( ) } val signOutSubmitRes = when { - logoutAction is Async.Loading -> R.string.screen_signout_in_progress_dialog_content + logoutAction is AsyncAction.Loading -> R.string.screen_signout_in_progress_dialog_content state.backupUploadState.isBackingUp() -> CommonStrings.action_signout_anyway else -> CommonStrings.action_signout } Button( text = stringResource(id = signOutSubmitRes), - showProgress = logoutAction is Async.Loading, + showProgress = logoutAction is AsyncAction.Loading, destructive = true, modifier = Modifier .fillMaxWidth() diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutPresenter.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutPresenter.kt new file mode 100644 index 0000000000..8ee46b63ac --- /dev/null +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutPresenter.kt @@ -0,0 +1,109 @@ +/* + * 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.logout.impl.direct + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.logout.api.direct.DirectLogoutEvents +import io.element.android.features.logout.api.direct.DirectLogoutPresenter +import io.element.android.features.logout.api.direct.DirectLogoutState +import io.element.android.features.logout.impl.tools.isBackingUp +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.runCatchingUpdatingState +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.encryption.BackupUploadState +import io.element.android.libraries.matrix.api.encryption.EncryptionService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch +import javax.inject.Inject + +@ContributesBinding(SessionScope::class) +class DefaultDirectLogoutPresenter @Inject constructor( + private val matrixClient: MatrixClient, + private val encryptionService: EncryptionService, + private val featureFlagService: FeatureFlagService, +) : DirectLogoutPresenter { + @Composable + override fun present(): DirectLogoutState { + val localCoroutineScope = rememberCoroutineScope() + + val logoutAction: MutableState> = remember { + mutableStateOf(AsyncAction.Uninitialized) + } + + val secureStorageFlag by featureFlagService.isFeatureEnabledFlow(FeatureFlags.SecureStorage) + .collectAsState(initial = null) + + val backupUploadState: BackupUploadState by remember(secureStorageFlag) { + when (secureStorageFlag) { + true -> encryptionService.waitForBackupUploadSteadyState() + false -> flowOf(BackupUploadState.Done) + else -> emptyFlow() + } + } + .collectAsState(initial = BackupUploadState.Unknown) + + var isLastSession by remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + isLastSession = encryptionService.isLastDevice().getOrNull() ?: false + } + + fun handleEvents(event: DirectLogoutEvents) { + when (event) { + is DirectLogoutEvents.Logout -> { + if (logoutAction.value.isConfirming() || event.ignoreSdkError) { + localCoroutineScope.logout(logoutAction, event.ignoreSdkError) + } else { + logoutAction.value = AsyncAction.Confirming + } + } + DirectLogoutEvents.CloseDialogs -> { + logoutAction.value = AsyncAction.Uninitialized + } + } + } + + return DirectLogoutState( + canDoDirectSignOut = !isLastSession && + !backupUploadState.isBackingUp(), + logoutAction = logoutAction.value, + eventSink = ::handleEvents + ) + } + + private fun CoroutineScope.logout( + logoutAction: MutableState>, + ignoreSdkError: Boolean, + ) = launch { + suspend { + matrixClient.logout(ignoreSdkError) + }.runCatchingUpdatingState(logoutAction) + } +} diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutView.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutView.kt new file mode 100644 index 0000000000..f646d86c9e --- /dev/null +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutView.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.logout.impl.direct + +import androidx.compose.runtime.Composable +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.logout.api.direct.DirectLogoutEvents +import io.element.android.features.logout.api.direct.DirectLogoutState +import io.element.android.features.logout.api.direct.DirectLogoutView +import io.element.android.features.logout.impl.ui.LogoutActionDialog +import io.element.android.libraries.di.SessionScope +import javax.inject.Inject + +@ContributesBinding(SessionScope::class) +class DefaultDirectLogoutView @Inject constructor() : DirectLogoutView { + @Composable + override fun Render( + state: DirectLogoutState, + onSuccessLogout: (logoutUrlResult: String?) -> Unit, + ) { + val eventSink = state.eventSink + LogoutActionDialog( + state.logoutAction, + onConfirmClicked = { + eventSink(DirectLogoutEvents.Logout(ignoreSdkError = false)) + }, + onForceLogoutClicked = { + eventSink(DirectLogoutEvents.Logout(ignoreSdkError = true)) + }, + onDismissError = { + eventSink(DirectLogoutEvents.CloseDialogs) + }, + onSuccessLogout = { + onSuccessLogout(it) + }, + ) + } +} diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/tools/Extensions.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/tools/Extensions.kt new file mode 100644 index 0000000000..5d9858782d --- /dev/null +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/tools/Extensions.kt @@ -0,0 +1,32 @@ +/* + * 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.logout.impl.tools + +import io.element.android.libraries.matrix.api.encryption.BackupUploadState +import io.element.android.libraries.matrix.api.encryption.SteadyStateException + +internal fun BackupUploadState.isBackingUp(): Boolean { + return when (this) { + BackupUploadState.Waiting, + is BackupUploadState.Uploading -> true + // The backup is in progress, but there have been a network issue, so we have to warn the user. + is BackupUploadState.SteadyException -> exception is SteadyStateException.Connection + BackupUploadState.Unknown, + BackupUploadState.Done, + BackupUploadState.Error -> false + } +} diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/LogoutActionDialog.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/LogoutActionDialog.kt new file mode 100644 index 0000000000..636dd68058 --- /dev/null +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/LogoutActionDialog.kt @@ -0,0 +1,60 @@ +/* + * 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.logout.impl.ui + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.res.stringResource +import io.element.android.features.logout.impl.R +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.components.ProgressDialog +import io.element.android.libraries.designsystem.components.dialogs.RetryDialog +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun LogoutActionDialog( + state: AsyncAction, + onConfirmClicked: () -> Unit, + onForceLogoutClicked: () -> Unit, + // TODO Rename + onDismissError: () -> Unit, + onSuccessLogout: (String?) -> Unit, +) { + when (state) { + AsyncAction.Uninitialized -> + Unit + AsyncAction.Confirming -> + LogoutConfirmationDialog( + onSubmitClicked = onConfirmClicked, + onDismiss = onDismissError + ) + is AsyncAction.Loading -> + ProgressDialog(text = stringResource(id = R.string.screen_signout_in_progress_dialog_content)) + is AsyncAction.Failure -> + RetryDialog( + title = stringResource(id = CommonStrings.dialog_title_error), + content = stringResource(id = CommonStrings.error_unknown), + retryText = stringResource(id = CommonStrings.action_signout_anyway), + onRetry = onForceLogoutClicked, + onDismiss = onDismissError, + ) + is AsyncAction.Success -> + LaunchedEffect(state) { + onSuccessLogout(state.data) + } + } +} diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/LogoutConfirmationDialog.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/LogoutConfirmationDialog.kt new file mode 100644 index 0000000000..caf04a2752 --- /dev/null +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/LogoutConfirmationDialog.kt @@ -0,0 +1,37 @@ +/* + * 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.logout.impl.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import io.element.android.features.logout.impl.R +import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun LogoutConfirmationDialog( + onSubmitClicked: () -> Unit, + onDismiss: () -> Unit, +) { + ConfirmationDialog( + title = stringResource(id = CommonStrings.action_signout), + content = stringResource(id = R.string.screen_signout_confirmation_dialog_content), + submitText = stringResource(id = CommonStrings.action_signout), + onSubmitClicked = onSubmitClicked, + onDismiss = onDismiss, + ) +} diff --git a/features/logout/impl/src/main/res/values-es/translations.xml b/features/logout/impl/src/main/res/values-es/translations.xml index 84f08fac3c..52e1fbddb7 100644 --- a/features/logout/impl/src/main/res/values-es/translations.xml +++ b/features/logout/impl/src/main/res/values-es/translations.xml @@ -2,4 +2,17 @@ "¿Estás seguro de que quieres cerrar sesión?" "Cerrando sesión…" + "Estás a punto de cerrar tu última sesión. Si cierras sesión ahora, perderás el acceso a tus mensajes cifrados." + "Has desactivado la copia de seguridad" + "Se estaba haciendo una copia de seguridad de tus claves cuando te desconectaste. Vuelve a conectarte para que se haga una copia de seguridad de tus claves antes de desconectarte." + "Se está guardando una copia de seguridad de tus claves" + "Espera a que se complete antes de cerrar sesión." + "Se sigue guardando una copia de seguridad de tus claves" + "Estás a punto de cerrar tu última sesión. Si cierras sesión ahora, perderás el acceso a tus mensajes cifrados." + "La recuperación no está configurada" + "Estás a punto de cerrar tu última sesión. Si cierras la sesión ahora, podrías perder el acceso a tus mensajes cifrados." + "¿Has guardado tu clave de recuperación?" + "Cerrar sesión" + "Cerrar sesión" + "Cerrar sesión" diff --git a/features/logout/impl/src/main/res/values-it/translations.xml b/features/logout/impl/src/main/res/values-it/translations.xml index 73c1fbd3a7..50b79fa708 100644 --- a/features/logout/impl/src/main/res/values-it/translations.xml +++ b/features/logout/impl/src/main/res/values-it/translations.xml @@ -2,4 +2,17 @@ "Sei sicuro di voler uscire?" "Uscita in corso…" + "Stai per disconnettere la tua ultima sessione. Se esci ora, perderai l\'accesso ai tuoi messaggi cifrati." + "Hai disattivato il backup" + "Il backup delle chiavi era ancora in corso quando sei andato offline. Riconnettiti per eseguire il backup delle chiavi prima di uscire." + "Il backup delle chiavi è ancora in corso" + "Attendi il completamento dell\'operazione prima di uscire." + "Il backup delle chiavi è ancora in corso" + "Stai per disconnettere la tua ultima sessione. Se esci ora, perderai l\'accesso ai tuoi messaggi cifrati." + "Recupero non impostato" + "Stai per disconnettere la tua ultima sessione. Se esci ora, potresti perdere l\'accesso ai tuoi messaggi cifrati." + "Hai salvato la chiave di recupero?" + "Disconnetti" + "Disconnetti" + "Disconnetti" diff --git a/features/logout/impl/src/main/res/values-zh-rTW/translations.xml b/features/logout/impl/src/main/res/values-zh-rTW/translations.xml index a4ca1b030d..646bf1e1ba 100644 --- a/features/logout/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/logout/impl/src/main/res/values-zh-rTW/translations.xml @@ -2,4 +2,7 @@ "您確定要登出嗎?" "正在登出…" + "登出" + "登出" + "登出" diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt index 75401c290a..83b7799c3a 100644 --- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt @@ -18,9 +18,10 @@ package io.element.android.features.logout.impl import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow +import app.cash.turbine.ReceiveTurbine import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.MatrixClient @@ -32,7 +33,6 @@ import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.tests.testutils.WarmUpRule -import io.element.android.tests.testutils.awaitLastSequentialItem import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.runTest @@ -40,7 +40,6 @@ import org.junit.Rule import org.junit.Test class LogoutPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -50,14 +49,13 @@ class LogoutPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - val initialState = awaitLastSequentialItem() + val initialState = awaitFirstItem() assertThat(initialState.isLastSession).isFalse() assertThat(initialState.backupState).isEqualTo(BackupState.UNKNOWN) assertThat(initialState.doesBackupExistOnServer).isTrue() assertThat(initialState.recoveryState).isEqualTo(RecoveryState.UNKNOWN) assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Unknown) - assertThat(initialState.showConfirmationDialog).isFalse() - assertThat(initialState.logoutAction).isEqualTo(Async.Uninitialized) + assertThat(initialState.logoutAction).isEqualTo(AsyncAction.Uninitialized) } } @@ -71,12 +69,11 @@ class LogoutPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - skipItems(1) + skipItems(3) val initialState = awaitItem() assertThat(initialState.isLastSession).isTrue() assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Unknown) - assertThat(initialState.showConfirmationDialog).isFalse() - assertThat(initialState.logoutAction).isEqualTo(Async.Uninitialized) + assertThat(initialState.logoutAction).isEqualTo(AsyncAction.Uninitialized) } } @@ -101,8 +98,7 @@ class LogoutPresenterTest { val initialState = awaitItem() assertThat(initialState.isLastSession).isFalse() assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Unknown) - assertThat(initialState.showConfirmationDialog).isFalse() - assertThat(initialState.logoutAction).isEqualTo(Async.Uninitialized) + assertThat(initialState.logoutAction).isEqualTo(AsyncAction.Uninitialized) skipItems(1) val waitingState = awaitItem() assertThat(waitingState.backupUploadState).isEqualTo(BackupUploadState.Waiting) @@ -120,13 +116,13 @@ class LogoutPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - val initialState = awaitLastSequentialItem() + val initialState = awaitFirstItem() initialState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false)) val confirmationState = awaitItem() - assertThat(confirmationState.showConfirmationDialog).isTrue() + assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.Confirming) initialState.eventSink.invoke(LogoutEvents.CloseDialogs) val finalState = awaitItem() - assertThat(finalState.showConfirmationDialog).isFalse() + assertThat(finalState.logoutAction).isEqualTo(AsyncAction.Uninitialized) } } @@ -136,17 +132,15 @@ class LogoutPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - val initialState = awaitLastSequentialItem() + val initialState = awaitFirstItem() initialState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false)) val confirmationState = awaitItem() - assertThat(confirmationState.showConfirmationDialog).isTrue() + assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.Confirming) confirmationState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false)) - skipItems(1) val loadingState = awaitItem() - assertThat(loadingState.showConfirmationDialog).isFalse() - assertThat(loadingState.logoutAction).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val successState = awaitItem() - assertThat(successState.logoutAction).isInstanceOf(Async.Success::class.java) + assertThat(successState.logoutAction).isInstanceOf(AsyncAction.Success::class.java) } } @@ -161,22 +155,18 @@ class LogoutPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - skipItems(1) - val initialState = awaitItem() + val initialState = awaitFirstItem() initialState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false)) val confirmationState = awaitItem() - assertThat(confirmationState.showConfirmationDialog).isTrue() + assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.Confirming) confirmationState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false)) - skipItems(1) val loadingState = awaitItem() - assertThat(loadingState.showConfirmationDialog).isFalse() - assertThat(loadingState.logoutAction).isInstanceOf(Async.Loading::class.java) - skipItems(1) + assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val errorState = awaitItem() - assertThat(errorState.logoutAction).isEqualTo(Async.Failure(A_THROWABLE)) + assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(A_THROWABLE)) errorState.eventSink.invoke(LogoutEvents.CloseDialogs) val finalState = awaitItem() - assertThat(finalState.logoutAction).isEqualTo(Async.Uninitialized) + assertThat(finalState.logoutAction).isEqualTo(AsyncAction.Uninitialized) } } @@ -191,28 +181,28 @@ class LogoutPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - skipItems(1) - val initialState = awaitItem() + val initialState = awaitFirstItem() initialState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false)) val confirmationState = awaitItem() - assertThat(confirmationState.showConfirmationDialog).isTrue() + assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.Confirming) confirmationState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false)) - skipItems(1) val loadingState = awaitItem() - assertThat(loadingState.showConfirmationDialog).isFalse() - assertThat(loadingState.logoutAction).isInstanceOf(Async.Loading::class.java) - skipItems(1) + assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val errorState = awaitItem() - assertThat(errorState.logoutAction).isEqualTo(Async.Failure(A_THROWABLE)) + assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(A_THROWABLE)) errorState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = true)) val loadingState2 = awaitItem() - assertThat(loadingState2.showConfirmationDialog).isFalse() - assertThat(loadingState2.logoutAction).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState2.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val successState = awaitItem() - assertThat(successState.logoutAction).isInstanceOf(Async.Success::class.java) + assertThat(successState.logoutAction).isInstanceOf(AsyncAction.Success::class.java) } } + private suspend fun ReceiveTurbine.awaitFirstItem(): T { + skipItems(2) + return awaitItem() + } + private fun createLogoutPresenter( matrixClient: MatrixClient = FakeMatrixClient(), encryptionService: EncryptionService = FakeEncryptionService(), @@ -222,4 +212,3 @@ class LogoutPresenterTest { featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)), ) } - diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutViewTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutViewTest.kt new file mode 100644 index 0000000000..697da3cad5 --- /dev/null +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutViewTest.kt @@ -0,0 +1,167 @@ +/* + * 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.logout.impl + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.testtags.TestTags +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EnsureNeverCalledWithParam +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.ensureCalledOnceWithParam +import io.element.android.tests.testutils.pressBack +import io.element.android.tests.testutils.pressTag +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class LogoutViewTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test + fun `clicking on logout sends a LogoutEvents`() { + val eventsRecorder = EventsRecorder() + rule.setContent { + LogoutView( + aLogoutState( + eventSink = eventsRecorder + ), + onChangeRecoveryKeyClicked = EnsureNeverCalled(), + onBackClicked = EnsureNeverCalled(), + onSuccessLogout = EnsureNeverCalledWithParam(), + ) + } + rule.clickOn(CommonStrings.action_signout) + eventsRecorder.assertSingle(LogoutEvents.Logout(false)) + } + + @Test + fun `confirming logout sends a LogoutEvents`() { + val eventsRecorder = EventsRecorder() + rule.setContent { + LogoutView( + aLogoutState( + logoutAction = AsyncAction.Confirming, + eventSink = eventsRecorder + ), + onChangeRecoveryKeyClicked = EnsureNeverCalled(), + onBackClicked = EnsureNeverCalled(), + onSuccessLogout = EnsureNeverCalledWithParam(), + ) + } + rule.pressTag(TestTags.dialogPositive.value) + eventsRecorder.assertSingle(LogoutEvents.Logout(false)) + } + + @Test + fun `clicking on back invoke back callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { callback -> + rule.setContent { + LogoutView( + aLogoutState( + eventSink = eventsRecorder + ), + onChangeRecoveryKeyClicked = EnsureNeverCalled(), + onBackClicked = callback, + onSuccessLogout = EnsureNeverCalledWithParam(), + ) + } + rule.pressBack() + } + } + + @Test + fun `clicking on confirm after error sends a LogoutEvents`() { + val eventsRecorder = EventsRecorder() + rule.setContent { + LogoutView( + aLogoutState( + logoutAction = AsyncAction.Failure(Exception("Failed to logout")), + eventSink = eventsRecorder + ), + onChangeRecoveryKeyClicked = EnsureNeverCalled(), + onBackClicked = EnsureNeverCalled(), + onSuccessLogout = EnsureNeverCalledWithParam(), + ) + } + rule.clickOn(CommonStrings.action_signout_anyway) + eventsRecorder.assertSingle(LogoutEvents.Logout(true)) + } + + @Test + fun `clicking on cancel after error sends a LogoutEvents`() { + val eventsRecorder = EventsRecorder() + rule.setContent { + LogoutView( + aLogoutState( + logoutAction = AsyncAction.Failure(Exception("Failed to logout")), + eventSink = eventsRecorder + ), + onChangeRecoveryKeyClicked = EnsureNeverCalled(), + onBackClicked = EnsureNeverCalled(), + onSuccessLogout = EnsureNeverCalledWithParam(), + ) + } + rule.clickOn(CommonStrings.action_cancel) + eventsRecorder.assertSingle(LogoutEvents.CloseDialogs) + } + + @Test + fun `success logout invoke onSuccessLogout`() { + val data = "data" + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnceWithParam(data) { callback -> + rule.setContent { + LogoutView( + aLogoutState( + logoutAction = AsyncAction.Success(data), + eventSink = eventsRecorder + ), + onChangeRecoveryKeyClicked = EnsureNeverCalled(), + onBackClicked = EnsureNeverCalled(), + onSuccessLogout = callback, + ) + } + } + } + + @Test + fun `last session setting button invoke onChangeRecoveryKeyClicked`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { callback -> + rule.setContent { + LogoutView( + aLogoutState( + isLastSession = true, + eventSink = eventsRecorder + ), + onChangeRecoveryKeyClicked = callback, + onBackClicked = EnsureNeverCalled(), + onSuccessLogout = EnsureNeverCalledWithParam(), + ) + } + rule.clickOn(CommonStrings.common_settings) + } + } +} diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutPresenterTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutPresenterTest.kt new file mode 100644 index 0000000000..50a1e381e8 --- /dev/null +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutPresenterTest.kt @@ -0,0 +1,195 @@ +/* + * 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.logout.impl.direct + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.ReceiveTurbine +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.features.logout.api.direct.DirectLogoutEvents +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.encryption.BackupUploadState +import io.element.android.libraries.matrix.api.encryption.EncryptionService +import io.element.android.libraries.matrix.test.A_THROWABLE +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService +import io.element.android.tests.testutils.WarmUpRule +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class DefaultDirectLogoutPresenterTest { + @get:Rule + val warmUpRule = WarmUpRule() + + @Test + fun `present - initial state`() = runTest { + val presenter = createDefaultDirectLogoutPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitFirstItem() + assertThat(initialState.canDoDirectSignOut).isTrue() + assertThat(initialState.logoutAction).isEqualTo(AsyncAction.Uninitialized) + } + } + + @Test + fun `present - initial state - last session`() = runTest { + val presenter = createDefaultDirectLogoutPresenter( + encryptionService = FakeEncryptionService().apply { + givenIsLastDevice(true) + } + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(2) + val initialState = awaitItem() + assertThat(initialState.canDoDirectSignOut).isFalse() + assertThat(initialState.logoutAction).isEqualTo(AsyncAction.Uninitialized) + } + } + + @Test + fun `present - initial state - backing up`() = runTest { + val encryptionService = FakeEncryptionService() + encryptionService.givenWaitForBackupUploadSteadyStateFlow( + flow { + emit(BackupUploadState.Waiting) + } + ) + val presenter = createDefaultDirectLogoutPresenter( + encryptionService = encryptionService + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(2) + val initialState = awaitItem() + assertThat(initialState.canDoDirectSignOut).isFalse() + assertThat(initialState.logoutAction).isEqualTo(AsyncAction.Uninitialized) + } + } + + @Test + fun `present - logout then cancel`() = runTest { + val presenter = createDefaultDirectLogoutPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitFirstItem() + initialState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) + val confirmationState = awaitItem() + assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.Confirming) + initialState.eventSink.invoke(DirectLogoutEvents.CloseDialogs) + val finalState = awaitItem() + assertThat(finalState.logoutAction).isEqualTo(AsyncAction.Uninitialized) + } + } + + @Test + fun `present - logout then confirm`() = runTest { + val presenter = createDefaultDirectLogoutPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitFirstItem() + initialState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) + val confirmationState = awaitItem() + assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.Confirming) + confirmationState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) + val loadingState = awaitItem() + assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) + val successState = awaitItem() + assertThat(successState.logoutAction).isInstanceOf(AsyncAction.Success::class.java) + } + } + + @Test + fun `present - logout with error then cancel`() = runTest { + val matrixClient = FakeMatrixClient().apply { + givenLogoutError(A_THROWABLE) + } + val presenter = createDefaultDirectLogoutPresenter( + matrixClient, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitFirstItem() + initialState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) + val confirmationState = awaitItem() + assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.Confirming) + confirmationState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) + val loadingState = awaitItem() + assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) + val errorState = awaitItem() + assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(A_THROWABLE)) + errorState.eventSink.invoke(DirectLogoutEvents.CloseDialogs) + val finalState = awaitItem() + assertThat(finalState.logoutAction).isEqualTo(AsyncAction.Uninitialized) + } + } + + @Test + fun `present - logout with error then force`() = runTest { + val matrixClient = FakeMatrixClient().apply { + givenLogoutError(A_THROWABLE) + } + val presenter = createDefaultDirectLogoutPresenter( + matrixClient, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitFirstItem() + initialState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) + val confirmationState = awaitItem() + assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.Confirming) + confirmationState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) + val loadingState = awaitItem() + assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) + val errorState = awaitItem() + assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(A_THROWABLE)) + errorState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = true)) + val loadingState2 = awaitItem() + assertThat(loadingState2.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) + val successState = awaitItem() + assertThat(successState.logoutAction).isInstanceOf(AsyncAction.Success::class.java) + } + } + + private suspend fun ReceiveTurbine.awaitFirstItem(): T { + skipItems(1) + return awaitItem() + } + + private fun createDefaultDirectLogoutPresenter( + matrixClient: MatrixClient = FakeMatrixClient(), + encryptionService: EncryptionService = FakeEncryptionService(), + ): DefaultDirectLogoutPresenter = DefaultDirectLogoutPresenter( + matrixClient = matrixClient, + encryptionService = encryptionService, + featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)), + ) +} diff --git a/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/timeline/HtmlConverterProvider.kt b/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/timeline/HtmlConverterProvider.kt index a248905086..c6c1fa641f 100644 --- a/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/timeline/HtmlConverterProvider.kt +++ b/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/timeline/HtmlConverterProvider.kt @@ -21,7 +21,6 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.wysiwyg.utils.HtmlConverter interface HtmlConverterProvider { - @Composable fun Update(currentUserId: UserId) diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 21d39de816..897dc36faa 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -74,7 +74,6 @@ dependencies { implementation(libs.vanniktech.blurhash) implementation(libs.telephoto.zoomableimage) implementation(libs.matrix.emojibase.bindings) - api(libs.matrix.richtexteditor.compose) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) @@ -92,7 +91,6 @@ dependencies { testImplementation(projects.libraries.mediapickers.test) testImplementation(projects.libraries.permissions.test) testImplementation(projects.libraries.preferences.test) - testImplementation(projects.libraries.textcomposer.test) testImplementation(projects.libraries.voicerecorder.test) testImplementation(projects.libraries.mediaplayer.test) testImplementation(projects.libraries.mediaviewer.test) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt index 5323531f97..28ef69226b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt @@ -130,7 +130,8 @@ internal fun ExpandableBottomSheetScaffold( contentOverflows = contentHeight > maxHeight val peekHeight = min( - maxHeight, // prevent the sheet from expanding beyond the screen + // prevent the sheet from expanding beyond the screen + maxHeight, contentHeight ) @@ -152,7 +153,8 @@ internal fun ExpandableBottomSheetScaffold( top.place(x = 0, y = 0) } }, - content = { sheetContent(false) }) + content = { sheetContent(false) } + ) }, sheetDragHandle, peekHeight) }.map { measurable: Measurable -> measurable.measure(constraints) @@ -161,7 +163,8 @@ internal fun ExpandableBottomSheetScaffold( layout(constraints.maxWidth, constraints.maxHeight) { scaffoldPlaceable.place(0, 0) } - }) + } + ) } private fun CustomSheetState.getIntOffset(): Int? = try { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index acccfeaedc..3cf480f7d1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -46,6 +46,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.poll.api.create.CreatePollEntryPoint import io.element.android.features.poll.api.create.CreatePollMode @@ -87,9 +88,7 @@ class MessagesFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins ) { - sealed interface NavTarget : Parcelable { - @Parcelize data object Empty : NavTarget @@ -138,8 +137,8 @@ class MessagesFlowNode @AssistedInject constructor( callback?.onRoomDetailsClicked() } - override fun onEventClicked(event: TimelineItem.Event) { - processEventClicked(event) + override fun onEventClicked(event: TimelineItem.Event): Boolean { + return processEventClicked(event) } override fun onPreviewAttachments(attachments: ImmutableList) { @@ -238,8 +237,8 @@ class MessagesFlowNode @AssistedInject constructor( } } - private fun processEventClicked(event: TimelineItem.Event) { - when (event.content) { + private fun processEventClicked(event: TimelineItem.Event): Boolean { + return when (event.content) { is TimelineItemImageContent -> { val navTarget = NavTarget.MediaViewer( mediaInfo = MediaInfo( @@ -252,6 +251,27 @@ class MessagesFlowNode @AssistedInject constructor( thumbnailSource = event.content.thumbnailSource, ) overlay.show(navTarget) + true + } + is TimelineItemStickerContent -> { + /* Sticker may have an empty url and no thumbnail + if encrypted on certain bridges */ + if (event.content.preferredMediaSource != null) { + val navTarget = NavTarget.MediaViewer( + mediaInfo = MediaInfo( + name = event.content.body, + mimeType = event.content.mimeType, + formattedFileSize = event.content.formattedFileSize, + fileExtension = event.content.fileExtension + ), + mediaSource = event.content.preferredMediaSource, + thumbnailSource = event.content.thumbnailSource, + ) + overlay.show(navTarget) + true + } else { + false + } } is TimelineItemVideoContent -> { val navTarget = NavTarget.MediaViewer( @@ -265,6 +285,7 @@ class MessagesFlowNode @AssistedInject constructor( thumbnailSource = event.content.thumbnailSource, ) overlay.show(navTarget) + true } is TimelineItemFileContent -> { val navTarget = NavTarget.MediaViewer( @@ -278,6 +299,7 @@ class MessagesFlowNode @AssistedInject constructor( thumbnailSource = event.content.thumbnailSource, ) overlay.show(navTarget) + true } is TimelineItemAudioContent -> { val navTarget = NavTarget.MediaViewer( @@ -291,6 +313,7 @@ class MessagesFlowNode @AssistedInject constructor( thumbnailSource = null, ) overlay.show(navTarget) + true } is TimelineItemLocationContent -> { val navTarget = NavTarget.LocationViewer( @@ -298,8 +321,9 @@ class MessagesFlowNode @AssistedInject constructor( description = event.content.description, ) overlay.show(navTarget) + true } - else -> Unit + else -> false } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index 09883779e8..c81cbebaae 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -31,13 +31,14 @@ import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.model.TimelineItem -import io.element.android.libraries.mediaplayer.api.MediaPlayer +import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo +import io.element.android.libraries.mediaplayer.api.MediaPlayer import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.extensions.toAnalyticsViewRoom import kotlinx.collections.immutable.ImmutableList @@ -48,17 +49,16 @@ class MessagesNode @AssistedInject constructor( @Assisted plugins: List, private val room: MatrixRoom, private val analyticsService: AnalyticsService, - private val presenterFactory: MessagesPresenter.Factory, + presenterFactory: MessagesPresenter.Factory, private val timelineItemPresenterFactories: TimelineItemPresenterFactories, private val mediaPlayer: MediaPlayer, ) : Node(buildContext, plugins = plugins), MessagesNavigator { - private val presenter = presenterFactory.create(this) private val callback = plugins().firstOrNull() interface Callback : Plugin { fun onRoomDetailsClicked() - fun onEventClicked(event: TimelineItem.Event) + fun onEventClicked(event: TimelineItem.Event): Boolean fun onPreviewAttachments(attachments: ImmutableList) fun onUserDataClicked(userId: UserId) fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) @@ -85,8 +85,8 @@ class MessagesNode @AssistedInject constructor( callback?.onRoomDetailsClicked() } - private fun onEventClicked(event: TimelineItem.Event) { - callback?.onEventClicked(event) + private fun onEventClicked(event: TimelineItem.Event): Boolean { + return callback?.onEventClicked(event).orFalse() } private fun onPreviewAttachments(attachments: ImmutableList) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 7d421a256a..e40d0bbb9b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -54,6 +54,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent @@ -64,7 +65,7 @@ import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.preferences.api.store.PreferencesStore import io.element.android.libraries.androidutils.clipboard.ClipboardHelper -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.meta.BuildMeta @@ -113,7 +114,6 @@ class MessagesPresenter @AssistedInject constructor( private val buildMeta: BuildMeta, private val currentSessionIdHolder: CurrentSessionIdHolder, ) : Presenter { - private val timelinePresenter = timelinePresenterFactory.create(navigator = navigator) @AssistedFactory @@ -139,11 +139,12 @@ class MessagesPresenter @AssistedInject constructor( val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value) val userHasPermissionToRedact by room.canRedactAsState(updateKey = syncUpdateFlow.value) - val roomName: Async by remember { - derivedStateOf { roomInfo?.name?.let { Async.Success(it) } ?: Async.Uninitialized } + val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION_SENT, updateKey = syncUpdateFlow.value) + val roomName: AsyncData by remember { + derivedStateOf { roomInfo?.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized } } - val roomAvatar: Async by remember { - derivedStateOf { roomInfo?.avatarData()?.let { Async.Success(it) } ?: Async.Uninitialized } + val roomAvatar: AsyncData by remember { + derivedStateOf { roomInfo?.avatarData()?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized } } var hasDismissedInviteDialog by rememberSaveable { @@ -160,7 +161,7 @@ class MessagesPresenter @AssistedInject constructor( } } - val inviteProgress = remember { mutableStateOf>(Async.Uninitialized) } + val inviteProgress = remember { mutableStateOf>(AsyncData.Uninitialized) } var showReinvitePrompt by remember { mutableStateOf(false) } LaunchedEffect(hasDismissedInviteDialog, composerState.hasFocus, syncUpdateFlow) { withContext(dispatchers.io) { @@ -219,6 +220,7 @@ class MessagesPresenter @AssistedInject constructor( roomAvatar = roomAvatar, userHasPermissionToSendMessage = userHasPermissionToSendMessage, userHasPermissionToRedact = userHasPermissionToRedact, + userHasPermissionToSendReaction = userHasPermissionToSendReaction, composerState = composerState, voiceMessageComposerState = voiceMessageComposerState, timelineState = timelineState, @@ -276,8 +278,8 @@ class MessagesPresenter @AssistedInject constructor( .onFailure { Timber.e(it) } } - private fun CoroutineScope.reinviteOtherUser(inviteProgress: MutableState>) = launch(dispatchers.io) { - inviteProgress.value = Async.Loading() + private fun CoroutineScope.reinviteOtherUser(inviteProgress: MutableState>) = launch(dispatchers.io) { + inviteProgress.value = AsyncData.Loading() runCatching { room.updateMembers() @@ -293,10 +295,10 @@ class MessagesPresenter @AssistedInject constructor( }.getOrThrow() }.fold( onSuccess = { - inviteProgress.value = Async.Success(Unit) + inviteProgress.value = AsyncData.Success(Unit) }, onFailure = { - inviteProgress.value = Async.Failure(it) + inviteProgress.value = AsyncData.Failure(it) } ) } @@ -349,6 +351,12 @@ class MessagesPresenter @AssistedInject constructor( type = AttachmentThumbnailType.Image, blurHash = targetEvent.content.blurhash, ) + is TimelineItemStickerContent -> AttachmentThumbnailInfo( + thumbnailSource = targetEvent.content.thumbnailSource ?: targetEvent.content.mediaSource, + textContent = targetEvent.content.body, + type = AttachmentThumbnailType.Image, + blurHash = targetEvent.content.blurhash, + ) is TimelineItemVideoContent -> AttachmentThumbnailInfo( thumbnailSource = targetEvent.content.thumbnailSource, textContent = targetEvent.content.body, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt index 7d342ab107..c196535607 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt @@ -25,7 +25,7 @@ import io.element.android.features.messages.impl.timeline.components.reactionsum import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuState import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.core.RoomId @@ -33,10 +33,11 @@ import io.element.android.libraries.matrix.api.core.RoomId @Immutable data class MessagesState( val roomId: RoomId, - val roomName: Async, - val roomAvatar: Async, + val roomName: AsyncData, + val roomAvatar: AsyncData, val userHasPermissionToSendMessage: Boolean, val userHasPermissionToRedact: Boolean, + val userHasPermissionToSendReaction: Boolean, val composerState: MessageComposerState, val voiceMessageComposerState: VoiceMessageComposerState, val timelineState: TimelineState, @@ -47,7 +48,7 @@ data class MessagesState( val readReceiptBottomSheetState: ReadReceiptBottomSheetState, val hasNetworkConnection: Boolean, val snackbarMessage: SnackbarMessage?, - val inviteProgress: Async, + val inviteProgress: AsyncData, val showReinvitePrompt: Boolean, val enableTextFormatting: Boolean, val enableVoiceMessages: Boolean, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index 00c15fb410..8fc7466f95 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -29,7 +29,7 @@ import io.element.android.features.messages.impl.timeline.components.retrysendme import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessagePreviewState -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.core.RoomId @@ -47,8 +47,8 @@ open class MessagesStateProvider : PreviewParameterProvider { aMessagesState().copy(userHasPermissionToSendMessage = false), aMessagesState().copy(showReinvitePrompt = true), aMessagesState().copy( - roomName = Async.Uninitialized, - roomAvatar = Async.Uninitialized, + roomName = AsyncData.Uninitialized, + roomAvatar = AsyncData.Uninitialized, ), aMessagesState().copy(composerState = aMessageComposerState().copy(showTextFormatting = true)), aMessagesState().copy( @@ -83,10 +83,11 @@ open class MessagesStateProvider : PreviewParameterProvider { fun aMessagesState() = MessagesState( roomId = RoomId("!id:domain"), - roomName = Async.Success("Room name"), - roomAvatar = Async.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)), + roomName = AsyncData.Success("Room name"), + roomAvatar = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)), userHasPermissionToSendMessage = true, userHasPermissionToRedact = false, + userHasPermissionToSendReaction = true, composerState = aMessageComposerState().copy( richTextEditorState = RichTextEditorState("Hello", initialFocus = true), isFullScreen = false, @@ -116,7 +117,7 @@ fun aMessagesState() = MessagesState( ), hasNetworkConnection = true, snackbarMessage = null, - inviteProgress = Async.Uninitialized, + inviteProgress = AsyncData.Uninitialized, showReinvitePrompt = false, enableTextFormatting = true, enableVoiceMessages = true, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index dd728aa274..96af343a5a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -117,7 +117,7 @@ fun MessagesView( state: MessagesState, onBackPressed: () -> Unit, onRoomDetailsClicked: () -> Unit, - onEventClicked: (event: TimelineItem.Event) -> Unit, + onEventClicked: (event: TimelineItem.Event) -> Boolean, onUserDataClicked: (UserId) -> Unit, onPreviewAttachments: (ImmutableList) -> Unit, onSendLocationClicked: () -> Unit, @@ -148,7 +148,10 @@ fun MessagesView( fun onMessageClicked(event: TimelineItem.Event) { Timber.v("OnMessageClicked= ${event.id}") - onEventClicked(event) + val hideKeyboard = onEventClicked(event) + if (hideKeyboard) { + localView.hideKeyboard() + } } fun onMessageLongClicked(event: TimelineItem.Event) { @@ -159,6 +162,7 @@ fun MessagesView( event = event, canRedact = state.userHasPermissionToRedact, canSendMessage = state.userHasPermissionToSendMessage, + canSendReaction = state.userHasPermissionToSendReaction, ) ) } @@ -568,7 +572,7 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class) state = state, onBackPressed = {}, onRoomDetailsClicked = {}, - onEventClicked = {}, + onEventClicked = { false }, onPreviewAttachments = {}, onUserDataClicked = {}, onSendLocationClicked = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt index 7c8fad6c7c..6339716ccf 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt @@ -24,5 +24,6 @@ sealed interface ActionListEvents { val event: TimelineItem.Event, val canRedact: Boolean, val canSendMessage: Boolean, + val canSendReaction: Boolean, ) : ActionListEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt index d936ea9af8..474a0cf022 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt @@ -19,7 +19,6 @@ package io.element.android.features.messages.impl.actionlist import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -42,7 +41,6 @@ import javax.inject.Inject class ActionListPresenter @Inject constructor( private val preferencesStore: PreferencesStore, ) : Presenter { - @Composable override fun present(): ActionListState { val localCoroutineScope = rememberCoroutineScope() @@ -53,13 +51,6 @@ class ActionListPresenter @Inject constructor( val isDeveloperModeEnabled by preferencesStore.isDeveloperModeEnabledFlow().collectAsState(initial = false) - val displayEmojiReactions by remember { - derivedStateOf { - val event = (target.value as? ActionListState.Target.Success)?.event - event?.isRemote == true && event.content.canReact() - } - } - fun handleEvents(event: ActionListEvents) { when (event) { ActionListEvents.Clear -> target.value = ActionListState.Target.None @@ -67,6 +58,7 @@ class ActionListPresenter @Inject constructor( timelineItem = event.event, userCanRedact = event.canRedact, userCanSendMessage = event.canSendMessage, + userCanSendReaction = event.canSendReaction, isDeveloperModeEnabled = isDeveloperModeEnabled, target = target, ) @@ -75,7 +67,6 @@ class ActionListPresenter @Inject constructor( return ActionListState( target = target.value, - displayEmojiReactions = displayEmojiReactions, eventSink = { handleEvents(it) } ) } @@ -84,6 +75,7 @@ class ActionListPresenter @Inject constructor( timelineItem: TimelineItem.Event, userCanRedact: Boolean, userCanSendMessage: Boolean, + userCanSendReaction: Boolean, isDeveloperModeEnabled: Boolean, target: MutableState ) = launch { @@ -159,7 +151,11 @@ class ActionListPresenter @Inject constructor( add(TimelineItemAction.Reply) } } - add(TimelineItemAction.Forward) + // Stickers can't be forwarded (yet) so we don't show the option + // See https://github.com/element-hq/element-x-android/issues/2161 + if (!timelineItem.isSticker) { + add(TimelineItemAction.Forward) + } } if (timelineItem.isMine && timelineItem.isTextMessage) { add(TimelineItemAction.Edit) @@ -178,8 +174,15 @@ class ActionListPresenter @Inject constructor( } } } - if (actions.isNotEmpty()) { - target.value = ActionListState.Target.Success(timelineItem, actions.toImmutableList()) + val displayEmojiReactions = userCanSendReaction && + timelineItem.isRemote && + timelineItem.content.canReact() + if (actions.isNotEmpty() || displayEmojiReactions) { + target.value = ActionListState.Target.Success( + event = timelineItem, + displayEmojiReactions = displayEmojiReactions, + actions = actions.toImmutableList() + ) } else { target.value = ActionListState.Target.None } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt index a8fbf81486..5566f949d0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt @@ -24,7 +24,6 @@ import kotlinx.collections.immutable.ImmutableList @Immutable data class ActionListState( val target: Target, - val displayEmojiReactions: Boolean, val eventSink: (ActionListEvents) -> Unit, ) { sealed interface Target { @@ -32,6 +31,7 @@ data class ActionListState( data class Loading(val event: TimelineItem.Event) : Target data class Success( val event: TimelineItem.Event, + val displayEmojiReactions: Boolean, val actions: ImmutableList, ) : Target } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt index 3ea99aaede..e33f54de3e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt @@ -42,6 +42,7 @@ open class ActionListStateProvider : PreviewParameterProvider { event = aTimelineItemEvent().copy( reactionsState = reactionsState ), + displayEmojiReactions = true, actions = aTimelineItemActionList(), ) ), @@ -50,6 +51,7 @@ open class ActionListStateProvider : PreviewParameterProvider { event = aTimelineItemEvent(content = aTimelineItemImageContent()).copy( reactionsState = reactionsState ), + displayEmojiReactions = true, actions = aTimelineItemActionList(), ) ), @@ -58,6 +60,7 @@ open class ActionListStateProvider : PreviewParameterProvider { event = aTimelineItemEvent(content = aTimelineItemVideoContent()).copy( reactionsState = reactionsState ), + displayEmojiReactions = true, actions = aTimelineItemActionList(), ) ), @@ -66,6 +69,7 @@ open class ActionListStateProvider : PreviewParameterProvider { event = aTimelineItemEvent(content = aTimelineItemFileContent()).copy( reactionsState = reactionsState ), + displayEmojiReactions = true, actions = aTimelineItemActionList(), ) ), @@ -74,6 +78,7 @@ open class ActionListStateProvider : PreviewParameterProvider { event = aTimelineItemEvent(content = aTimelineItemAudioContent()).copy( reactionsState = reactionsState ), + displayEmojiReactions = true, actions = aTimelineItemActionList(), ) ), @@ -82,6 +87,7 @@ open class ActionListStateProvider : PreviewParameterProvider { event = aTimelineItemEvent(content = aTimelineItemVoiceContent()).copy( reactionsState = reactionsState ), + displayEmojiReactions = true, actions = aTimelineItemActionList(), ) ), @@ -90,6 +96,7 @@ open class ActionListStateProvider : PreviewParameterProvider { event = aTimelineItemEvent(content = aTimelineItemLocationContent()).copy( reactionsState = reactionsState ), + displayEmojiReactions = true, actions = aTimelineItemActionList(), ) ), @@ -98,18 +105,18 @@ open class ActionListStateProvider : PreviewParameterProvider { event = aTimelineItemEvent(content = aTimelineItemLocationContent()).copy( reactionsState = reactionsState ), + displayEmojiReactions = false, actions = aTimelineItemActionList(), ), - displayEmojiReactions = false, ), anActionListState().copy( target = ActionListState.Target.Success( event = aTimelineItemEvent(content = aTimelineItemPollContent()).copy( reactionsState = reactionsState ), + displayEmojiReactions = false, actions = aTimelineItemPollActionList(), ), - displayEmojiReactions = false, ), ) } @@ -117,7 +124,6 @@ open class ActionListStateProvider : PreviewParameterProvider { fun anActionListState() = ActionListState( target = ActionListState.Target.None, - displayEmojiReactions = true, eventSink = {} ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt index c7b72ce527..3db91fd67c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt @@ -63,6 +63,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent @@ -171,15 +172,16 @@ private fun SheetContent( item { Column { MessageSummary( - event = target.event, modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) + event = target.event, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) ) Spacer(modifier = Modifier.height(14.dp)) HorizontalDivider() } } - if (state.displayEmojiReactions) { + if (target.displayEmojiReactions) { item { EmojiReactionsRow( highlightedEmojis = target.event.reactionsState.highlightedKeys, @@ -239,6 +241,9 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif is TimelineItemImageContent -> { content = { ContentForBody(event.content.body) } } + is TimelineItemStickerContent -> { + content = { ContentForBody(event.content.body) } + } is TimelineItemVideoContent -> { content = { ContentForBody(event.content.body) } } @@ -295,7 +300,11 @@ private fun EmojiReactionsRow( ) { // TODO use most recently used emojis here when available from the Rust SDK val defaultEmojis = sequenceOf( - "👍️", "👎️", "🔥", "❤️", "👏" + "👍️", + "👎️", + "🔥", + "❤️", + "👏" ) for (emoji in defaultEmojis) { val isHighlighted = highlightedEmojis.contains(emoji) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/Attachment.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/Attachment.kt index 55c8033636..2e500a6b53 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/Attachment.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/Attachment.kt @@ -23,7 +23,6 @@ import kotlinx.parcelize.Parcelize @Immutable sealed interface Attachment : Parcelable { - @Parcelize data class Media(val localMedia: LocalMedia, val compressIfPossible: Boolean) : Attachment } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewNode.kt index 2ca04ecf19..254642b62c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewNode.kt @@ -36,7 +36,6 @@ class AttachmentsPreviewNode @AssistedInject constructor( @Assisted plugins: List, presenterFactory: AttachmentsPreviewPresenter.Factory, ) : Node(buildContext, plugins = plugins) { - data class Inputs(val attachment: Attachment) : NodeInputs private val inputs: Inputs = inputs() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt index 28d403c9a6..2c5a3ccf08 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt @@ -40,7 +40,6 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( @Assisted private val attachment: Attachment, private val mediaSender: MediaSender, ) : Presenter { - @AssistedFactory interface Factory { fun create(attachment: Attachment): AttachmentsPreviewPresenter @@ -48,7 +47,6 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( @Composable override fun present(): AttachmentsPreviewState { - val coroutineScope = rememberCoroutineScope() val sendActionState = remember { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt index 183e1ea590..ad44a1368e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt @@ -34,4 +34,3 @@ sealed interface SendActionState { data class Failure(val error: Throwable) : SendActionState data object Done : SendActionState } - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt index 37b2ee9c78..1fdc52cf99 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt @@ -36,7 +36,8 @@ open class AttachmentsPreviewStateProvider : PreviewParameterProvider Unit, modifier: Modifier = Modifier, ) { - fun postSendAttachment() { state.eventSink(AttachmentsPreviewEvents.SendAttachment) } @@ -88,7 +87,6 @@ private fun AttachmentSendStateView( onDismissClicked: () -> Unit, onRetryClicked: () -> Unit ) { - when (sendActionState) { is SendActionState.Sending -> { ProgressDialog( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesNode.kt index 9d57f8e2fc..4f58cfa274 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesNode.kt @@ -53,7 +53,6 @@ class ForwardMessagesNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins, ) { - @Parcelize object NavTarget : Parcelable diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt index f61f0f7b52..367a071084 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt @@ -25,7 +25,7 @@ import androidx.compose.runtime.remember import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId @@ -40,7 +40,6 @@ class ForwardMessagesPresenter @AssistedInject constructor( private val room: MatrixRoom, private val matrixCoroutineScope: CoroutineScope, ) : Presenter { - private val eventId: EventId = EventId(eventId) @AssistedFactory @@ -48,7 +47,7 @@ class ForwardMessagesPresenter @AssistedInject constructor( fun create(eventId: String): ForwardMessagesPresenter } - private val forwardingActionState: MutableState>> = mutableStateOf(Async.Uninitialized) + private val forwardingActionState: MutableState>> = mutableStateOf(AsyncData.Uninitialized) fun onRoomSelected(roomIds: List) { matrixCoroutineScope.forwardEvent(eventId, roomIds.toPersistentList(), forwardingActionState) @@ -62,13 +61,13 @@ class ForwardMessagesPresenter @AssistedInject constructor( fun handleEvents(event: ForwardMessagesEvents) { when (event) { - ForwardMessagesEvents.ClearError -> forwardingActionState.value = Async.Uninitialized + ForwardMessagesEvents.ClearError -> forwardingActionState.value = AsyncData.Uninitialized } } return ForwardMessagesState( isForwarding = forwardingActionState.value.isLoading(), - error = (forwardingActionState.value as? Async.Failure)?.error, + error = (forwardingActionState.value as? AsyncData.Failure)?.error, forwardingSucceeded = forwardingSucceeded, eventSink = { handleEvents(it) } ) @@ -77,12 +76,12 @@ class ForwardMessagesPresenter @AssistedInject constructor( private fun CoroutineScope.forwardEvent( eventId: EventId, roomIds: ImmutableList, - isForwardMessagesState: MutableState>>, + isForwardMessagesState: MutableState>>, ) = launch { - isForwardMessagesState.value = Async.Loading() + isForwardMessagesState.value = AsyncData.Loading() room.forwardEvent(eventId, roomIds).fold( - { isForwardMessagesState.value = Async.Success(roomIds) }, - { isForwardMessagesState.value = Async.Failure(it) } + { isForwardMessagesState.value = AsyncData.Success(roomIds) }, + { isForwardMessagesState.value = AsyncData.Failure(it) } ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsProcessor.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsProcessor.kt index d2c3e6e518..696f0fe93e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsProcessor.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsProcessor.kt @@ -29,7 +29,6 @@ import io.element.android.libraries.textcomposer.model.SuggestionType * This class is responsible for processing mention suggestions when `@`, `/` or `#` are type in the composer. */ object MentionSuggestionsProcessor { - // We don't want to retrieve thousands of members private const val MAX_BATCH_ITEMS = 100 @@ -88,8 +87,8 @@ object MentionSuggestionsProcessor { } fun memberMatchesQuery(member: RoomMember, query: String): Boolean { - return member.userId.value.contains(query, ignoreCase = true) - || member.displayName?.contains(query, ignoreCase = true) == true + return member.userId.value.contains(query, ignoreCase = true) || + member.displayName?.contains(query, ignoreCase = true) == true } val matchingMembers = roomMembers diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt index b3b53db013..af268856e9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt @@ -62,12 +62,12 @@ internal fun AttachmentsBottomSheet( } LaunchedEffect(state.showAttachmentSourcePicker) { - if (state.showAttachmentSourcePicker) { + isVisible = if (state.showAttachmentSourcePicker) { // We need to use this instead of `LocalFocusManager.clearFocus()` to hide the keyboard when focus is on an Android View localView.hideKeyboard() - isVisible = true + true } else { - isVisible = false + false } } // Send 'DismissAttachmentMenu' event when the bottomsheet was just hidden @@ -122,7 +122,7 @@ private fun AttachmentSourcePickerMenu( ) ListItem( modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) }, - leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_take_photo_camera, )), + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_take_photo_camera)), headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_camera_photo)) }, style = ListItemStyle.Primary, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt index c8ce20b6b9..4f5c2aba10 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt @@ -16,6 +16,7 @@ package io.element.android.features.messages.impl.messagecomposer +import android.net.Uri import androidx.compose.runtime.Immutable import io.element.android.features.messages.impl.mentions.MentionSuggestion import io.element.android.libraries.textcomposer.model.Message @@ -26,6 +27,7 @@ import io.element.android.libraries.textcomposer.model.Suggestion sealed interface MessageComposerEvents { data object ToggleFullScreenState : MessageComposerEvents data class SendMessage(val message: Message) : MessageComposerEvents + data class SendUri(val uri: Uri) : MessageComposerEvents data object CloseSpecialMode : MessageComposerEvents data class SetMode(val composerMode: MessageComposerMode) : MessageComposerEvents data object AddAttachment : MessageComposerEvents diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 38ba51fa72..6044b1abe7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -94,7 +94,6 @@ class MessageComposerPresenter @Inject constructor( private val currentSessionIdHolder: CurrentSessionIdHolder, permissionsPresenterFactory: PermissionsPresenter.Factory, ) : Presenter { - private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA) private var pendingEvent: MessageComposerEvents? = null @@ -184,15 +183,14 @@ class MessageComposerPresenter @Inject constructor( val currentUserId = currentSessionIdHolder.current suspend fun canSendRoomMention(): Boolean { - val roomIsDm = room.isDirect && room.isOneToOne val userCanSendAtRoom = room.canUserTriggerRoomNotification(currentUserId).getOrDefault(false) - return !roomIsDm && userCanSendAtRoom + return !room.isDm && userCanSendAtRoom } // This will trigger a search immediately when `@` is typed val mentionStartTrigger = suggestionSearchTrigger.filter { it?.text.isNullOrEmpty() } // This will start a search when the user changes the text after the `@` with a debounce to prevent too much wasted work - val mentionCompletionTrigger = suggestionSearchTrigger.filter { !it?.text.isNullOrEmpty() }.debounce(0.3.seconds) + val mentionCompletionTrigger = suggestionSearchTrigger.debounce(0.3.seconds).filter { !it?.text.isNullOrEmpty() } merge(mentionStartTrigger, mentionCompletionTrigger) .combine(room.membersStateFlow) { suggestion, roomMembersState -> memberSuggestions.clear() @@ -225,6 +223,18 @@ class MessageComposerPresenter @Inject constructor( updateComposerMode = { messageComposerContext.composerMode = it }, richTextEditorState = richTextEditorState, ) + is MessageComposerEvents.SendUri -> appCoroutineScope.sendAttachment( + attachment = Attachment.Media( + localMedia = localMediaFactory.createFromUri( + uri = event.uri, + mimeType = null, + name = null, + formattedFileSize = null + ), + compressIfPossible = true + ), + attachmentState = attachmentsState, + ) is MessageComposerEvents.SetMode -> { messageComposerContext.composerMode = event.composerMode when (event.composerMode) { @@ -364,7 +374,8 @@ class MessageComposerPresenter @Inject constructor( inThread = capturedMode.inThread, isEditing = capturedMode.isEditing, isReply = capturedMode.isReply, - messageType = Composer.MessageType.Text, // Set proper type when we'll be sending other types of messages. + // Set proper type when we'll be sending other types of messages. + messageType = Composer.MessageType.Text, ) ) } @@ -444,4 +455,3 @@ class MessageComposerPresenter @Inject constructor( } } } - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt index 28e40a910f..47b5adea5e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt @@ -16,6 +16,7 @@ package io.element.android.features.messages.impl.messagecomposer +import android.net.Uri import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.height @@ -52,6 +53,10 @@ internal fun MessageComposerView( state.eventSink(MessageComposerEvents.SendMessage(message)) } + fun sendUri(uri: Uri) { + state.eventSink(MessageComposerEvents.SendUri(uri)) + } + fun onAddAttachment() { state.eventSink(MessageComposerEvents.AddAttachment) } @@ -117,6 +122,7 @@ internal fun MessageComposerView( onSuggestionReceived = ::onSuggestionReceived, onError = ::onError, currentUserId = state.currentUserId, + onRichContentSelected = ::sendUri, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/RichTextEditorStateFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/RichTextEditorStateFactory.kt index 00b39c47e2..52fff81c31 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/RichTextEditorStateFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/RichTextEditorStateFactory.kt @@ -35,4 +35,3 @@ class DefaultRichTextEditorStateFactory @Inject constructor() : RichTextEditorSt return rememberRichTextEditorState() } } - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageNode.kt index 1be4571161..a1157e2830 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageNode.kt @@ -36,7 +36,6 @@ class ReportMessageNode @AssistedInject constructor( @Assisted plugins: List, presenterFactory: ReportMessagePresenter.Factory, ) : Node(buildContext, plugins = plugins) { - data class Inputs( val eventId: EventId, val senderId: UserId, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenter.kt index d474e3d25f..ae7f780b14 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenter.kt @@ -27,7 +27,7 @@ import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher @@ -44,7 +44,6 @@ class ReportMessagePresenter @AssistedInject constructor( @Assisted private val inputs: Inputs, private val snackbarDispatcher: SnackbarDispatcher, ) : Presenter { - data class Inputs( val eventId: EventId, val senderId: UserId, @@ -60,14 +59,14 @@ class ReportMessagePresenter @AssistedInject constructor( val coroutineScope = rememberCoroutineScope() var reason by rememberSaveable { mutableStateOf("") } var blockUser by rememberSaveable { mutableStateOf(false) } - var result: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + var result: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } fun handleEvents(event: ReportMessageEvents) { when (event) { is ReportMessageEvents.UpdateReason -> reason = event.reason ReportMessageEvents.ToggleBlockUser -> blockUser = !blockUser ReportMessageEvents.Report -> coroutineScope.report(inputs.eventId, inputs.senderId, reason, blockUser, result) - ReportMessageEvents.ClearError -> result.value = Async.Uninitialized + ReportMessageEvents.ClearError -> result.value = AsyncAction.Uninitialized } } @@ -84,7 +83,7 @@ class ReportMessagePresenter @AssistedInject constructor( userId: UserId, reason: String, blockUser: Boolean, - result: MutableState>, + result: MutableState>, ) = launch { result.runUpdatingState { val userIdToBlock = userId.takeIf { blockUser } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageState.kt index 809668c88f..71afa43031 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageState.kt @@ -16,11 +16,11 @@ package io.element.android.features.messages.impl.report -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction data class ReportMessageState( val reason: String, val blockUser: Boolean, - val result: Async, + val result: AsyncAction, val eventSink: (ReportMessageEvents) -> Unit ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageStateProvider.kt index de5e787a3d..52f0948264 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageStateProvider.kt @@ -17,7 +17,7 @@ package io.element.android.features.messages.impl.report import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction open class ReportMessageStateProvider : PreviewParameterProvider { override val values: Sequence @@ -25,9 +25,9 @@ open class ReportMessageStateProvider : PreviewParameterProvider = Async.Uninitialized, + result: AsyncAction = AsyncAction.Uninitialized, ) = ReportMessageState( reason = reason, blockUser = blockUser, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt index 8ec2b8be03..2207c81750 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt @@ -18,7 +18,6 @@ package io.element.android.features.messages.impl.report import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets @@ -42,8 +41,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.features.messages.impl.R -import io.element.android.libraries.architecture.Async -import io.element.android.libraries.designsystem.components.async.AsyncView +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -55,7 +54,7 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.ui.strings.CommonStrings -@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ReportMessageView( state: ReportMessageState, @@ -63,10 +62,10 @@ fun ReportMessageView( modifier: Modifier = Modifier, ) { val focusManager = LocalFocusManager.current - val isSending = state.result is Async.Loading - AsyncView( + val isSending = state.result is AsyncAction.Loading + AsyncActionView( async = state.result, - showProgressDialog = false, + progressDialog = {}, onSuccess = { onBackClicked() }, errorMessage = { stringResource(CommonStrings.error_unknown) }, onErrorDismiss = { state.eventSink(ReportMessageEvents.ClearError) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt index 0afe57b9e5..aefadcbb25 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt @@ -39,15 +39,14 @@ import javax.inject.Inject @ContributesBinding(SessionScope::class) @SingleIn(SessionScope::class) -class DefaultHtmlConverterProvider @Inject constructor(): HtmlConverterProvider { - +class DefaultHtmlConverterProvider @Inject constructor() : HtmlConverterProvider { private val htmlConverter: MutableState = mutableStateOf(null) @Composable override fun Update(currentUserId: UserId) { val isInEditMode = LocalInspectionMode.current val mentionDetector = remember(isInEditMode) { - if (isInEditMode) { null } else { newMentionDetector() } + if (isInEditMode) null else newMentionDetector() } val editorStyle = ElementRichTextEditorStyle.textStyle() @@ -67,7 +66,7 @@ class DefaultHtmlConverterProvider @Inject constructor(): HtmlConverterProvider return TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor(text, url)) } }, - isMention = { _, url -> mentionDetector?.isMention(url).orFalse() } + isMention = { _, url -> mentionDetector?.isMention(url).orFalse() } ).apply { configureWith(editorStyle) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index 8613f2404e..85b555fa7c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -40,8 +40,6 @@ import io.element.android.features.poll.api.actions.EndPollAction import io.element.android.features.poll.api.actions.SendPollResponseAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.featureflag.api.FeatureFlagService -import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.EncryptionService @@ -71,12 +69,10 @@ class TimelinePresenter @AssistedInject constructor( @Assisted private val navigator: MessagesNavigator, private val verificationService: SessionVerificationService, private val encryptionService: EncryptionService, - private val featureFlagService: FeatureFlagService, private val redactedVoiceMessageManager: RedactedVoiceMessageManager, private val sendPollResponseAction: SendPollResponseAction, private val endPollAction: EndPollAction, ) : Presenter { - @AssistedFactory interface Factory { fun create(navigator: MessagesNavigator): TimelinePresenter @@ -98,6 +94,7 @@ class TimelinePresenter @AssistedInject constructor( val paginationState by timeline.paginationState.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value) + val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION_SENT, updateKey = syncUpdateFlow.value) val prevMostRecentItemId = rememberSaveable { mutableStateOf(null) } val newItemState = remember { mutableStateOf(NewEventState.None) } @@ -114,7 +111,6 @@ class TimelinePresenter @AssistedInject constructor( } } - val readReceiptsEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.ReadReceipts).collectAsState(initial = false) val membersState by room.membersStateFlow.collectAsState() fun handleEvents(event: TimelineEvents) { @@ -158,12 +154,7 @@ class TimelinePresenter @AssistedInject constructor( .onEach { timelineItemsFactory.replaceWith( timelineItems = it, - roomMembers = if (readReceiptsEnabled) { - membersState.roomMembers().orEmpty() - } else { - // Give an empty list to not affect performance - emptyList() - } + roomMembers = membersState.roomMembers().orEmpty() ) } .onEach { timelineItems -> @@ -175,15 +166,20 @@ class TimelinePresenter @AssistedInject constructor( .launchIn(this) } + val timelineRoomInfo by remember { + derivedStateOf { + TimelineRoomInfo( + isDirect = room.isDirect, + userHasPermissionToSendMessage = userHasPermissionToSendMessage, + userHasPermissionToSendReaction = userHasPermissionToSendReaction, + ) + } + } return TimelineState( - timelineRoomInfo = TimelineRoomInfo( - isDirect = room.isDirect - ), + timelineRoomInfo = timelineRoomInfo, highlightedEventId = highlightedEventId.value, - userHasPermissionToSendMessage = userHasPermissionToSendMessage, paginationState = paginationState, timelineItems = timelineItems, - showReadReceipts = readReceiptsEnabled, newEventState = newItemState.value, sessionState = sessionState, eventSink = { handleEvents(it) } @@ -240,7 +236,8 @@ class TimelinePresenter @AssistedInject constructor( } private fun getLastEventIdBeforeOrAt(index: Int, items: ImmutableList): EventId? { - for (item in items.subList(index, items.count())) { + for (i in index until items.count()) { + val item = items[i] if (item is TimelineItem.Event) { return item.eventId } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt index d334aebf8c..ef25d94ebc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt @@ -28,9 +28,7 @@ import kotlinx.collections.immutable.ImmutableList data class TimelineState( val timelineItems: ImmutableList, val timelineRoomInfo: TimelineRoomInfo, - val showReadReceipts: Boolean, val highlightedEventId: EventId?, - val userHasPermissionToSendMessage: Boolean, val paginationState: MatrixTimeline.PaginationState, val newEventState: NewEventState, val sessionState: SessionState, @@ -40,4 +38,6 @@ data class TimelineState( @Immutable data class TimelineRoomInfo( val isDirect: Boolean, + val userHasPermissionToSendMessage: Boolean, + val userHasPermissionToSendReaction: Boolean, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index 63d8f0ed01..3012a095e8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -48,14 +48,12 @@ import kotlin.random.Random fun aTimelineState(timelineItems: ImmutableList = persistentListOf()) = TimelineState( timelineItems = timelineItems, timelineRoomInfo = aTimelineRoomInfo(), - showReadReceipts = false, paginationState = MatrixTimeline.PaginationState( isBackPaginating = false, hasMoreToLoadBackwards = true, beginningOfRoomReached = false, ), highlightedEventId = null, - userHasPermissionToSendMessage = true, newEventState = NewEventState.None, sessionState = aSessionState( isSessionVerified = true, @@ -180,7 +178,9 @@ internal fun aTimelineItemDebugInfo( originalJson: String? = null, latestEditedJson: String? = null, ) = TimelineItemDebugInfo( - model, originalJson, latestEditedJson + model, + originalJson, + latestEditedJson ) internal fun aTimelineItemReadReceipts(): TimelineItemReadReceipts { @@ -189,13 +189,16 @@ internal fun aTimelineItemReadReceipts(): TimelineItemReadReceipts { ) } -internal fun aGroupedEvents(id: Long = 0): TimelineItem.GroupedEvents { +internal fun aGroupedEvents( + id: Long = 0, + withReadReceipts: Boolean = false, +): TimelineItem.GroupedEvents { val event1 = aTimelineItemEvent( isMine = true, content = aTimelineItemStateEventContent(), groupPosition = TimelineItemGroupPosition.None, readReceiptState = TimelineItemReadReceipts( - receipts = listOf(aReadReceiptData(0)).toPersistentList(), + receipts = (if (withReadReceipts) listOf(aReadReceiptData(0)) else emptyList()).toImmutableList() ), ) val event2 = aTimelineItemEvent( @@ -203,7 +206,7 @@ internal fun aGroupedEvents(id: Long = 0): TimelineItem.GroupedEvents { content = aTimelineItemStateEventContent(body = "Another state event"), groupPosition = TimelineItemGroupPosition.None, readReceiptState = TimelineItemReadReceipts( - receipts = listOf(aReadReceiptData(1)).toPersistentList(), + receipts = (if (withReadReceipts) listOf(aReadReceiptData(1)) else emptyList()).toImmutableList() ), ) val events = listOf(event1, event2) @@ -218,4 +221,6 @@ internal fun aTimelineRoomInfo( isDirect: Boolean = false, ) = TimelineRoomInfo( isDirect = isDirect, + userHasPermissionToSendMessage = true, + userHasPermissionToSendReaction = true, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index bc285b0bc5..4f686e594b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -119,11 +119,9 @@ fun TimelineView( TimelineItemRow( timelineItem = timelineItem, timelineRoomInfo = state.timelineRoomInfo, - showReadReceipts = state.showReadReceipts, - isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true - && state.timelineItems.first().identifier() == timelineItem.identifier(), + isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true && + state.timelineItems.first().identifier() == timelineItem.identifier(), highlightedItem = state.highlightedEventId?.value, - userHasPermissionToSendMessage = state.userHasPermissionToSendMessage, onClick = onMessageClicked, onLongClick = onMessageLongClicked, onUserDataClick = onUserDataClicked, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt index a2a0ae5f4e..a7c860f46e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt @@ -26,16 +26,13 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem internal fun ATimelineItemEventRow( event: TimelineItem.Event, timelineRoomInfo: TimelineRoomInfo = aTimelineRoomInfo(), - showReadReceipts: Boolean = false, isLastOutgoingMessage: Boolean = false, isHighlighted: Boolean = false, ) = TimelineItemEventRow( event = event, timelineRoomInfo = timelineRoomInfo, - showReadReceipts = showReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = isHighlighted, - canReply = true, onClick = {}, onLongClick = {}, onUserDataClick = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt index d80bde4cf4..9b340eee67 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt @@ -24,7 +24,9 @@ import androidx.compose.foundation.border import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -41,6 +43,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage +import io.element.android.compound.theme.ElementTheme import io.element.android.features.messages.impl.R import io.element.android.features.messages.impl.timeline.model.AggregatedReaction import io.element.android.features.messages.impl.timeline.model.AggregatedReactionProvider @@ -52,7 +56,8 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Surface import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.CommonDrawables -import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.matrix.ui.media.MediaRequestData @Composable @OptIn(ExperimentalFoundationApi::class) @@ -114,8 +119,9 @@ sealed interface MessagesReactionsButtonContent { val isHighlighted get() = this is Reaction && reaction.isHighlighted } -private val reactionEmojiLineHeight = 20.sp -private val addEmojiSize = 16.dp +internal val REACTION_EMOJI_LINE_HEIGHT = 20.sp +internal const val REACTION_IMAGE_ASPECT_RATIO = 1.0f +private val ADD_EMOJI_SIZE = 16.dp @Composable private fun TextContent( @@ -123,7 +129,7 @@ private fun TextContent( modifier: Modifier = Modifier, ) = Text( modifier = modifier - .height(reactionEmojiLineHeight.toDp()), + .height(REACTION_EMOJI_LINE_HEIGHT.toDp()), text = text, style = ElementTheme.typography.fontBodyMdRegular, color = ElementTheme.materialColors.primary @@ -138,8 +144,7 @@ private fun IconContent( contentDescription = stringResource(id = R.string.screen_room_timeline_add_reaction), tint = ElementTheme.materialColors.secondary, modifier = modifier - .size(addEmojiSize) - + .size(ADD_EMOJI_SIZE) ) @Composable @@ -150,13 +155,24 @@ private fun ReactionContent( verticalAlignment = Alignment.CenterVertically, modifier = modifier, ) { - Text( - text = reaction.displayKey, - style = ElementTheme.typography.fontBodyMdRegular.copy( - fontSize = 15.sp, - lineHeight = reactionEmojiLineHeight, - ), - ) + // Check if this is a custom reaction (MSC4027) + if (reaction.key.startsWith("mxc://")) { + AsyncImage( + modifier = modifier + .heightIn(min = REACTION_EMOJI_LINE_HEIGHT.toDp(), max = REACTION_EMOJI_LINE_HEIGHT.toDp()) + .aspectRatio(REACTION_IMAGE_ASPECT_RATIO, false), + model = MediaRequestData(MediaSource(reaction.key), MediaRequestData.Kind.Content), + contentDescription = null + ) + } else { + Text( + text = reaction.displayKey, + style = ElementTheme.typography.fontBodyMdRegular.copy( + fontSize = 15.sp, + lineHeight = REACTION_EMOJI_LINE_HEIGHT, + ), + ) + } if (reaction.count > 1) { Spacer(modifier = Modifier.width(4.dp)) Text( @@ -207,4 +223,3 @@ internal fun MessagesReactionExtraButtonsPreview() = ElementPreview { ) } } - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ReplySwipeIndicator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ReplySwipeIndicator.kt index 17d1d11f83..1fcfd30182 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ReplySwipeIndicator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ReplySwipeIndicator.kt @@ -25,8 +25,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.utils.CommonDrawables diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt index 3451c1590c..8283380655 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt @@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding @@ -69,7 +70,8 @@ fun TimelineEventTimestampView( Row( modifier = Modifier .then(clickModifier) - .padding(start = 16.dp) // Add extra padding for touch target size + // Add extra padding for touch target size + .padding(PaddingValues(start = TimelineEventTimestampViewDefaults.spacing)) .then(modifier), verticalAlignment = Alignment.CenterVertically, ) { @@ -107,3 +109,7 @@ internal fun TimelineEventTimestampViewPreview(@PreviewParameter(TimelineItemEve onLongClick = {}, ) } + +object TimelineEventTimestampViewDefaults { + val spacing = 16.dp +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index d22cfedae7..3e23114729 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -56,6 +56,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex @@ -67,7 +68,8 @@ import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView -import io.element.android.features.messages.impl.timeline.components.event.toExtraPadding +import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout +import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView import io.element.android.features.messages.impl.timeline.model.InReplyToDetails @@ -78,9 +80,12 @@ import io.element.android.features.messages.impl.timeline.model.bubble.BubbleSta import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent +import io.element.android.features.messages.impl.timeline.model.event.canBeRepliedTo import io.element.android.features.messages.impl.timeline.model.metadata import io.element.android.libraries.androidutils.system.openUrlInExternalApp import io.element.android.libraries.designsystem.colors.AvatarColorsProvider @@ -109,10 +114,8 @@ import kotlin.math.roundToInt fun TimelineItemEventRow( event: TimelineItem.Event, timelineRoomInfo: TimelineRoomInfo, - showReadReceipts: Boolean, isLastOutgoingMessage: Boolean, isHighlighted: Boolean, - canReply: Boolean, onClick: () -> Unit, onLongClick: () -> Unit, onUserDataClick: (UserId) -> Unit, @@ -151,6 +154,7 @@ fun TimelineItemEventRow( } else { Spacer(modifier = Modifier.height(2.dp)) } + val canReply = timelineRoomInfo.userHasPermissionToSendMessage && event.content.canBeRepliedTo() if (canReply) { val state: SwipeableActionsState = rememberSwipeableActionsState() val offset = state.offset.floatValue @@ -219,7 +223,6 @@ fun TimelineItemEventRow( isLastOutgoingMessage = isLastOutgoingMessage, receipts = event.readReceiptState.receipts, ), - showReadReceipts = showReadReceipts, onReadReceiptsClicked = { onReadReceiptClick(event) }, modifier = Modifier.padding(top = 4.dp), ) @@ -335,6 +338,7 @@ private fun TimelineItemEventRowContent( if (event.reactionsState.reactions.isNotEmpty()) { TimelineItemReactionsView( reactionsState = event.reactionsState, + userCanSendReaction = timelineRoomInfo.userHasPermissionToSendReaction, isOutgoing = event.isMine, onReactionClicked = onReactionClicked, onReactionLongClicked = onReactionLongClicked, @@ -410,10 +414,10 @@ private fun MessageEventBubbleContent( onMentionClicked: (Mention) -> Unit, eventSink: (TimelineEvents) -> Unit, @SuppressLint("ModifierParameter") + // need to rename this modifier to prevent linter false positives @Suppress("ModifierNaming") - bubbleModifier: Modifier = Modifier, // need to rename this modifier to prevent linter false positives + bubbleModifier: Modifier = Modifier, ) { - // Long clicks are not not automatically propagated from a `clickable` // to its `combinedClickable` parent so we do it manually fun onTimestampLongClick() = onMessageLongClick() @@ -445,38 +449,47 @@ private fun MessageEventBubbleContent( fun WithTimestampLayout( timestampPosition: TimestampPosition, modifier: Modifier = Modifier, - content: @Composable () -> Unit, + canShrinkContent: Boolean = false, + content: @Composable (onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit) -> Unit, ) { when (timestampPosition) { TimestampPosition.Overlay -> Box(modifier, contentAlignment = Alignment.Center) { - content() + content {} TimelineEventTimestampView( event = event, onClick = onTimestampClicked, onLongClick = ::onTimestampLongClick, modifier = Modifier - .padding(horizontal = 4.dp, vertical = 4.dp) // Outer padding + // Outer padding + .padding(horizontal = 4.dp, vertical = 4.dp) .background(ElementTheme.colors.bgSubtleSecondary, RoundedCornerShape(10.0.dp)) .align(Alignment.BottomEnd) - .padding(horizontal = 4.dp, vertical = 2.dp) // Inner padding + // Inner padding + .padding(horizontal = 4.dp, vertical = 2.dp) ) } TimestampPosition.Aligned -> - Box(modifier) { - content() - TimelineEventTimestampView( - event = event, - onClick = onTimestampClicked, - onLongClick = ::onTimestampLongClick, - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(horizontal = 8.dp, vertical = 4.dp) - ) - } + ContentAvoidingLayout( + modifier = modifier, + // The spacing is negative to make the content overlap the empty space at the start of the timestamp + spacing = (-4).dp, + overlayOffset = DpOffset(0.dp, -1.dp), + shrinkContent = canShrinkContent, + content = { content(this::onContentLayoutChanged) }, + overlay = { + TimelineEventTimestampView( + event = event, + onClick = onTimestampClicked, + onLongClick = ::onTimestampLongClick, + modifier = Modifier + .padding(horizontal = 8.dp, vertical = 4.dp) + ) + } + ) TimestampPosition.Below -> Column(modifier) { - content() + content {} TimelineEventTimestampView( event = event, onClick = onTimestampClicked, @@ -495,7 +508,8 @@ private fun MessageEventBubbleContent( timestampPosition: TimestampPosition, showThreadDecoration: Boolean, inReplyToDetails: InReplyToDetails?, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + canShrinkContent: Boolean = false, ) { val context = LocalContext.current val timestampLayoutModifier: Modifier @@ -512,7 +526,8 @@ private fun MessageEventBubbleContent( } timestampPosition != TimestampPosition.Overlay -> { timestampLayoutModifier = Modifier - contentModifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 8.dp, bottom = 8.dp) + contentModifier = Modifier + .padding(start = 12.dp, end = 12.dp, top = 8.dp, bottom = 8.dp) } else -> { timestampLayoutModifier = Modifier @@ -527,8 +542,9 @@ private fun MessageEventBubbleContent( val contentWithTimestamp = @Composable { WithTimestampLayout( timestampPosition = timestampPosition, + canShrinkContent = canShrinkContent, modifier = timestampLayoutModifier, - ) { + ) { onContentLayoutChanged -> TimelineItemEventContentView( content = event.content, onLinkClicked = { url -> @@ -545,9 +561,9 @@ private fun MessageEventBubbleContent( } } }, - extraPadding = event.toExtraPadding(), eventSink = eventSink, - modifier = contentModifier, + onContentLayoutChanged = onContentLayoutChanged, + modifier = contentModifier ) } } @@ -562,7 +578,6 @@ private fun MessageEventBubbleContent( .clip(RoundedCornerShape(6.dp)) .clickable(enabled = true, onClick = inReplyToClick), ) - } if (inReplyToDetails != null) { // Use SubComposeLayout only if necessary as it can have consequences on the performance. @@ -581,6 +596,7 @@ private fun MessageEventBubbleContent( val timestampPosition = when (event.content) { is TimelineItemImageContent, + is TimelineItemStickerContent, is TimelineItemVideoContent, is TimelineItemLocationContent -> TimestampPosition.Overlay is TimelineItemPollContent -> TimestampPosition.Below @@ -590,6 +606,7 @@ private fun MessageEventBubbleContent( showThreadDecoration = event.isThreaded, timestampPosition = timestampPosition, inReplyToDetails = event.inReplyTo, + canShrinkContent = event.content is TimelineItemVoiceContent, modifier = bubbleModifier ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowForDirectRoomPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowForDirectRoomPreview.kt index 4443c00f3d..3ac2703ee5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowForDirectRoomPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowForDirectRoomPreview.kt @@ -59,4 +59,3 @@ internal fun TimelineItemEventRowForDirectRoomPreview() = ElementPreview { } } } - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt index a1bef47731..e6525584b8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt @@ -24,7 +24,6 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import org.jsoup.Jsoup @PreviewsDayNight @Composable @@ -38,18 +37,15 @@ internal fun TimelineItemEventRowTimestampPreview( "Text longer, displayed on 1 line", "Text which should be rendered on several lines", ).forEach { str -> - listOf(false, true).forEach { useDocument -> - ATimelineItemEventRow( - event = event.copy( - content = oldContent.copy( - body = str, - htmlDocument = if (useDocument) Jsoup.parse(str) else null, - ), - reactionsState = aTimelineItemReactions(count = 0), - senderDisplayName = if (useDocument) "Document case" else "Text case", + ATimelineItemEventRow( + event = event.copy( + content = oldContent.copy( + body = str, ), - ) - } + reactionsState = aTimelineItemReactions(count = 0), + senderDisplayName = "A sender", + ), + ) } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithManyReactionsPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithManyReactionsPreview.kt index bbd65a62ad..efb619bcc2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithManyReactionsPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithManyReactionsPreview.kt @@ -42,4 +42,3 @@ internal fun TimelineItemEventRowWithManyReactionsPreview() = ElementPreview { } } } - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt index 4a9972d19b..0e43e34670 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt @@ -47,7 +47,6 @@ internal fun TimelineItemEventRowWithRRPreview( timelineItemReactions = aTimelineItemReactions(count = 0), readReceiptState = TimelineItemReadReceipts(state.receipts), ), - showReadReceipts = true, isLastOutgoingMessage = false, ) // A message from current user @@ -61,7 +60,6 @@ internal fun TimelineItemEventRowWithRRPreview( timelineItemReactions = aTimelineItemReactions(count = 0), readReceiptState = TimelineItemReadReceipts(state.receipts), ), - showReadReceipts = true, isLastOutgoingMessage = false, ) // Another message from current user @@ -75,7 +73,6 @@ internal fun TimelineItemEventRowWithRRPreview( timelineItemReactions = aTimelineItemReactions(count = 0), readReceiptState = TimelineItemReadReceipts(state.receipts), ), - showReadReceipts = true, isLastOutgoingMessage = true, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt index ed046f1735..146a985039 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt @@ -42,6 +42,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MessageConten import io.element.android.libraries.matrix.api.timeline.item.event.MessageType import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType import io.element.android.libraries.matrix.api.timeline.item.event.PollContent +import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType @@ -109,6 +110,10 @@ class InReplyToDetailsProvider : PreviewParameterProvider { body = "Image", type = ImageMessageType("Image", MediaSource("url"), null), ), + aMessageContent( + body = "Sticker", + type = StickerMessageType("Image", MediaSource("url"), null), + ), aMessageContent( body = "File", type = FileMessageType("File", MediaSource("url"), null), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt index d52795c052..5ca78adee6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt @@ -24,8 +24,8 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.res.pluralStringResource import io.element.android.features.messages.impl.R -import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.aGroupedEvents import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo import io.element.android.features.messages.impl.timeline.components.group.GroupHeaderView @@ -43,7 +43,6 @@ import io.element.android.libraries.matrix.api.core.UserId fun TimelineItemGroupedEventsRow( timelineItem: TimelineItem.GroupedEvents, timelineRoomInfo: TimelineRoomInfo, - showReadReceipts: Boolean, isLastOutgoingMessage: Boolean, highlightedItem: String?, sessionState: SessionState, @@ -71,7 +70,6 @@ fun TimelineItemGroupedEventsRow( timelineItem = timelineItem, timelineRoomInfo = timelineRoomInfo, highlightedItem = highlightedItem, - showReadReceipts = showReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, sessionState = sessionState, onClick = onClick, @@ -95,7 +93,6 @@ private fun TimelineItemGroupedEventsRowContent( timelineItem: TimelineItem.GroupedEvents, timelineRoomInfo: TimelineRoomInfo, highlightedItem: String?, - showReadReceipts: Boolean, isLastOutgoingMessage: Boolean, sessionState: SessionState, onClick: (TimelineItem.Event) -> Unit, @@ -127,11 +124,9 @@ private fun TimelineItemGroupedEventsRowContent( TimelineItemRow( timelineItem = subGroupEvent, timelineRoomInfo = timelineRoomInfo, - showReadReceipts = showReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, highlightedItem = highlightedItem, sessionState = sessionState, - userHasPermissionToSendMessage = false, onClick = onClick, onLongClick = onLongClick, inReplyToClick = inReplyToClick, @@ -146,14 +141,13 @@ private fun TimelineItemGroupedEventsRowContent( ) } } - } else if (showReadReceipts) { + } else { TimelineItemReadReceiptView( state = ReadReceiptViewState( sendState = null, isLastOutgoingMessage = false, receipts = timelineItem.aggregatedReadReceipts, ), - showReadReceipts = true, onReadReceiptsClicked = onExpandGroupClick ) } @@ -166,10 +160,9 @@ internal fun TimelineItemGroupedEventsRowContentExpandedPreview() = ElementPrevi TimelineItemGroupedEventsRowContent( isExpanded = true, onExpandGroupClick = {}, - timelineItem = aGroupedEvents(), + timelineItem = aGroupedEvents(withReadReceipts = true), timelineRoomInfo = aTimelineRoomInfo(), highlightedItem = null, - showReadReceipts = true, isLastOutgoingMessage = false, sessionState = aSessionState(), onClick = {}, @@ -191,10 +184,9 @@ internal fun TimelineItemGroupedEventsRowContentCollapsePreview() = ElementPrevi TimelineItemGroupedEventsRowContent( isExpanded = false, onExpandGroupClick = {}, - timelineItem = aGroupedEvents(), + timelineItem = aGroupedEvents(withReadReceipts = true), timelineRoomInfo = aTimelineRoomInfo(), highlightedItem = null, - showReadReceipts = true, isLastOutgoingMessage = false, sessionState = aSessionState(), onClick = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt index d30a60c7a4..5272473a42 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt @@ -26,8 +26,8 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import io.element.android.features.messages.impl.R -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.utils.CommonDrawables /** @@ -46,7 +46,7 @@ import io.element.android.libraries.designsystem.utils.CommonDrawables @Composable fun TimelineItemReactionsLayout( expandButton: @Composable () -> Unit, - addMoreButton: @Composable () -> Unit, + addMoreButton: (@Composable () -> Unit)?, modifier: Modifier = Modifier, itemSpacing: Dp = 0.dp, rowSpacing: Dp = 0.dp, @@ -82,21 +82,21 @@ fun TimelineItemReactionsLayout( // Used to render the collapsed state, this takes the rows inputted and adds the extra button to the last row, // removing only as many trailing reactions as needed to make space for it. - fun replaceTrailingItemsWithButtons(rowsIn: List>, expandButton: Placeable, addMoreButton: Placeable): List> { + fun replaceTrailingItemsWithButtons(rowsIn: List>, expandButton: Placeable, addMoreButton: Placeable?): List> { val rows = rowsIn.toMutableList() val lastRow = rows.last() - val buttonsWidth = expandButton.width + itemSpacing.toPx().toInt() + addMoreButton.width + val buttonsWidth = expandButton.width + itemSpacing.toPx().toInt() + (addMoreButton?.width ?: 0) var rowX = 0 lastRow.forEachIndexed { i, placeable -> val horizontalSpacing = if (i == 0) 0 else itemSpacing.toPx().toInt() rowX += placeable.width + horizontalSpacing if (rowX > constraints.maxWidth - (buttonsWidth + horizontalSpacing)) { - val lastRowWithButton = lastRow.take(i) + listOf(expandButton, addMoreButton) + val lastRowWithButton = lastRow.take(i) + listOfNotNull(expandButton, addMoreButton) rows[rows.size - 1] = lastRowWithButton return rows } } - val lastRowWithButton = lastRow + listOf(expandButton, addMoreButton) + val lastRowWithButton = lastRow + listOfNotNull(expandButton, addMoreButton) rows[rows.size - 1] = lastRowWithButton return rows } @@ -117,7 +117,7 @@ fun TimelineItemReactionsLayout( return rows } - /// Given a list of rows place them in the layout. + // Given a list of rows place them in the layout. fun layoutRows(rows: List>): MeasureResult { var width = 0 var height = 0 @@ -155,16 +155,15 @@ fun TimelineItemReactionsLayout( val newConstrains = constraints.copy(minHeight = maxHeight) reactionsPlaceables = subcompose(2, reactions).map { it.measure(newConstrains) } expandPlaceable = subcompose(3, expandButton).first().measure(newConstrains) - val addMorePlaceable = subcompose(4, addMoreButton).first().measure(newConstrains) - + val addMorePlaceable = addMoreButton?.let { subcompose(4, addMoreButton).first().measure(newConstrains) } // Calculate the layout of the rows with the reactions button and add more button - val reactionsAndAddMore = calculateRows(reactionsPlaceables + listOf(addMorePlaceable)) + val reactionsAndAddMore = calculateRows(reactionsPlaceables + listOfNotNull(addMorePlaceable)) // If we have extended beyond the defined number of rows we are showing the expand/collapse ui if (rowsBeforeCollapsible?.let { reactionsAndAddMore.size > it } == true) { if (expanded) { // Show all subviews with the add more button at the end - var reactionsAndButtons = calculateRows(reactionsPlaceables + listOf(expandPlaceable, addMorePlaceable)) + var reactionsAndButtons = calculateRows(reactionsPlaceables + listOfNotNull(expandPlaceable, addMorePlaceable)) reactionsAndButtons = ensureCollapseAndAddMoreButtonsAreOnTheSameRow(reactionsAndButtons) layoutRows(reactionsAndButtons) } else { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt index 764b4cdea4..20b7fac13f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt @@ -31,8 +31,8 @@ import io.element.android.features.messages.impl.R import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.model.AggregatedReaction import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.utils.CommonDrawables import kotlinx.collections.immutable.ImmutableList @@ -40,6 +40,7 @@ import kotlinx.collections.immutable.ImmutableList fun TimelineItemReactionsView( reactionsState: TimelineItemReactions, isOutgoing: Boolean, + userCanSendReaction: Boolean, onReactionClicked: (emoji: String) -> Unit, onReactionLongClicked: (emoji: String) -> Unit, onMoreReactionsClicked: () -> Unit, @@ -49,6 +50,7 @@ fun TimelineItemReactionsView( TimelineItemReactionsView( modifier = modifier, reactions = reactionsState.reactions, + userCanSendReaction = userCanSendReaction, expanded = expanded, isOutgoing = isOutgoing, onReactionClick = onReactionClicked, @@ -61,6 +63,7 @@ fun TimelineItemReactionsView( @Composable private fun TimelineItemReactionsView( reactions: ImmutableList, + userCanSendReaction: Boolean, isOutgoing: Boolean, expanded: Boolean, onReactionClick: (emoji: String) -> Unit, @@ -72,11 +75,11 @@ private fun TimelineItemReactionsView( // In LTR languages we want an incoming message's reactions to be LTR and outgoing to be RTL. // For RTL languages it should be the opposite. val currentLayout = LocalLayoutDirection.current - val reactionsLayoutDirection = if (!isOutgoing) currentLayout - else if (currentLayout == LayoutDirection.Ltr) - LayoutDirection.Rtl - else - LayoutDirection.Ltr + val reactionsLayoutDirection = when { + !isOutgoing -> currentLayout + currentLayout == LayoutDirection.Ltr -> LayoutDirection.Rtl + else -> LayoutDirection.Ltr + } return CompositionLocalProvider(LocalLayoutDirection provides reactionsLayoutDirection) { TimelineItemReactionsLayout( @@ -93,19 +96,28 @@ private fun TimelineItemReactionsView( onLongClick = {} ) }, - addMoreButton = { - MessagesReactionButton( - content = MessagesReactionsButtonContent.Icon(CommonDrawables.ic_add_reaction), - onClick = onMoreReactionsClick, - onLongClick = {} - ) + addMoreButton = if (userCanSendReaction) { + { + MessagesReactionButton( + content = MessagesReactionsButtonContent.Icon(CommonDrawables.ic_add_reaction), + onClick = onMoreReactionsClick, + onLongClick = {} + ) + } + } else { + null }, reactions = { reactions.forEach { reaction -> CompositionLocalProvider(LocalLayoutDirection provides currentLayout) { MessagesReactionButton( content = MessagesReactionsButtonContent.Reaction(reaction = reaction), - onClick = { onReactionClick(reaction.key) }, + onClick = { + // Always allow user to redact their own reactions + if (reaction.isHighlighted || userCanSendReaction) { + onReactionClick(reaction.key) + } + }, onLongClick = { onReactionLongClick(reaction.key) } ) } @@ -157,10 +169,10 @@ private fun ContentToPreview( reactionsState = TimelineItemReactions( reactions ), + userCanSendReaction = true, isOutgoing = isOutgoing, onReactionClicked = {}, onReactionLongClicked = {}, onMoreReactionsClicked = {}, ) } - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt index 92dbbe46b6..2fe1080743 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt @@ -18,11 +18,10 @@ package io.element.android.features.messages.impl.timeline.components import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent -import io.element.android.features.messages.impl.timeline.model.event.canBeRepliedTo import io.element.android.features.messages.impl.timeline.session.SessionState import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId @@ -31,10 +30,8 @@ import io.element.android.libraries.matrix.api.core.UserId internal fun TimelineItemRow( timelineItem: TimelineItem, timelineRoomInfo: TimelineRoomInfo, - showReadReceipts: Boolean, isLastOutgoingMessage: Boolean, highlightedItem: String?, - userHasPermissionToSendMessage: Boolean, sessionState: SessionState, onUserDataClick: (UserId) -> Unit, onClick: (TimelineItem.Event) -> Unit, @@ -61,7 +58,6 @@ internal fun TimelineItemRow( if (timelineItem.content is TimelineItemStateContent) { TimelineItemStateEventRow( event = timelineItem, - showReadReceipts = showReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = highlightedItem == timelineItem.identifier(), onClick = { onClick(timelineItem) }, @@ -74,10 +70,8 @@ internal fun TimelineItemRow( TimelineItemEventRow( event = timelineItem, timelineRoomInfo = timelineRoomInfo, - showReadReceipts = showReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = highlightedItem == timelineItem.identifier(), - canReply = userHasPermissionToSendMessage && timelineItem.content.canBeRepliedTo(), onClick = { onClick(timelineItem) }, onLongClick = { onLongClick(timelineItem) }, onUserDataClick = onUserDataClick, @@ -97,7 +91,6 @@ internal fun TimelineItemRow( TimelineItemGroupedEventsRow( timelineItem = timelineItem, timelineRoomInfo = timelineRoomInfo, - showReadReceipts = showReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, highlightedItem = highlightedItem, sessionState = sessionState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt index b2557b378d..85eba29bc1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt @@ -32,7 +32,6 @@ import androidx.compose.ui.zIndex import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView -import io.element.android.features.messages.impl.timeline.components.event.noExtraPadding import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData @@ -48,7 +47,6 @@ import kotlinx.collections.immutable.toPersistentList @Composable fun TimelineItemStateEventRow( event: TimelineItem.Event, - showReadReceipts: Boolean, isLastOutgoingMessage: Boolean, isHighlighted: Boolean, onClick: () -> Unit, @@ -81,7 +79,6 @@ fun TimelineItemStateEventRow( TimelineItemEventContentView( content = event.content, onLinkClicked = {}, - extraPadding = noExtraPadding, eventSink = eventSink, modifier = Modifier.defaultTimelineContentPadding() ) @@ -93,7 +90,6 @@ fun TimelineItemStateEventRow( isLastOutgoingMessage = isLastOutgoingMessage, receipts = event.readReceiptState.receipts, ), - showReadReceipts = showReadReceipts, onReadReceiptsClicked = { onReadReceiptsClick(event) }, ) } @@ -111,7 +107,6 @@ internal fun TimelineItemStateEventRowPreview() = ElementPreview { receipts = listOf(aReadReceiptData(0)).toPersistentList(), ) ), - showReadReceipts = true, isLastOutgoingMessage = false, isHighlighted = false, onClick = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt index 4bf8e5b847..5ef3c2ef2c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt @@ -18,7 +18,6 @@ package io.element.android.features.messages.impl.timeline.components import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import io.element.android.features.messages.impl.timeline.session.SessionState import io.element.android.features.messages.impl.timeline.components.virtual.TimelineEncryptedHistoryBannerView import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemDaySeparatorView import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemReadMarkerView @@ -26,6 +25,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemEncryptedHistoryBannerVirtualModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemReadMarkerModel +import io.element.android.features.messages.impl.timeline.session.SessionState @Composable fun TimelineItemVirtualRow( @@ -39,4 +39,3 @@ fun TimelineItemVirtualRow( is TimelineItemEncryptedHistoryBannerVirtualModel -> TimelineEncryptedHistoryBannerView(sessionState, modifier) } } - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt index 8bbd6cbff7..a75e084abf 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt @@ -21,16 +21,15 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import io.element.android.libraries.architecture.Presenter -import kotlinx.coroutines.launch import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.libraries.architecture.Presenter import kotlinx.collections.immutable.toImmutableSet +import kotlinx.coroutines.launch import javax.inject.Inject class CustomReactionPresenter @Inject constructor( private val emojibaseProvider: EmojibaseProvider ) : Presenter { - @Composable override fun present(): CustomReactionState { val target: MutableState = remember { @@ -59,7 +58,12 @@ class CustomReactionPresenter @Inject constructor( } } val event = (target.value as? CustomReactionState.Target.Success)?.event - val selectedEmoji = event?.reactionsState?.reactions?.mapNotNull { if(it.isHighlighted) it.key else null }.orEmpty().toImmutableSet() + val selectedEmoji = event + ?.reactionsState + ?.reactions + ?.mapNotNull { if (it.isHighlighted) it.key else null } + .orEmpty() + .toImmutableSet() return CustomReactionState( target = target.value, selectedEmoji = selectedEmoji, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt index f6f7d2b0f9..5474068df0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt @@ -26,7 +26,6 @@ data class CustomReactionState( val eventSink: (CustomReactionEvents) -> Unit, ) { sealed interface Target { - data object None : Target data class Loading(val event: TimelineItem.Event) : Target data class Success( @@ -35,4 +34,3 @@ data class CustomReactionState( ) : Target } } - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt index a68d6f0d4e..6ee8a434ae 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt @@ -20,10 +20,8 @@ import android.content.Context import io.element.android.emojibasebindings.EmojibaseDatasource import io.element.android.emojibasebindings.EmojibaseStore -class DefaultEmojibaseProvider(val context: Context): EmojibaseProvider { - +class DefaultEmojibaseProvider(val context: Context) : EmojibaseProvider { override val emojibaseStore: EmojibaseStore by lazy { EmojibaseDatasource().load(context) } - } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/ExtraPadding.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/ExtraPadding.kt index e0be0f0662..c9706f06ac 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/ExtraPadding.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/ExtraPadding.kt @@ -75,7 +75,7 @@ fun TimelineItem.Event.toExtraPadding(): ExtraPadding { private fun TextMeasurer.getExtraPadding(text: String, density: Density): Dp { val timestampTextStyle = ElementTheme.typography.fontBodyXsRegular val textWidth = measure(text = text, style = timestampTextStyle).size.width - return (textWidth/density.density).dp + return (textWidth / density.density).dp } /** diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt index 257edbe28c..d4a2dbabc9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt @@ -34,6 +34,8 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme +import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout +import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContentProvider import io.element.android.libraries.designsystem.preview.ElementPreview @@ -44,15 +46,17 @@ import io.element.android.libraries.designsystem.theme.components.Text @Composable fun TimelineItemAudioView( content: TimelineItemAudioContent, - extraPadding: ExtraPadding, + onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit, modifier: Modifier = Modifier, ) { + val iconSize = 32.dp + val spacing = 8.dp Row( modifier = modifier, ) { Box( modifier = Modifier - .size(32.dp) + .size(iconSize) .clip(CircleShape) .background(ElementTheme.materialColors.background), contentAlignment = Alignment.Center, @@ -65,7 +69,7 @@ fun TimelineItemAudioView( .size(16.dp), ) } - Spacer(Modifier.width(8.dp)) + Spacer(Modifier.width(spacing)) Column { Text( text = content.body, @@ -75,11 +79,15 @@ fun TimelineItemAudioView( overflow = TextOverflow.Ellipsis ) Text( - text = content.fileExtensionAndSize + extraPadding.getStr(ElementTheme.typography.fontBodySmRegular), + text = content.fileExtensionAndSize, color = ElementTheme.materialColors.secondary, style = ElementTheme.typography.fontBodySmRegular, maxLines = 1, overflow = TextOverflow.Ellipsis, + onTextLayout = ContentAvoidingLayout.measureLastTextLine( + onContentLayoutChanged = onContentLayoutChanged, + extraWidth = iconSize + spacing + ) ) } } @@ -91,6 +99,6 @@ internal fun TimelineItemAudioViewPreview(@PreviewParameter(TimelineItemAudioCon ElementPreview { TimelineItemAudioView( content, - extraPadding = noExtraPadding, + onContentLayoutChanged = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt index d488d01ef4..26e5388c74 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt @@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -29,14 +30,14 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun TimelineItemEncryptedView( @Suppress("UNUSED_PARAMETER") content: TimelineItemEncryptedContent, - extraPadding: ExtraPadding, + onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit, modifier: Modifier = Modifier ) { TimelineItemInformativeView( text = stringResource(id = CommonStrings.common_waiting_for_decryption_key), iconDescription = stringResource(id = CommonStrings.dialog_title_warning), iconResourceId = CommonDrawables.ic_waiting_to_decrypt, - extraPadding = extraPadding, + onContentLayoutChanged = onContentLayoutChanged, modifier = modifier ) } @@ -48,6 +49,6 @@ internal fun TimelineItemEncryptedViewPreview() = ElementPreview { content = TimelineItemEncryptedContent( data = UnableToDecryptContent.Data.Unknown ), - extraPadding = noExtraPadding + onContentLayoutChanged = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt index 82f1af4858..e7dbf678af 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt @@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.rememberPresenter import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent @@ -30,6 +31,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent @@ -40,32 +42,32 @@ import io.element.android.libraries.architecture.Presenter @Composable fun TimelineItemEventContentView( content: TimelineItemEventContent, - extraPadding: ExtraPadding, onLinkClicked: (url: String) -> Unit, eventSink: (TimelineEvents) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit = {}, ) { val presenterFactories = LocalTimelineItemPresenterFactories.current when (content) { is TimelineItemEncryptedContent -> TimelineItemEncryptedView( content = content, - extraPadding = extraPadding, + onContentLayoutChanged = onContentLayoutChanged, modifier = modifier ) is TimelineItemRedactedContent -> TimelineItemRedactedView( content = content, - extraPadding = extraPadding, + onContentLayoutChanged = onContentLayoutChanged, modifier = modifier ) is TimelineItemTextBasedContent -> TimelineItemTextView( content = content, - extraPadding = extraPadding, modifier = modifier, onLinkClicked = onLinkClicked, + onContentLayoutChanged = onContentLayoutChanged ) is TimelineItemUnknownContent -> TimelineItemUnknownView( content = content, - extraPadding = extraPadding, + onContentLayoutChanged = onContentLayoutChanged, modifier = modifier ) is TimelineItemLocationContent -> TimelineItemLocationView( @@ -76,18 +78,22 @@ fun TimelineItemEventContentView( content = content, modifier = modifier, ) + is TimelineItemStickerContent -> TimelineItemStickerView( + content = content, + modifier = modifier, + ) is TimelineItemVideoContent -> TimelineItemVideoView( content = content, modifier = modifier ) is TimelineItemFileContent -> TimelineItemFileView( content = content, - extraPadding = extraPadding, + onContentLayoutChanged = onContentLayoutChanged, modifier = modifier ) is TimelineItemAudioContent -> TimelineItemAudioView( content = content, - extraPadding = extraPadding, + onContentLayoutChanged = onContentLayoutChanged, modifier = modifier ) is TimelineItemStateContent -> TimelineItemStateView( @@ -104,7 +110,7 @@ fun TimelineItemEventContentView( TimelineItemVoiceView( state = presenter.present(), content = content, - extraPadding = extraPadding, + onContentLayoutChanged = onContentLayoutChanged, modifier = modifier ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt index f0dd10763a..7058f143cf 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt @@ -33,6 +33,8 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme +import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout +import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContentProvider import io.element.android.libraries.designsystem.preview.ElementPreview @@ -44,15 +46,17 @@ import io.element.android.libraries.designsystem.utils.CommonDrawables @Composable fun TimelineItemFileView( content: TimelineItemFileContent, - extraPadding: ExtraPadding, + onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit, modifier: Modifier = Modifier, ) { + val iconSize = 32.dp + val spacing = 8.dp Row( modifier = modifier, ) { Box( modifier = Modifier - .size(32.dp) + .size(iconSize) .clip(CircleShape) .background(ElementTheme.materialColors.background), contentAlignment = Alignment.Center, @@ -66,7 +70,7 @@ fun TimelineItemFileView( .rotate(-45f), ) } - Spacer(Modifier.width(8.dp)) + Spacer(Modifier.width(spacing)) Column { Text( text = content.body, @@ -76,11 +80,15 @@ fun TimelineItemFileView( overflow = TextOverflow.Ellipsis ) Text( - text = content.fileExtensionAndSize + extraPadding.getStr(textStyle = ElementTheme.typography.fontBodySmRegular), + text = content.fileExtensionAndSize, color = ElementTheme.materialColors.secondary, style = ElementTheme.typography.fontBodySmRegular, maxLines = 1, overflow = TextOverflow.Ellipsis, + onTextLayout = ContentAvoidingLayout.measureLastTextLine( + onContentLayoutChanged = onContentLayoutChanged, + extraWidth = iconSize + spacing + ) ) } } @@ -91,6 +99,6 @@ fun TimelineItemFileView( internal fun TimelineItemFileViewPreview(@PreviewParameter(TimelineItemFileContentProvider::class) content: TimelineItemFileContent) = ElementPreview { TimelineItemFileView( content, - extraPadding = noExtraPadding, + onContentLayoutChanged = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemInformativeView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemInformativeView.kt index a81e928f18..73bedb4921 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemInformativeView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemInformativeView.kt @@ -25,9 +25,11 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme +import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -39,12 +41,19 @@ fun TimelineItemInformativeView( text: String, iconDescription: String, @DrawableRes iconResourceId: Int, - extraPadding: ExtraPadding, + onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit, modifier: Modifier = Modifier ) { Row( - modifier = modifier, - verticalAlignment = Alignment.CenterVertically + modifier = modifier.onSizeChanged { size -> + onContentLayoutChanged( + ContentAvoidingLayoutData( + contentWidth = size.width, + contentHeight = size.height, + ) + ) + }, + verticalAlignment = Alignment.CenterVertically, ) { Icon( resourceId = iconResourceId, @@ -57,7 +66,7 @@ fun TimelineItemInformativeView( fontStyle = FontStyle.Italic, color = MaterialTheme.colorScheme.secondary, style = ElementTheme.typography.fontBodyMdRegular, - text = text + extraPadding.getStr(textStyle = ElementTheme.typography.fontBodyMdRegular) + text = text ) } } @@ -69,6 +78,6 @@ internal fun TimelineItemInformativeViewPreview() = ElementPreview { text = "Info", iconDescription = "", iconResourceId = CompoundDrawables.ic_delete, - extraPadding = noExtraPadding, + onContentLayoutChanged = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt index c9d24a1637..0a4b008336 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt @@ -27,8 +27,8 @@ import androidx.compose.ui.unit.dp import io.element.android.features.location.api.StaticMapView import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContentProvider -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Text @Composable @@ -62,4 +62,3 @@ internal fun TimelineItemLocationViewPreview(@PreviewParameter(TimelineItemLocat ElementPreview { TimelineItemLocationView(content) } - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemRedactedView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemRedactedView.kt index 7ff7a23e7e..ba57c7e2af 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemRedactedView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemRedactedView.kt @@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.designsystem.preview.ElementPreview @@ -28,14 +29,14 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun TimelineItemRedactedView( @Suppress("UNUSED_PARAMETER") content: TimelineItemRedactedContent, - extraPadding: ExtraPadding, + onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit, modifier: Modifier = Modifier ) { TimelineItemInformativeView( text = stringResource(id = CommonStrings.common_message_removed), iconDescription = stringResource(id = CommonStrings.common_message_removed), iconResourceId = CompoundDrawables.ic_delete, - extraPadding = extraPadding, + onContentLayoutChanged = onContentLayoutChanged, modifier = modifier ) } @@ -45,6 +46,6 @@ fun TimelineItemRedactedView( internal fun TimelineItemRedactedViewPreview() = ElementPreview { TimelineItemRedactedView( TimelineItemRedactedContent, - extraPadding = noExtraPadding + onContentLayoutChanged = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStateView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStateView.kt index 7d89ec3993..706ca69f9c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStateView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStateView.kt @@ -20,12 +20,12 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign +import io.element.android.compound.theme.ElementTheme import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.ElementTheme @Composable fun TimelineItemStateView( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt new file mode 100644 index 0000000000..c34da9e473 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt @@ -0,0 +1,60 @@ +/* + * 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.messages.impl.timeline.components.event + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.heightIn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContentProvider +import io.element.android.libraries.designsystem.components.BlurHashAsyncImage +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.matrix.ui.media.MediaRequestData + +private const val STICKER_SIZE_IN_DP = 128 +private const val DEFAULT_ASPECT_RATIO = 1.33f + +@Composable +fun TimelineItemStickerView( + content: TimelineItemStickerContent, + modifier: Modifier = Modifier, +) { + val safeAspectRatio = content.aspectRatio ?: DEFAULT_ASPECT_RATIO + Box( + modifier = modifier + .heightIn(min = STICKER_SIZE_IN_DP.dp, max = STICKER_SIZE_IN_DP.dp) + .aspectRatio(safeAspectRatio, false), + contentAlignment = Alignment.TopStart, + ) { + BlurHashAsyncImage( + model = MediaRequestData(content.preferredMediaSource, MediaRequestData.Kind.File(content.body, content.mimeType)), + blurHash = content.blurhash, + ) + } +} + +@PreviewsDayNight +@Composable +internal fun TimelineItemStickerViewPreview(@PreviewParameter(TimelineItemStickerContentProvider::class) content: TimelineItemStickerContent) = ElementPreview { + TimelineItemStickerView(content) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt index dfe46d09f9..2df3766175 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt @@ -22,12 +22,11 @@ import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.dp -import androidx.core.text.buildSpannedString import io.element.android.compound.theme.ElementTheme +import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout +import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContentProvider import io.element.android.libraries.designsystem.preview.ElementPreview @@ -38,30 +37,24 @@ import io.element.android.wysiwyg.compose.EditorStyledText @Composable fun TimelineItemTextView( content: TimelineItemTextBasedContent, - extraPadding: ExtraPadding, onLinkClicked: (String) -> Unit, modifier: Modifier = Modifier, + onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit = {}, ) { CompositionLocalProvider( LocalContentColor provides ElementTheme.colors.textPrimary, LocalTextStyle provides ElementTheme.typography.fontBodyLgRegular ) { - val formattedBody = content.formattedBody val body = SpannableString(formattedBody ?: content.body) - val extraPaddingText = extraPadding.getStr() Box(modifier) { - val textWithPadding = remember(body) { - buildSpannedString { - append(body) - append(extraPaddingText) - } - } EditorStyledText( - text = textWithPadding, + text = body, onLinkClickedListener = onLinkClicked, style = ElementRichTextEditorStyle.textStyle(), + onTextLayout = ContentAvoidingLayout.measureLegacyLastTextLine(onContentLayoutChanged = onContentLayoutChanged), + releaseOnDetach = false, ) } } @@ -74,7 +67,6 @@ internal fun TimelineItemTextViewPreview( ) = ElementPreview { TimelineItemTextView( content = content, - extraPadding = ExtraPadding(extraWidth = 32.dp), onLinkClicked = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemUnknownView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemUnknownView.kt index 63fc5fd00b..c14eefc1a2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemUnknownView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemUnknownView.kt @@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.designsystem.preview.ElementPreview @@ -28,14 +29,14 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun TimelineItemUnknownView( @Suppress("UNUSED_PARAMETER") content: TimelineItemUnknownContent, - extraPadding: ExtraPadding, + onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit, modifier: Modifier = Modifier ) { TimelineItemInformativeView( text = stringResource(id = CommonStrings.common_unsupported_event), iconDescription = stringResource(id = CommonStrings.dialog_title_warning), iconResourceId = CompoundDrawables.ic_info_solid, - extraPadding = extraPadding, + onContentLayoutChanged = onContentLayoutChanged, modifier = modifier ) } @@ -45,6 +46,6 @@ fun TimelineItemUnknownView( internal fun TimelineItemUnknownViewPreview() = ElementPreview { TimelineItemUnknownView( content = TimelineItemUnknownContent, - extraPadding = noExtraPadding + onContentLayoutChanged = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt index 87685e432c..4900180800 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt @@ -32,8 +32,8 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContentProvider import io.element.android.libraries.designsystem.components.BlurHashAsyncImage import io.element.android.libraries.designsystem.modifiers.roundedBackground -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.ui.media.MediaRequestData import io.element.android.libraries.ui.strings.CommonStrings diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt index ed7e031392..befb09728a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt @@ -34,6 +34,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription @@ -43,6 +44,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme +import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContentProvider import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageEvents @@ -64,7 +66,7 @@ import kotlinx.coroutines.delay fun TimelineItemVoiceView( state: VoiceMessageState, content: TimelineItemVoiceContent, - extraPadding: ExtraPadding, + onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit, modifier: Modifier = Modifier, ) { fun playPause() { @@ -73,9 +75,18 @@ fun TimelineItemVoiceView( val a11y = stringResource(CommonStrings.common_voice_message) Row( - modifier = modifier.semantics { - contentDescription = a11y - }, + modifier = modifier + .semantics { + contentDescription = a11y + } + .onSizeChanged { + onContentLayoutChanged( + ContentAvoidingLayoutData( + contentWidth = it.width, + contentHeight = it.height, + ) + ) + }, verticalAlignment = Alignment.CenterVertically, ) { when (state.button) { @@ -99,13 +110,10 @@ fun TimelineItemVoiceView( showCursor = state.showCursor, playbackProgress = state.progress, waveform = content.waveform, - modifier = Modifier - .height(34.dp) - .weight(1f), + modifier = Modifier.height(34.dp), seekEnabled = !context.isScreenReaderEnabled(), onSeek = { state.eventSink(VoiceMessageEvents.Seek(it)) }, ) - Spacer(Modifier.width(extraPadding.getDpSize())) } } @@ -237,7 +245,7 @@ internal fun TimelineItemVoiceViewPreview( TimelineItemVoiceView( state = timelineItemVoiceViewParameters.state, content = timelineItemVoiceViewParameters.content, - extraPadding = noExtraPadding, + onContentLayoutChanged = {}, ) } @@ -250,7 +258,7 @@ internal fun TimelineItemVoiceViewUnifiedPreview() = ElementPreview { TimelineItemVoiceView( state = it.state, content = it.content, - extraPadding = noExtraPadding, + onContentLayoutChanged = {}, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/layout/ContentAvoidingLayout.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/layout/ContentAvoidingLayout.kt new file mode 100644 index 0000000000..5df38c1845 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/layout/ContentAvoidingLayout.kt @@ -0,0 +1,204 @@ +/* + * 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.messages.impl.timeline.components.layout + +import android.text.Layout +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.SubcomposeLayout +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.text.roundToPx +import io.element.android.wysiwyg.compose.EditorStyledText +import kotlin.math.max +import kotlin.math.min +import kotlin.math.roundToInt + +/** + * A layout with 2 children: the [content] and the [overlay]. + * + * It will try to place the [overlay] on top of the [content] if possible, avoiding the area of it that is non-overlapping. + * If the [overlay] can't be placed on top of the [content], it will be placed to the right of it, if it fits, otherwise, to its bottom in a new row. + * + * @param overlay The 'overlay' component of the layout, which will be positioned relative to the [content]. + * @param modifier The modifier for the layout. + * @param spacing The spacing between the [content] and the [overlay]. Defaults to `0.dp`. + * @param overlayOffset The offset of the [overlay] from the bottom right corner of the [content]. + * @param shrinkContent Whether the content should be shrunk to fit the available width or not. Defaults to `false`. + * @param content The 'content' component of the layout. + */ +@Composable +fun ContentAvoidingLayout( + overlay: @Composable () -> Unit, + modifier: Modifier = Modifier, + spacing: Dp = 0.dp, + overlayOffset: DpOffset = DpOffset.Zero, + shrinkContent: Boolean = false, + content: @Composable ContentAvoidingLayoutScope.() -> Unit, +) { + val scope = remember { ContentAvoidingLayoutScopeInstance() } + + SubcomposeLayout( + modifier = modifier, + ) { constraints -> + + // Measure the `overlay` view first, in case we need to shrink the `content` + val overlayPlaceable = subcompose(0, overlay).first().measure(Constraints(minWidth = 0, maxWidth = constraints.maxWidth)) + val contentConstraints = if (shrinkContent) { + Constraints(minWidth = 0, maxWidth = constraints.maxWidth - overlayPlaceable.width) + } else { + Constraints(minWidth = 0, maxWidth = constraints.maxWidth) + } + val contentPlaceable = subcompose(1) { scope.content() }.first().measure(contentConstraints) + + var layoutWidth = contentPlaceable.width + var layoutHeight = contentPlaceable.height + + val data = scope.data + + // Free space = width of the whole component - width of its non overlapping contents + val freeSpace = max(contentPlaceable.width - data.nonOverlappingContentWidth, 0) + + when { + // When the content + the overlay don't fit in the available max width, we need to move the overlay to a new row + !shrinkContent && data.nonOverlappingContentWidth + overlayPlaceable.width > constraints.maxWidth -> { + layoutHeight += overlayPlaceable.height + overlayOffset.y.roundToPx() + } + // If the content is smaller than the available max width, we can move the overlay to the right of the content + contentPlaceable.width < constraints.maxWidth -> { + // If both the content and the overlay plus the padding can fit inside the current layoutWidth, there is no need to increase it + if (freeSpace < overlayPlaceable.width + spacing.roundToPx()) { + // Otherwise, we need to increase it by the width of the overlay + some padding adjustments + val calculatedWidth = max(data.nonOverlappingContentWidth + overlayPlaceable.width + spacing.roundToPx(), contentPlaceable.width) + layoutWidth = min(calculatedWidth, constraints.maxWidth) + } + } + else -> Unit + } + + layoutWidth = max(layoutWidth, constraints.minWidth) + layoutHeight = max(layoutHeight, constraints.minHeight) + + layout(layoutWidth, layoutHeight) { + contentPlaceable.placeRelative(0, 0) + overlayPlaceable.placeRelative(layoutWidth - overlayPlaceable.width, layoutHeight - overlayPlaceable.height + overlayOffset.y.roundToPx()) + } + } +} + +/** + * Data class to hold the content layout data. + * This is used to pass the data from the content to the [ContentAvoidingLayout]. + * + * @param contentWidth The full width of the content in pixels. + * @param contentHeight The full height of the content in pixels. + * @param nonOverlappingContentWidth The width of the part of the content that can't overlap with the timestamp. + * @param nonOverlappingContentHeight The height of the part of the content that can't overlap with the timestamp. + */ +@Suppress("DataClassShouldBeImmutable") +data class ContentAvoidingLayoutData( + var contentWidth: Int = 0, + var contentHeight: Int = 0, + var nonOverlappingContentWidth: Int = contentWidth, + var nonOverlappingContentHeight: Int = contentHeight, +) + +/** + * A scope for the [ContentAvoidingLayout]. + */ +interface ContentAvoidingLayoutScope { + /** + * It should be called when the content layout changes, so it can update the [ContentAvoidingLayoutData] and measure and layout the content properly. + */ + fun onContentLayoutChanged(data: ContentAvoidingLayoutData) +} + +private class ContentAvoidingLayoutScopeInstance( + val data: ContentAvoidingLayoutData = ContentAvoidingLayoutData(), +) : ContentAvoidingLayoutScope { + override fun onContentLayoutChanged(data: ContentAvoidingLayoutData) { + this.data.contentWidth = data.contentWidth + this.data.contentHeight = data.contentHeight + this.data.nonOverlappingContentWidth = data.nonOverlappingContentWidth + this.data.nonOverlappingContentHeight = data.nonOverlappingContentHeight + } +} + +object ContentAvoidingLayout { + /** + * Measures the last line of a [TextLayoutResult] and calls [onContentLayoutChanged] with the [ContentAvoidingLayoutData]. + * + * This is supposed to be used in the `onTextLayout` parameter of a Text based component. + */ + @Composable + internal fun measureLastTextLine( + onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit, + extraWidth: Dp = 0.dp, + ): ((TextLayoutResult) -> Unit) { + val layoutDirection = LocalLayoutDirection.current + val extraWidthPx = extraWidth.roundToPx() + return { textLayout: TextLayoutResult -> + // We need to add the external extra width so it's not taken into account as 'free space' + val lastLineWidth = when (layoutDirection) { + LayoutDirection.Ltr -> textLayout.getLineRight(textLayout.lineCount - 1).roundToInt() + LayoutDirection.Rtl -> textLayout.getLineLeft(textLayout.lineCount - 1).roundToInt() + } + val lastLineHeight = textLayout.getLineBottom(textLayout.lineCount - 1).roundToInt() + onContentLayoutChanged( + ContentAvoidingLayoutData( + contentWidth = textLayout.size.width + extraWidthPx, + contentHeight = textLayout.size.height, + nonOverlappingContentWidth = lastLineWidth + extraWidthPx, + nonOverlappingContentHeight = lastLineHeight, + ) + ) + } + } + + /** + * Measures the last line of a [Layout] and calls [onContentLayoutChanged] with the [ContentAvoidingLayoutData]. + * + * This is supposed to be used in the `onTextLayout` parameter of an [EditorStyledText] component. + */ + @Composable + internal fun measureLegacyLastTextLine( + onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit, + extraWidth: Dp = 0.dp, + ): ((Layout) -> Unit) { + val extraWidthPx = extraWidth.roundToPx() + return { textLayout: Layout -> + // We need to add the external extra width so it's not taken into account as 'free space' + val lastLineWidth = textLayout.getLineWidth(textLayout.lineCount - 1).roundToInt() + val lastLineHeight = textLayout.getLineBottom(textLayout.lineCount - 1) + onContentLayoutChanged( + ContentAvoidingLayoutData( + contentWidth = textLayout.width + extraWidthPx, + contentHeight = textLayout.height, + nonOverlappingContentWidth = lastLineWidth + extraWidthPx, + nonOverlappingContentHeight = lastLineHeight, + ) + ) + } + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt index 7c30350cc4..5f9a2ea428 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt @@ -65,18 +65,17 @@ class ReactionSummaryPresenter @Inject constructor( derivedStateOf { summary?.let { summary -> summary.copy(reactions = summary.reactions.map { reaction -> - reaction.copy(senders = reaction.senders.map { sender -> - val member = members.firstOrNull { it.userId == sender.senderId } - val user = MatrixUser( - userId = sender.senderId, - displayName = member?.displayName, - avatarUrl = member?.avatarUrl - ) - sender.copy(user = user) - }) + reaction.copy(senders = reaction.senders.map { sender -> + val member = members.firstOrNull { it.userId == sender.senderId } + val user = MatrixUser( + userId = sender.senderId, + displayName = member?.displayName, + avatarUrl = member?.avatarUrl + ) + sender.copy(user = user) + }) }) } } } - } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt index 37e150320b..1d1606e53d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt @@ -22,11 +22,10 @@ import io.element.android.libraries.matrix.api.core.EventId data class ReactionSummaryState( val target: Summary?, val eventSink: (ReactionSummaryEvents) -> Unit -){ +) { data class Summary( val reactions: List, val selectedKey: String, val selectedEventId: EventId ) } - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt index 3e6568d880..e58f614234 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn @@ -57,20 +58,27 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage +import io.element.android.compound.theme.ElementTheme +import io.element.android.features.messages.impl.timeline.components.REACTION_IMAGE_ASPECT_RATIO import io.element.android.features.messages.impl.timeline.model.AggregatedReaction import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.text.toDp import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet import io.element.android.libraries.designsystem.theme.components.Surface import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.ui.media.MediaRequestData import io.element.android.libraries.matrix.ui.model.getAvatarData -import io.element.android.compound.theme.ElementTheme import kotlinx.coroutines.launch +internal val REACTION_SUMMARY_LINE_HEIGHT = 25.sp + @OptIn(ExperimentalMaterial3Api::class) @Composable fun ReactionSummaryView( @@ -112,7 +120,7 @@ private fun SheetContent( LaunchedEffect(pagerState.currentPage) { selectedReactionKey = summary.reactions[pagerState.currentPage].key - val visibleInfo = reactionListState.layoutInfo.visibleItemsInfo + val visibleInfo = reactionListState.layoutInfo.visibleItemsInfo if (selectedReactionIndex <= visibleInfo.first().index || selectedReactionIndex >= visibleInfo.last().index) { reactionListState.animateScrollToItem(selectedReactionIndex) } @@ -166,7 +174,6 @@ private fun AggregatedReactionButton( onClick: () -> Unit, modifier: Modifier = Modifier, ) { - val buttonColor = if (isHighlighted) { ElementTheme.colors.bgActionPrimaryRest } else { @@ -192,13 +199,24 @@ private fun AggregatedReactionButton( verticalAlignment = Alignment.CenterVertically, modifier = Modifier, ) { - Text( - text = reaction.displayKey, - style = ElementTheme.typography.fontBodyMdRegular.copy( - fontSize = 20.sp, - lineHeight = 25.sp - ), - ) + // Check if this is a custom reaction (MSC4027) + if (reaction.key.startsWith("mxc://")) { + AsyncImage( + modifier = Modifier + .heightIn(min = REACTION_SUMMARY_LINE_HEIGHT.toDp(), max = REACTION_SUMMARY_LINE_HEIGHT.toDp()) + .aspectRatio(REACTION_IMAGE_ASPECT_RATIO, false), + model = MediaRequestData(MediaSource(reaction.key), MediaRequestData.Kind.Content), + contentDescription = null + ) + } else { + Text( + text = reaction.displayKey, + style = ElementTheme.typography.fontBodyMdRegular.copy( + fontSize = 20.sp, + lineHeight = REACTION_SUMMARY_LINE_HEIGHT + ), + ) + } if (reaction.count > 1) { Spacer(modifier = Modifier.width(4.dp)) Text( @@ -206,7 +224,7 @@ private fun AggregatedReactionButton( color = textColor, style = ElementTheme.typography.fontBodyMdRegular.copy( fontSize = 20.sp, - lineHeight = 25.sp + lineHeight = REACTION_SUMMARY_LINE_HEIGHT ) ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt index 70e5486c2a..b8f5dc3ca9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import io.element.android.appconfig.TimelineConfig +import io.element.android.compound.theme.ElementTheme import io.element.android.features.messages.impl.timeline.model.ReadReceiptData import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarSize @@ -50,7 +51,6 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonPlurals import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList @@ -58,51 +58,50 @@ import kotlinx.collections.immutable.ImmutableList @Composable fun TimelineItemReadReceiptView( state: ReadReceiptViewState, - showReadReceipts: Boolean, onReadReceiptsClicked: () -> Unit, modifier: Modifier = Modifier, ) { if (state.receipts.isNotEmpty()) { - if (showReadReceipts) { - ReadReceiptsRow(modifier = modifier) { - ReadReceiptsAvatars( - receipts = state.receipts, - modifier = Modifier - .clip(RoundedCornerShape(4.dp)) - .clickable { - onReadReceiptsClicked() - } - .padding(2.dp) - ) - } + ReadReceiptsRow(modifier = modifier) { + ReadReceiptsAvatars( + receipts = state.receipts, + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .clickable { + onReadReceiptsClicked() + } + .padding(2.dp) + ) } - } else when (state.sendState) { - LocalEventSendState.NotSentYet -> { - ReadReceiptsRow(modifier) { - Icon( - modifier = Modifier.padding(2.dp), - resourceId = CommonDrawables.ic_sending, - contentDescription = stringResource(id = CommonStrings.common_sending), - tint = ElementTheme.colors.iconSecondary - ) - } - } - LocalEventSendState.Canceled -> Unit - is LocalEventSendState.SendingFailed -> { - // Error? The timestamp is already displayed in red - } - null, - is LocalEventSendState.Sent -> { - if (state.isLastOutgoingMessage) { - ReadReceiptsRow(modifier = modifier) { + } else { + when (state.sendState) { + LocalEventSendState.NotSentYet -> { + ReadReceiptsRow(modifier) { Icon( modifier = Modifier.padding(2.dp), - resourceId = CommonDrawables.ic_sent, - contentDescription = stringResource(id = CommonStrings.common_sent), + resourceId = CommonDrawables.ic_sending, + contentDescription = stringResource(id = CommonStrings.common_sending), tint = ElementTheme.colors.iconSecondary ) } } + LocalEventSendState.Canceled -> Unit + is LocalEventSendState.SendingFailed -> { + // Error? The timestamp is already displayed in red + } + null, + is LocalEventSendState.Sent -> { + if (state.isLastOutgoingMessage) { + ReadReceiptsRow(modifier = modifier) { + Icon( + modifier = Modifier.padding(2.dp), + resourceId = CommonDrawables.ic_sent, + contentDescription = stringResource(id = CommonStrings.common_sent), + tint = ElementTheme.colors.iconSecondary + ) + } + } + } } } } @@ -150,7 +149,7 @@ private fun ReadReceiptsAvatars( contentAlignment = Alignment.CenterEnd, ) { receipts - .take(TimelineConfig.maxReadReceiptToDisplay) + .take(TimelineConfig.MAX_READ_RECEIPT_TO_DISPLAY) .reversed() .forEachIndexed { index, readReceiptData -> Box( @@ -168,9 +167,9 @@ private fun ReadReceiptsAvatars( } } } - if (receipts.size > TimelineConfig.maxReadReceiptToDisplay) { + if (receipts.size > TimelineConfig.MAX_READ_RECEIPT_TO_DISPLAY) { Text( - text = "+" + (receipts.size - TimelineConfig.maxReadReceiptToDisplay), + text = "+" + (receipts.size - TimelineConfig.MAX_READ_RECEIPT_TO_DISPLAY), style = ElementTheme.typography.fontBodyXsRegular, color = ElementTheme.colors.textSecondary, ) @@ -207,7 +206,6 @@ internal fun TimelineItemReactionsViewPreview( ) = ElementPreview { TimelineItemReadReceiptView( state = state, - showReadReceipts = true, onReadReceiptsClicked = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt index a4a55cbc9e..f1b1deb34d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt @@ -25,9 +25,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.libraries.architecture.Presenter import javax.inject.Inject -class ReadReceiptBottomSheetPresenter @Inject constructor( -) : Presenter { - +class ReadReceiptBottomSheetPresenter @Inject constructor() : Presenter { @Composable override fun present(): ReadReceiptBottomSheetState { var selectedEvent: TimelineItem.Event? by remember { mutableStateOf(null) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuEvents.kt index 97ef92ceb8..77be3815e5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuEvents.kt @@ -22,5 +22,5 @@ sealed interface RetrySendMenuEvents { data class EventSelected(val event: TimelineItem.Event) : RetrySendMenuEvents data object RetrySend : RetrySendMenuEvents data object RemoveFailed : RetrySendMenuEvents - data object Dismiss: RetrySendMenuEvents + data object Dismiss : RetrySendMenuEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenter.kt index c9ebd9be8c..bc1415829f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenter.kt @@ -31,7 +31,6 @@ import javax.inject.Inject class RetrySendMenuPresenter @Inject constructor( private val room: MatrixRoom, ) : Presenter { - @Composable override fun present(): RetrySendMenuState { val coroutineScope = rememberCoroutineScope() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt index bbd35a7d4f..1eec2547cd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt @@ -33,12 +33,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.features.messages.impl.R -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.coroutines.launch diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemDaySeparatorView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemDaySeparatorView.kt index f3b007354f..3bb38fca7c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemDaySeparatorView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemDaySeparatorView.kt @@ -26,12 +26,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModelProvider -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.ElementTheme @Composable internal fun TimelineItemDaySeparatorView( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemReadMarkerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemReadMarkerView.kt index 0d8dca11b7..1d50bedfbb 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemReadMarkerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemReadMarkerView.kt @@ -25,12 +25,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.features.messages.impl.R import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.ElementTheme @Composable internal fun TimelineItemReadMarkerView( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemRoomBeginningView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemRoomBeginningView.kt index 78ce6521d9..7601a466c4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemRoomBeginningView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemRoomBeginningView.kt @@ -27,11 +27,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.features.messages.impl.R import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.ElementTheme @Composable fun TimelineItemRoomBeginningView( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineLoadingMoreIndicator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineLoadingMoreIndicator.kt index 847aa2bcef..23cc516156 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineLoadingMoreIndicator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineLoadingMoreIndicator.kt @@ -24,8 +24,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator @Composable diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoNode.kt index b9e4a75d97..6f53c5a306 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoNode.kt @@ -35,7 +35,6 @@ class EventDebugInfoNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, ) : Node(buildContext, plugins = plugins) { - data class Inputs( val eventId: EventId?, val timelineItemDebugInfo: TimelineItemDebugInfo, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoView.kt index d3d05dc6a2..443451f565 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoView.kt @@ -95,7 +95,8 @@ fun EventDebugInfoView( .fillMaxWidth() .padding(padding) // Window insets .consumeWindowInsets(padding) - .padding(horizontal = 16.dp) // Internal padding + // Internal padding + .padding(horizontal = 16.dp) ) { item { Column(Modifier.padding(vertical = 10.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/diff/TimelineItemsCacheInvalidator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/diff/TimelineItemsCacheInvalidator.kt index a7a3bea00e..8d54e04b48 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/diff/TimelineItemsCacheInvalidator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/diff/TimelineItemsCacheInvalidator.kt @@ -27,7 +27,6 @@ import io.element.android.libraries.androidutils.diff.MutableDiffCache * This is needed because a timeline item is computed based on the previous and next items. */ internal class TimelineItemsCacheInvalidator : DiffCacheInvalidator { - private val delegate = DefaultDiffCacheInvalidator() override fun onChanged(position: Int, count: Int, cache: MutableDiffCache) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt index 5e72392914..abd4dce536 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt @@ -45,7 +45,6 @@ class TimelineItemContentFactory @Inject constructor( private val failedToParseMessageFactory: TimelineItemContentFailedToParseMessageFactory, private val failedToParseStateFactory: TimelineItemContentFailedToParseStateFactory ) { - suspend fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent { return when (val itemContent = eventTimelineItem.content) { is FailedToParseMessageLikeContent -> failedToParseMessageFactory.create(itemContent) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseMessageFactory.kt index e764cfa28f..6a7d933282 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseMessageFactory.kt @@ -22,7 +22,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParse import javax.inject.Inject class TimelineItemContentFailedToParseMessageFactory @Inject constructor() { - fun create(@Suppress("UNUSED_PARAMETER") failedToParseMessageLike: FailedToParseMessageLikeContent): TimelineItemEventContent { return TimelineItemUnknownContent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseStateFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseStateFactory.kt index f7d3c10483..91414f2394 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseStateFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseStateFactory.kt @@ -22,7 +22,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParse import javax.inject.Inject class TimelineItemContentFailedToParseStateFactory @Inject constructor() { - @Suppress("UNUSED_PARAMETER") fun create(failedToParseState: FailedToParseStateContent): TimelineItemEventContent { return TimelineItemUnknownContent 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 69c453bb44..b53d749613 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 @@ -32,6 +32,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemNoticeContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent @@ -50,6 +51,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MessageConten import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType @@ -66,11 +68,10 @@ class TimelineItemContentMessageFactory @Inject constructor( private val featureFlagService: FeatureFlagService, private val htmlConverterProvider: HtmlConverterProvider, ) { - suspend fun create(content: MessageContent, senderDisplayName: String, eventId: EventId?): TimelineItemEventContent { return when (val messageType = content.type) { is EmoteMessageType -> { - val emoteBody = "* $senderDisplayName ${messageType.body}" + val emoteBody = "* $senderDisplayName ${messageType.body.trimEnd()}" TimelineItemEmoteContent( body = emoteBody, htmlDocument = messageType.formatted?.toHtmlDocument(prefix = "* $senderDisplayName"), @@ -81,7 +82,22 @@ class TimelineItemContentMessageFactory @Inject constructor( is ImageMessageType -> { val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height) TimelineItemImageContent( - body = messageType.body, + body = messageType.body.trimEnd(), + mediaSource = messageType.source, + thumbnailSource = messageType.info?.thumbnailSource, + mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, + blurhash = messageType.info?.blurhash, + width = messageType.info?.width?.toInt(), + height = messageType.info?.height?.toInt(), + aspectRatio = aspectRatio, + formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0), + fileExtension = fileExtensionExtractor.extractFromName(messageType.body) + ) + } + is StickerMessageType -> { + val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height) + TimelineItemStickerContent( + body = messageType.body.trimEnd(), mediaSource = messageType.source, thumbnailSource = messageType.info?.thumbnailSource, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, @@ -96,16 +112,17 @@ class TimelineItemContentMessageFactory @Inject constructor( is LocationMessageType -> { val location = Location.fromGeoUri(messageType.geoUri) if (location == null) { + val body = messageType.body.trimEnd() TimelineItemTextContent( - body = messageType.body, + body = body, htmlDocument = null, - plainText = messageType.body, + plainText = body, formattedBody = null, isEdited = content.isEdited, ) } else { TimelineItemLocationContent( - body = messageType.body, + body = messageType.body.trimEnd(), location = location, description = messageType.description ) @@ -114,7 +131,7 @@ class TimelineItemContentMessageFactory @Inject constructor( is VideoMessageType -> { val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height) TimelineItemVideoContent( - body = messageType.body, + body = messageType.body.trimEnd(), thumbnailSource = messageType.info?.thumbnailSource, videoSource = messageType.source, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, @@ -129,7 +146,7 @@ class TimelineItemContentMessageFactory @Inject constructor( } is AudioMessageType -> { TimelineItemAudioContent( - body = messageType.body, + body = messageType.body.trimEnd(), mediaSource = messageType.source, duration = messageType.info?.duration ?: Duration.ZERO, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, @@ -142,7 +159,7 @@ class TimelineItemContentMessageFactory @Inject constructor( true -> { TimelineItemVoiceContent( eventId = eventId, - body = messageType.body, + body = messageType.body.trimEnd(), mediaSource = messageType.source, duration = messageType.info?.duration ?: Duration.ZERO, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, @@ -151,7 +168,7 @@ class TimelineItemContentMessageFactory @Inject constructor( } false -> { TimelineItemAudioContent( - body = messageType.body, + body = messageType.body.trimEnd(), mediaSource = messageType.source, duration = messageType.info?.duration ?: Duration.ZERO, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, @@ -164,7 +181,7 @@ class TimelineItemContentMessageFactory @Inject constructor( is FileMessageType -> { val fileExtension = fileExtensionExtractor.extractFromName(messageType.body) TimelineItemFileContent( - body = messageType.body, + body = messageType.body.trimEnd(), thumbnailSource = messageType.info?.thumbnailSource, fileSource = messageType.source, mimeType = messageType.info?.mimetype ?: MimeTypes.fromFileExtension(fileExtension), @@ -172,26 +189,33 @@ class TimelineItemContentMessageFactory @Inject constructor( fileExtension = fileExtension ) } - is NoticeMessageType -> TimelineItemNoticeContent( - body = messageType.body, - htmlDocument = messageType.formatted?.toHtmlDocument(), - formattedBody = parseHtml(messageType.formatted) ?: messageType.body.withLinks(), - isEdited = content.isEdited, - ) - is TextMessageType -> { - TimelineItemTextContent( - body = messageType.body, + is NoticeMessageType -> { + val body = messageType.body.trimEnd() + TimelineItemNoticeContent( + body = body, htmlDocument = messageType.formatted?.toHtmlDocument(), - formattedBody = parseHtml(messageType.formatted) ?: messageType.body.withLinks(), + formattedBody = parseHtml(messageType.formatted) ?: body.withLinks(), + isEdited = content.isEdited, + ) + } + is TextMessageType -> { + val body = messageType.body.trimEnd() + TimelineItemTextContent( + body = body, + htmlDocument = messageType.formatted?.toHtmlDocument(), + formattedBody = parseHtml(messageType.formatted) ?: body.withLinks(), + isEdited = content.isEdited, + ) + } + is OtherMessageType -> { + val body = messageType.body.trimEnd() + TimelineItemTextContent( + body = body, + htmlDocument = null, + formattedBody = body.withLinks(), isEdited = content.isEdited, ) } - is OtherMessageType -> TimelineItemTextContent( - body = messageType.body, - htmlDocument = null, - formattedBody = messageType.body.withLinks(), - isEdited = content.isEdited, - ) } } @@ -208,7 +232,7 @@ class TimelineItemContentMessageFactory @Inject constructor( private fun parseHtml(formattedBody: FormattedBody?, prefix: String? = null): CharSequence? { if (formattedBody == null || formattedBody.format != MessageFormat.HTML) return null val result = htmlConverterProvider.provide() - .fromHtmlToSpans(formattedBody.body) + .fromHtmlToSpans(formattedBody.body.trimEnd()) .withFixedURLSpans() return if (prefix != null) { buildSpannedString { @@ -244,7 +268,7 @@ class TimelineItemContentMessageFactory @Inject constructor( @Suppress("USELESS_ELVIS") private fun String.withLinks(): CharSequence? { - /* Note: toSpannable() can return null when running unit tests */ + // Note: toSpannable() can return null when running unit tests val spannable = toSpannable() ?: return null val addedLinks = LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES) return spannable.takeIf { addedLinks } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt index c3b417abd6..df30dfb7b2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt @@ -30,7 +30,6 @@ class TimelineItemContentPollFactory @Inject constructor( private val featureFlagService: FeatureFlagService, private val pollContentStateFactory: PollContentStateFactory, ) { - suspend fun create( event: EventTimelineItem, content: PollContent, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt index e54d88326d..38c631de8d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt @@ -26,7 +26,6 @@ import javax.inject.Inject class TimelineItemContentProfileChangeFactory @Inject constructor( private val timelineEventFormatter: TimelineEventFormatter, ) { - fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent { val text = timelineEventFormatter.format(eventTimelineItem) return TimelineItemProfileChangeContent(text.orEmpty().toString()) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRedactedFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRedactedFactory.kt index 9419c7c31d..59ec5cada1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRedactedFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRedactedFactory.kt @@ -22,7 +22,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.RedactedConte import javax.inject.Inject class TimelineItemContentRedactedFactory @Inject constructor() { - fun create(@Suppress("UNUSED_PARAMETER") content: RedactedContent): TimelineItemEventContent { return TimelineItemRedactedContent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt index d5cf0cec2d..a7eab0797a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt @@ -26,7 +26,6 @@ import javax.inject.Inject class TimelineItemContentRoomMembershipFactory @Inject constructor( private val timelineEventFormatter: TimelineEventFormatter, ) { - fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent { val text = timelineEventFormatter.format(eventTimelineItem) return TimelineItemRoomMembershipContent(text.orEmpty().toString()) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt index 072b568af9..cfce61ca37 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt @@ -26,7 +26,6 @@ import javax.inject.Inject class TimelineItemContentStateFactory @Inject constructor( private val timelineEventFormatter: TimelineEventFormatter, ) { - fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent { val text = timelineEventFormatter.format(eventTimelineItem) return TimelineItemStateEventContent(text.orEmpty().toString()) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt index 1607bb77c4..91b62a6d51 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt @@ -17,13 +17,42 @@ package io.element.android.features.messages.impl.timeline.factories.event import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent -import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent +import io.element.android.libraries.androidutils.filesize.FileSizeFormatter +import io.element.android.libraries.core.mimetype.MimeTypes +import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent +import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractor import javax.inject.Inject -class TimelineItemContentStickerFactory @Inject constructor() { +class TimelineItemContentStickerFactory @Inject constructor( + private val fileSizeFormatter: FileSizeFormatter, + private val fileExtensionExtractor: FileExtensionExtractor +) { + private fun aspectRatioOf(width: Long?, height: Long?): Float? { + val result = if (height != null && width != null) { + width.toFloat() / height.toFloat() + } else { + null + } - fun create(@Suppress("UNUSED_PARAMETER") content: StickerContent): TimelineItemEventContent { - return TimelineItemUnknownContent + return result?.takeIf { it.isFinite() } + } + + fun create(content: StickerContent): TimelineItemEventContent { + val aspectRatio = aspectRatioOf(content.info.width, content.info.height) + + return TimelineItemStickerContent( + body = content.body, + mediaSource = MediaSource(content.url), + thumbnailSource = content.info.thumbnailSource, + mimeType = content.info.mimetype ?: MimeTypes.OctetStream, + blurhash = content.info.blurhash, + width = content.info.width?.toInt(), + height = content.info.height?.toInt(), + aspectRatio = aspectRatio, + formattedFileSize = fileSizeFormatter.format(content.info.size ?: 0), + fileExtension = fileExtensionExtractor.extractFromName(content.body) + ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentUTDFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentUTDFactory.kt index 3281f6dd9b..d53e04d96e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentUTDFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentUTDFactory.kt @@ -22,7 +22,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecry import javax.inject.Inject class TimelineItemContentUTDFactory @Inject constructor() { - fun create(content: UnableToDecryptContent): TimelineItemEventContent { return TimelineItemEncryptedContent(content.data) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt index 51520c66dc..0f204921d2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt @@ -43,7 +43,6 @@ class TimelineItemEventFactory @Inject constructor( private val matrixClient: MatrixClient, private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, ) { - suspend fun create( currentTimelineItem: MatrixTimelineItem.Event, index: Int, @@ -193,7 +192,8 @@ class TimelineItemEventFactory @Inject constructor( } } } - previousSender == currentSender /* && nextSender != currentSender (== true) */ -> { + // In the following case, we have nextSender != currentSender == true + previousSender == currentSender -> { if (previousIsGroupable) { TimelineItemGroupPosition.Last } else { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt index 5a778fe28a..38c869f6f8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt @@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTime import javax.inject.Inject class TimelineItemDaySeparatorFactory @Inject constructor(private val daySeparatorFormatter: DaySeparatorFormatter) { - fun create(virtualItem: VirtualTimelineItem.DayDivider): TimelineItemVirtualModel { val formattedDate = daySeparatorFormatter.format(virtualItem.timestamp) return TimelineItemDaySeparatorModel( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt index cc513705e3..64d08acacb 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt @@ -27,7 +27,6 @@ import javax.inject.Inject class TimelineItemVirtualFactory @Inject constructor( private val daySeparatorFactory: TimelineItemDaySeparatorFactory, ) { - fun create( virtualTimelineItem: MatrixTimelineItem.Virtual, ): TimelineItem.Virtual { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt index 51ab200c48..f2ecaee85d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt @@ -27,6 +27,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRoomMembershipContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent @@ -54,6 +55,7 @@ internal fun TimelineItem.Event.canBeGrouped(): Boolean { is TimelineItemTextBasedContent, is TimelineItemEncryptedContent, is TimelineItemImageContent, + is TimelineItemStickerContent, is TimelineItemFileContent, is TimelineItemVideoContent, is TimelineItemAudioContent, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt index f819be4161..b0d2f39338 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt @@ -25,7 +25,6 @@ import javax.inject.Inject @SingleIn(RoomScope::class) class TimelineItemGrouper @Inject constructor() { - /** * Keys are identifier of items in a group, only one by group will be kept. * Values are the actual groupIds. diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetails.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetails.kt index 14596ac1f0..759ead5b61 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetails.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetails.kt @@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.event.EventContent import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent +import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.ui.messages.toPlainText @@ -45,6 +46,10 @@ fun InReplyTo.map() = when (this) { val messageContent = content as MessageContent (messageContent.type as? TextMessageType)?.toPlainText() ?: messageContent.body } + is StickerContent -> { + val stickerContent = content as StickerContent + stickerContent.body + } else -> null } ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToMetadata.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToMetadata.kt index 63775d5ac2..d5803ddd72 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToMetadata.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToMetadata.kt @@ -19,12 +19,14 @@ package io.element.android.features.messages.impl.timeline.model import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.ui.res.stringResource +import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent import io.element.android.libraries.matrix.api.timeline.item.event.PollContent +import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo @@ -33,7 +35,6 @@ import io.element.android.libraries.ui.strings.CommonStrings @Immutable internal sealed interface InReplyToMetadata { - val text: String? data class Thumbnail( @@ -98,6 +99,13 @@ internal fun InReplyToDetails.metadata(): InReplyToMetadata? = when (eventConten ) else -> InReplyToMetadata.Text(textContent ?: eventContent.body) } + is StickerContent -> InReplyToMetadata.Thumbnail( + AttachmentThumbnailInfo( + thumbnailSource = MediaSource(eventContent.url), + textContent = eventContent.body, + type = AttachmentThumbnailType.Image + ) + ) is PollContent -> InReplyToMetadata.Thumbnail( AttachmentThumbnailInfo( textContent = eventContent.question, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/NewEventState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/NewEventState.kt index 9a00c9fd8f..0d37e529b0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/NewEventState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/NewEventState.kt @@ -21,5 +21,7 @@ package io.element.android.features.messages.impl.timeline.model * This can be used to scroll to the bottom of the list when a new event is added. */ enum class NewEventState { - None, FromMe, FromOther + None, + FromMe, + FromOther } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index 916a9b88b4..fa00c26760 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.timeline.model import androidx.compose.runtime.Immutable import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel import io.element.android.libraries.designsystem.components.avatar.AvatarData @@ -31,7 +32,6 @@ import kotlinx.collections.immutable.ImmutableList @Immutable sealed interface TimelineItem { - fun identifier(): String = when (this) { is Event -> id is Virtual -> id @@ -72,7 +72,6 @@ sealed interface TimelineItem { val debugInfo: TimelineItemDebugInfo, val origin: TimelineItemEventOrigin?, ) : TimelineItem { - val showSenderInformation = groupPosition.isNew() && !isMine val safeSenderName: String = senderDisplayName ?: senderId.value @@ -81,6 +80,8 @@ sealed interface TimelineItem { val isTextMessage: Boolean = content is TimelineItemTextBasedContent + val isSticker: Boolean = content is TimelineItemStickerContent + val isRemote = eventId != null } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemGroupPosition.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemGroupPosition.kt index 556493bd84..1874fc5b65 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemGroupPosition.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemGroupPosition.kt @@ -65,4 +65,3 @@ sealed interface TimelineItemGroupPosition { else -> false } } - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt index aa228efcbd..05af93cc33 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt @@ -17,6 +17,7 @@ package io.element.android.features.messages.impl.timeline.model.event import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize import kotlin.time.Duration data class TimelineItemAudioContent( @@ -27,9 +28,8 @@ data class TimelineItemAudioContent( val formattedFileSize: String, val fileExtension: String, ) : TimelineItemEventContent { - val fileExtensionAndSize = - io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize( + formatFileExtensionAndSize( fileExtension, formattedFileSize ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt index feb3246e54..ad0f62a94d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt @@ -47,6 +47,7 @@ fun TimelineItemEventContent.canBeRepliedTo(): Boolean = /** * Return true if user can react (i.e. send a reaction) on the event content. + * This does not take into account the power level of the user. */ fun TimelineItemEventContent.canReact(): Boolean = when (this) { @@ -55,6 +56,7 @@ fun TimelineItemEventContent.canReact(): Boolean = is TimelineItemEncryptedContent, is TimelineItemFileContent, is TimelineItemImageContent, + is TimelineItemStickerContent, is TimelineItemLocationContent, is TimelineItemPollContent, is TimelineItemVoiceContent, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt index 974607c33b..9878a28402 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt @@ -46,7 +46,6 @@ class TimelineItemEventContentProvider : PreviewParameterProvider { - private fun buildSpanned(text: String) = buildSpannedString { inSpans(StyleSpan(Typeface.BOLD)) { append("Rich Text") diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContentProvider.kt index dc5a03fa2b..a21f262071 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContentProvider.kt @@ -36,4 +36,3 @@ fun aTimelineItemLocationContent(description: String? = null) = TimelineItemLoca ), description = description, ) - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt new file mode 100644 index 0000000000..9c8070ecc9 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt @@ -0,0 +1,38 @@ +/* + * 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.messages.impl.timeline.model.event + +import io.element.android.libraries.matrix.api.media.MediaSource + +data class TimelineItemStickerContent( + val body: String, + val mediaSource: MediaSource, + val thumbnailSource: MediaSource?, + val formattedFileSize: String, + val fileExtension: String, + val mimeType: String, + val blurhash: String?, + val width: Int?, + val height: Int?, + val aspectRatio: Float? +) : TimelineItemEventContent { + override val type: String = "TimelineItemStickerContent" + + /* Stickers are supposed to be small images so + we allow using the mediaSource (unless the url is empty) */ + val preferredMediaSource = if (mediaSource.url.isEmpty()) thumbnailSource else mediaSource +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContentProvider.kt new file mode 100644 index 0000000000..5080cd1c84 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContentProvider.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.messages.impl.timeline.model.event + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.media3.common.MimeTypes +import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.matrix.ui.components.A_BLUR_HASH + +open class TimelineItemStickerContentProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aTimelineItemStickerContent(), + aTimelineItemStickerContent().copy(aspectRatio = 1.0f), + aTimelineItemStickerContent().copy(aspectRatio = 1.5f), + ) +} + +fun aTimelineItemStickerContent() = TimelineItemStickerContent( + body = "a body", + mediaSource = MediaSource(""), + thumbnailSource = null, + mimeType = MimeTypes.IMAGE_JPEG, + blurhash = A_BLUR_HASH, + width = null, + height = 128, + aspectRatio = 0.5f, + formattedFileSize = "4MB", + fileExtension = "jpg" +) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/MessageSummaryFormatterImpl.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/MessageSummaryFormatterImpl.kt index 762e43cf92..97d5188466 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/MessageSummaryFormatterImpl.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/MessageSummaryFormatterImpl.kt @@ -28,6 +28,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemProfileChangeContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent @@ -41,6 +42,11 @@ import javax.inject.Inject class MessageSummaryFormatterImpl @Inject constructor( @ApplicationContext private val context: Context, ) : MessageSummaryFormatter { + companion object { + // Max characters to display in the summary message. This works around https://github.com/element-hq/element-x-android/issues/2105 + private const val MAX_SAFE_LENGTH = 500 + } + override fun format(event: TimelineItem.Event): String { return when (event.content) { is TimelineItemTextBasedContent -> event.content.plainText @@ -53,9 +59,10 @@ class MessageSummaryFormatterImpl @Inject constructor( is TimelineItemVoiceContent -> context.getString(CommonStrings.common_voice_message) is TimelineItemUnknownContent -> context.getString(CommonStrings.common_unsupported_event) is TimelineItemImageContent -> context.getString(CommonStrings.common_image) + is TimelineItemStickerContent -> context.getString(CommonStrings.common_sticker) is TimelineItemVideoContent -> context.getString(CommonStrings.common_video) is TimelineItemFileContent -> context.getString(CommonStrings.common_file) is TimelineItemAudioContent -> context.getString(CommonStrings.common_audio) - } + }.take(MAX_SAFE_LENGTH) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/VoiceMessageException.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/VoiceMessageException.kt index 7ca95ad9a7..0d453edb1f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/VoiceMessageException.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/VoiceMessageException.kt @@ -18,12 +18,15 @@ package io.element.android.features.messages.impl.voicemessages internal sealed class VoiceMessageException : Exception() { data class FileException( - override val message: String?, override val cause: Throwable? = null + override val message: String?, + override val cause: Throwable? = null ) : VoiceMessageException() data class PermissionMissing( - override val message: String?, override val cause: Throwable? + override val message: String?, + override val cause: Throwable? ) : VoiceMessageException() data class PlayMessageError( - override val message: String?, override val cause: Throwable? + override val message: String?, + override val cause: Throwable? ) : VoiceMessageException() } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerEvents.kt index 0c83e834b0..100bdbadb6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerEvents.kt @@ -17,20 +17,20 @@ package io.element.android.features.messages.impl.voicemessages.composer import androidx.lifecycle.Lifecycle -import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent +import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent sealed interface VoiceMessageComposerEvents { data class RecorderEvent( val recorderEvent: VoiceMessageRecorderEvent - ): VoiceMessageComposerEvents + ) : VoiceMessageComposerEvents data class PlayerEvent( val playerEvent: VoiceMessagePlayerEvent, - ): VoiceMessageComposerEvents - data object SendVoiceMessage: VoiceMessageComposerEvents - data object DeleteVoiceMessage: VoiceMessageComposerEvents - data object AcceptPermissionRationale: VoiceMessageComposerEvents - data object DismissPermissionsRationale: VoiceMessageComposerEvents - data class LifecycleEvent(val event: Lifecycle.Event): VoiceMessageComposerEvents - data object DismissSendFailureDialog: VoiceMessageComposerEvents + ) : VoiceMessageComposerEvents + data object SendVoiceMessage : VoiceMessageComposerEvents + data object DeleteVoiceMessage : VoiceMessageComposerEvents + data object AcceptPermissionRationale : VoiceMessageComposerEvents + data object DismissPermissionsRationale : VoiceMessageComposerEvents + data class LifecycleEvent(val event: Lifecycle.Event) : VoiceMessageComposerEvents + data object DismissSendFailureDialog : VoiceMessageComposerEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt index 0e1b960ecc..ea08da4448 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt @@ -194,7 +194,6 @@ class VoiceMessageComposerPlayer @Inject constructor( val currentPosition: Long, val progress: Float, ) { - companion object { val Initial = State( playState = PlayState.Stopped, @@ -247,4 +246,3 @@ class VoiceMessageComposerPlayer @Inject constructor( } } } - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt index 528711b71e..c8ad616154 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt @@ -37,8 +37,8 @@ import io.element.android.libraries.di.SingleIn import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.permissions.api.PermissionsEvents import io.element.android.libraries.permissions.api.PermissionsPresenter -import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent +import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent import io.element.android.libraries.textcomposer.model.VoiceMessageState import io.element.android.libraries.voicerecorder.api.VoiceRecorder import io.element.android.libraries.voicerecorder.api.VoiceRecorderState @@ -121,7 +121,7 @@ class VoiceMessageComposerPresenter @Inject constructor( } } } - val onPlayerEvent = { event: VoiceMessagePlayerEvent -> localCoroutineScope.launch { + val onPlayerEvent = { event: VoiceMessagePlayerEvent -> localCoroutineScope.launch { when (event) { VoiceMessagePlayerEvent.Play -> player.play() @@ -129,7 +129,7 @@ class VoiceMessageComposerPresenter @Inject constructor( is VoiceMessagePlayerEvent.Seek -> player.seek(event.position) } } - } } + } val onAcceptPermissionsRationale = { permissionState.eventSink(PermissionsEvents.OpenSystemSettingAndCloseDialog) @@ -216,7 +216,7 @@ class VoiceMessageComposerPresenter @Inject constructor( recorderState: VoiceRecorderState, isSending: Boolean, ): VoiceMessageState { - val showCursor by remember(playerState.isStopped, isSending) { derivedStateOf { !playerState.isStopped && !isSending }} + val showCursor by remember(playerState.isStopped, isSending) { derivedStateOf { !playerState.isStopped && !isSending } } val playerTime by remember(playerState, recorderState) { derivedStateOf { displayTime(playerState, recorderState) } } val waveform by remember(recorderState) { derivedStateOf { recorderState.finishedWaveform() } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerState.kt index aaab388ec1..a1737a9cdc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerState.kt @@ -27,4 +27,3 @@ data class VoiceMessageComposerState( val keepScreenOn: Boolean, val eventSink: (VoiceMessageComposerEvents) -> Unit, ) - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerStateProvider.kt index 0e884f5491..9f835681d8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerStateProvider.kt @@ -52,5 +52,3 @@ internal fun aVoiceMessagePreviewState() = VoiceMessageState.Preview( ) internal var aWaveformLevels = List(100) { it.toFloat() / 100 }.toPersistentList() - - diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageMediaRepo.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageMediaRepo.kt index 62ddc4845b..8ce6aa0acf 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageMediaRepo.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessageMediaRepo.kt @@ -35,7 +35,6 @@ import java.io.File * Whenever a given mxc is found in the cache, it is returned immediately. */ interface VoiceMessageMediaRepo { - /** * Factory for [VoiceMessageMediaRepo]. */ @@ -73,7 +72,6 @@ class DefaultVoiceMessageMediaRepo @AssistedInject constructor( @Assisted("mimeType") private val mimeType: String?, @Assisted("body") private val body: String?, ) : VoiceMessageMediaRepo { - @ContributesBinding(RoomScope::class) @AssistedFactory fun interface Factory : VoiceMessageMediaRepo.Factory { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePlayer.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePlayer.kt index 76180397f4..1ab499c79f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePlayer.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePlayer.kt @@ -34,9 +34,7 @@ import javax.inject.Inject * A media player specialized in playing a single voice message. */ interface VoiceMessagePlayer { - fun interface Factory { - /** * Creates a [VoiceMessagePlayer]. * @@ -126,7 +124,6 @@ class DefaultVoiceMessagePlayer( mimeType: String?, body: String?, ) : VoiceMessagePlayer { - @ContributesBinding(RoomScope::class) // Scoped types can't use @AssistedInject. class Factory @Inject constructor( private val mediaPlayer: MediaPlayer, @@ -197,7 +194,8 @@ class DefaultVoiceMessagePlayer( mediaPlayer.setMedia( uri = mediaFile.path, mediaId = eventId.value, - mimeType = MimeTypes.Ogg, // Files in the voice cache have no extension so we need to set the mime type manually. + // Files in the voice cache have no extension so we need to set the mime type manually. + mimeType = MimeTypes.Ogg, startPositionMs = if (state.isEnded) 0L else state.currentPosition, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt index fa6632308a..13c99a2640 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt @@ -33,7 +33,7 @@ import io.element.android.features.messages.impl.timeline.di.TimelineItemEventCo import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactory import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent import io.element.android.features.messages.impl.voicemessages.VoiceMessageException -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.di.RoomScope @@ -58,7 +58,6 @@ class VoiceMessagePresenter @AssistedInject constructor( private val scope: CoroutineScope, @Assisted private val content: TimelineItemVoiceContent, ) : Presenter { - @AssistedFactory fun interface Factory : TimelineItemPresenterFactory { override fun create(content: TimelineItemVoiceContent): VoiceMessagePresenter @@ -71,11 +70,10 @@ class VoiceMessagePresenter @AssistedInject constructor( body = content.body, ) - private val play = mutableStateOf>(Async.Uninitialized) + private val play = mutableStateOf>(AsyncData.Uninitialized) @Composable override fun present(): VoiceMessageState { - val playerState by player.state.collectAsState( VoiceMessagePlayer.State( isReady = false, @@ -91,8 +89,8 @@ class VoiceMessagePresenter @AssistedInject constructor( when { content.eventId == null -> VoiceMessageState.Button.Disabled playerState.isPlaying -> VoiceMessageState.Button.Pause - play.value is Async.Loading -> VoiceMessageState.Button.Downloading - play.value is Async.Failure -> VoiceMessageState.Button.Retry + play.value is AsyncData.Loading -> VoiceMessageState.Button.Downloading + play.value is AsyncData.Failure -> VoiceMessageState.Button.Retry else -> VoiceMessageState.Button.Play } } diff --git a/features/messages/impl/src/main/res/values-de/translations.xml b/features/messages/impl/src/main/res/values-de/translations.xml index 4798aaf696..2ecb2a31a8 100644 --- a/features/messages/impl/src/main/res/values-de/translations.xml +++ b/features/messages/impl/src/main/res/values-de/translations.xml @@ -20,7 +20,7 @@ "Den ganzen Raum benachrichtigen" "Prüfe, ob du alle aktuellen und zukünftigen Nachrichten dieses Benutzers ausblenden möchtest" "Kamera" - "Foto machen" + "Foto aufnehmen" "Video aufnehmen" "Anhang" "Foto- und Videobibliothek" @@ -34,10 +34,10 @@ "Du bist allein in diesem Chat" "Nachricht wurde kopiert" "Du bist nicht berechtigt, in diesem Raum zu posten" - "Benutzerdefinierte Einstellung zulassen" + "Benutzerdefinierte Einstellungen verwenden" "Wenn du diese Option aktivierst, wird deine Standardeinstellung außer Kraft gesetzt." "Benachrichtige mich in diesem Chat bei" - "Du kannst das in deinem %1$s ändern." + "Zum Anpassen der Standardeinstellungen gehe zu: %1$s" "Globale Einstellungen" "Standardeinstellung" "Benutzerdefinierte Einstellung entfernen" @@ -55,7 +55,7 @@ "Weniger anzeigen" "Zum Aufnehmen gedrückt halten" "Alle" - "Benutzer sperren" + "Benutzer blockieren" "Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut." "Nur Erwähnungen und Schlüsselwörter" diff --git a/features/messages/impl/src/main/res/values-es/translations.xml b/features/messages/impl/src/main/res/values-es/translations.xml index 493fdb7ad7..02710138f4 100644 --- a/features/messages/impl/src/main/res/values-es/translations.xml +++ b/features/messages/impl/src/main/res/values-es/translations.xml @@ -17,6 +17,45 @@ "Este es el principio de %1$s." "Este es el principio de esta conversación." "Nuevos" + "Notificar a toda la sala" "Marque si quieres ocultar todos los mensajes actuales y futuros de este usuario" + "Cámara" + "Hacer foto" + "Grabar video" + "Archivo adjunto" + "Biblioteca de fotos y vídeos" + "Ubicación" + "Encuesta" + "Formato de Texto" + "El historial de mensajes no está disponible en este momento." + "El historial de mensajes no está disponible en esta sala. Verifica este dispositivo para ver tu historial de mensajes." + "No se pudieron recuperar los detalles del usuario" + "¿Quieres volver a invitarlos?" + "Estás solo en este chat" + "Mensaje copiado" + "No tienes permiso para publicar en esta sala" + "Permitir configuración personalizada" + "Si activas esta opción, anularás tu configuración por defecto" + "Notificarme en este chat para" + "Puedes cambiarlo en tus %1$s." + "ajustes globales" + "Ajustes predeterminados" + "Eliminar configuración personalizada" + "Se ha producido un error al cargar la configuración de las notificaciones." + "No se pudo restaurar el modo predeterminado, por favor vuelve a intentarlo de nuevo." + "No se pudo cambiar el modo, por favor inténtalo de nuevo." + "Tu servidor principal no admite esta opción en salas cifradas, no recibirás notificaciones en esta sala." + "Todos los mensajes" + "En esta sala, notificarme por" + "Mostrar menos" + "Mostrar más" + "Enviar de nuevo" + "No se pudo enviar tu mensaje" + "Añadir emoji" + "Mostrar menos" + "Mantén pulsado para grabar" + "Todos" "Bloquear usuario" + "Error al procesar el contenido multimedia, por favor inténtalo de nuevo." + "Únicamente Menciones y Palabras clave" diff --git a/features/messages/impl/src/main/res/values-it/translations.xml b/features/messages/impl/src/main/res/values-it/translations.xml index c36e8c515c..0dd03afae2 100644 --- a/features/messages/impl/src/main/res/values-it/translations.xml +++ b/features/messages/impl/src/main/res/values-it/translations.xml @@ -17,6 +17,45 @@ "Questo è l\'inizio di %1$s." "Questo è l\'inizio della conversazione." "Nuovo" + "Avvisa l\'intera stanza" "Seleziona se vuoi nascondere tutti i messaggi attuali e futuri di questo utente" + "Fotocamera" + "Scatta foto" + "Registra video" + "Allegato" + "Libreria di foto e video" + "Posizione" + "Sondaggio" + "Formattazione del testo" + "La cronologia dei messaggi non è attualmente disponibile." + "La cronologia dei messaggi non è disponibile in questa stanza. Verifica questo dispositivo per vedere la cronologia dei messaggi." + "Impossibile recuperare i dettagli dell\'utente" + "Vorresti invitarli di nuovo?" + "Ci sei solo tu in questa chat" + "Messaggio copiato" + "Non sei autorizzato a postare in questa stanza" + "Consenti impostazione personalizzata" + "L\'attivazione di questa opzione sovrascriverà l\'impostazione predefinita" + "Avvisami in questa chat per" + "Puoi cambiarlo nelle tue %1$s." + "impostazioni globali" + "Impostazione predefinita" + "Rimuovi l\'impostazione personalizzata" + "Si è verificato un errore durante il caricamento delle impostazioni di notifica." + "Ripristino della modalità predefinita fallito, riprova." + "Impossibile impostare la modalità, riprova." + "Il tuo homeserver non supporta questa opzione nelle stanze criptate, quindi non riceverai notifiche in questa stanza." + "Tutti i messaggi" + "In questa stanza, avvisami per" + "Mostra meno" + "Mostra di più" + "Invia di nuovo" + "Il tuo messaggio non è stato inviato" + "Aggiungi emoji" + "Mostra meno" + "Tieni premuto per registrare" + "Tutti" "Blocca utente" + "Elaborazione del file multimediale da caricare fallita, riprova." + "Solo menzioni e parole chiave" diff --git a/features/messages/impl/src/main/res/values-ru/translations.xml b/features/messages/impl/src/main/res/values-ru/translations.xml index 77f7e1d0e5..c3175d9a53 100644 --- a/features/messages/impl/src/main/res/values-ru/translations.xml +++ b/features/messages/impl/src/main/res/values-ru/translations.xml @@ -5,7 +5,7 @@ "Еда и напитки" "Животные и природа" "Объекты" - "Смайлы и люди" + "Улыбки и люди" "Путешествия и места" "Символы" diff --git a/features/messages/impl/src/main/res/values-zh-rTW/translations.xml b/features/messages/impl/src/main/res/values-zh-rTW/translations.xml index 4f22c5bb7a..4f75bf4b0d 100644 --- a/features/messages/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/messages/impl/src/main/res/values-zh-rTW/translations.xml @@ -21,6 +21,7 @@ "位置" "投票" "格式化文字" + "您想要邀請他們回來嗎?" "此聊天室只有您一個人" "訊息已複製" "您沒有權限在此聊天室傳送訊息" @@ -35,6 +36,7 @@ "無法傳送您的訊息" "新增表情符號" "較少" + "所有人" "封鎖使用者" "僅限提及與關鍵字" diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 876884b6cf..1ad6a693ab 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -51,7 +51,7 @@ import io.element.android.features.networkmonitor.test.FakeNetworkMonitor import io.element.android.features.poll.test.actions.FakeEndPollAction import io.element.android.features.poll.test.actions.FakeSendPollResponseAction import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.designsystem.components.avatar.AvatarData @@ -103,7 +103,6 @@ import kotlin.time.Duration.Companion.milliseconds @Suppress("LargeClass") class MessagesPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -117,14 +116,14 @@ class MessagesPresenterTest { }.test { val initialState = consumeItemsUntilTimeout().last() assertThat(initialState.roomId).isEqualTo(A_ROOM_ID) - assertThat(initialState.roomName).isEqualTo(Async.Success("")) + assertThat(initialState.roomName).isEqualTo(AsyncData.Success("")) assertThat(initialState.roomAvatar) - .isEqualTo(Async.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom))) + .isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom))) assertThat(initialState.userHasPermissionToSendMessage).isTrue() assertThat(initialState.userHasPermissionToRedact).isFalse() assertThat(initialState.hasNetworkConnection).isTrue() assertThat(initialState.snackbarMessage).isNull() - assertThat(initialState.inviteProgress).isEqualTo(Async.Uninitialized) + assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized) assertThat(initialState.showReinvitePrompt).isFalse() } } @@ -685,7 +684,6 @@ class MessagesPresenterTest { navigator = navigator, encryptionService = FakeEncryptionService(), verificationService = FakeSessionVerificationService(), - featureFlagService = FakeFeatureFlagService(), redactedVoiceMessageManager = FakeRedactedVoiceMessageManager(), endPollAction = FakeEndPollAction(), sendPollResponseAction = FakeSendPollResponseAction(), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt index 3c8dcf6e85..0dbdc7194b 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt @@ -39,7 +39,6 @@ import org.junit.Rule import org.junit.Test class ActionListPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -62,14 +61,15 @@ class ActionListPresenterTest { }.test { val initialState = awaitItem() val messageEvent = aMessageEvent(isMine = true, content = TimelineItemRedactedContent) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( - messageEvent, - persistentListOf( + event = messageEvent, + displayEmojiReactions = false, + actions = persistentListOf( TimelineItemAction.ViewSource, ) ) @@ -87,14 +87,15 @@ class ActionListPresenterTest { }.test { val initialState = awaitItem() val messageEvent = aMessageEvent(isMine = false, content = TimelineItemRedactedContent) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( - messageEvent, - persistentListOf( + event = messageEvent, + displayEmojiReactions = false, + actions = persistentListOf( TimelineItemAction.ViewSource, ) ) @@ -115,14 +116,15 @@ class ActionListPresenterTest { isMine = false, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( - messageEvent, - persistentListOf( + event = messageEvent, + displayEmojiReactions = true, + actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, TimelineItemAction.Copy, @@ -147,14 +149,15 @@ class ActionListPresenterTest { isMine = false, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = false)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = false, canSendReaction = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( - messageEvent, - persistentListOf( + event = messageEvent, + displayEmojiReactions = true, + actions = persistentListOf( TimelineItemAction.Forward, TimelineItemAction.Copy, TimelineItemAction.ViewSource, @@ -178,12 +181,45 @@ class ActionListPresenterTest { isMine = false, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = true, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = true, canSendMessage = true, canSendReaction = true)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( - messageEvent, - persistentListOf( + event = messageEvent, + displayEmojiReactions = true, + actions = persistentListOf( + TimelineItemAction.Reply, + TimelineItemAction.Forward, + TimelineItemAction.Copy, + TimelineItemAction.ViewSource, + TimelineItemAction.ReportContent, + TimelineItemAction.Redact, + ) + ) + ) + initialState.eventSink.invoke(ActionListEvents.Clear) + assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) + } + } + + @Test + fun `present - compute for others message and cannot send reaction`() = runTest { + val presenter = createActionListPresenter(isDeveloperModeEnabled = true) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val messageEvent = aMessageEvent( + isMine = false, + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) + ) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = true, canSendMessage = true, canSendReaction = false)) + val successState = awaitItem() + assertThat(successState.target).isEqualTo( + ActionListState.Target.Success( + event = messageEvent, + displayEmojiReactions = false, + actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, TimelineItemAction.Copy, @@ -209,14 +245,15 @@ class ActionListPresenterTest { isMine = true, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( - messageEvent, - persistentListOf( + event = messageEvent, + displayEmojiReactions = true, + actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, TimelineItemAction.Edit, @@ -242,14 +279,15 @@ class ActionListPresenterTest { isMine = true, content = aTimelineItemImageContent(), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( - messageEvent, - persistentListOf( + event = messageEvent, + displayEmojiReactions = true, + actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, TimelineItemAction.ViewSource, @@ -273,14 +311,15 @@ class ActionListPresenterTest { isMine = true, content = aTimelineItemStateEventContent(), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, canRedact = false, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( - stateEvent, - persistentListOf( + event = stateEvent, + displayEmojiReactions = false, + actions = persistentListOf( TimelineItemAction.Copy, TimelineItemAction.ViewSource, ) @@ -302,14 +341,15 @@ class ActionListPresenterTest { isMine = true, content = aTimelineItemStateEventContent(), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, canRedact = false, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( - stateEvent, - persistentListOf( + event = stateEvent, + displayEmojiReactions = false, + actions = persistentListOf( TimelineItemAction.Copy, ) ) @@ -330,14 +370,15 @@ class ActionListPresenterTest { isMine = true, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( - messageEvent, - persistentListOf( + event = messageEvent, + displayEmojiReactions = true, + actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, TimelineItemAction.Edit, @@ -367,13 +408,12 @@ class ActionListPresenterTest { content = TimelineItemRedactedContent, ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(redactedEvent, canRedact = false, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(redactedEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) awaitItem().run { assertThat(target).isEqualTo(ActionListState.Target.None) - assertThat(displayEmojiReactions).isFalse() } } } @@ -386,24 +426,25 @@ class ActionListPresenterTest { }.test { val initialState = awaitItem() val messageEvent = aMessageEvent( - eventId = null, // No event id, so it's not sent yet + // No event id, so it's not sent yet + eventId = null, isMine = true, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( - messageEvent, - persistentListOf( + event = messageEvent, + displayEmojiReactions = false, + actions = persistentListOf( TimelineItemAction.Edit, TimelineItemAction.Copy, TimelineItemAction.Redact, ) ) ) - assertThat(successState.displayEmojiReactions).isFalse() } } @@ -419,12 +460,13 @@ class ActionListPresenterTest { isEditable = true, content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = false)), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( - messageEvent, - persistentListOf( + event = messageEvent, + displayEmojiReactions = true, + actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Edit, TimelineItemAction.EndPoll, @@ -432,9 +474,9 @@ class ActionListPresenterTest { ) ) ) - assertThat(successState.displayEmojiReactions).isTrue() } } + @Test fun `present - compute for non-editable poll message`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = false) @@ -447,19 +489,19 @@ class ActionListPresenterTest { isEditable = false, content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = true)), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( - messageEvent, - persistentListOf( + event = messageEvent, + displayEmojiReactions = true, + actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.EndPoll, TimelineItemAction.Redact, ) ) ) - assertThat(successState.displayEmojiReactions).isTrue() } } @@ -475,18 +517,18 @@ class ActionListPresenterTest { isEditable = false, content = aTimelineItemPollContent(isEnded = true), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( - messageEvent, - persistentListOf( + event = messageEvent, + displayEmojiReactions = true, + actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Redact, ) ) ) - assertThat(successState.displayEmojiReactions).isTrue() } } @@ -501,19 +543,19 @@ class ActionListPresenterTest { isMine = true, content = aTimelineItemVoiceContent(), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true)) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( - messageEvent, - persistentListOf( + event = messageEvent, + displayEmojiReactions = true, + actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, TimelineItemAction.Redact, ) ) ) - assertThat(successState.displayEmojiReactions).isTrue() } } } @@ -522,4 +564,3 @@ private fun createActionListPresenter(isDeveloperModeEnabled: Boolean): ActionLi val preferencesStore = InMemoryPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled) return ActionListPresenter(preferencesStore = preferencesStore) } - diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt index 5b8afcfe37..0168fcbde5 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt @@ -41,7 +41,6 @@ import org.junit.Rule import org.junit.Test class AttachmentsPreviewPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/aMediaAttachment.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MediaAttachmentFixtures.kt similarity index 100% rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/aMediaAttachment.kt rename to features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MediaAttachmentFixtures.kt diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/aMessageEvent.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt similarity index 100% rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/aMessageEvent.kt rename to features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/timelineItemsFactory.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt similarity index 96% rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/timelineItemsFactory.kt rename to features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt index 587d9f47df..562424474c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/timelineItemsFactory.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt @@ -59,7 +59,10 @@ internal fun TestScope.aTimelineItemsFactory(): TimelineItemsFactory { htmlConverterProvider = FakeHtmlConverterProvider(), ), redactedMessageFactory = TimelineItemContentRedactedFactory(), - stickerFactory = TimelineItemContentStickerFactory(), + stickerFactory = TimelineItemContentStickerFactory( + fileSizeFormatter = FakeFileSizeFormatter(), + fileExtensionExtractor = FileExtensionExtractorWithoutValidation() + ), pollFactory = TimelineItemContentPollFactory(FakeFeatureFlagService(), FakePollContentStateFactory()), utdFactory = TimelineItemContentUTDFactory(), roomMembershipFactory = TimelineItemContentRoomMembershipFactory(timelineEventFormatter), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTests.kt index 1e35ec94bd..cf084700ca 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTests.kt @@ -31,7 +31,6 @@ import org.junit.Rule import org.junit.Test class ForwardMessagesPresenterTests { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagesummary/FakeMessageSummaryFormatter.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagesummary/FakeMessageSummaryFormatter.kt index 16f262c965..b1f28420a3 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagesummary/FakeMessageSummaryFormatter.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagesummary/FakeMessageSummaryFormatter.kt @@ -20,7 +20,6 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.utils.messagesummary.MessageSummaryFormatter class FakeMessageSummaryFormatter : MessageSummaryFormatter { - private var result = "A message" override fun format(event: TimelineItem.Event): String = result diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTests.kt index d09feaf49f..5f289faec9 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTests.kt @@ -20,7 +20,7 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.test.AN_EVENT_ID @@ -32,7 +32,6 @@ import org.junit.Rule import org.junit.Test class ReportMessagePresenterTests { - @get:Rule val warmUpRule = WarmUpRule() @@ -45,7 +44,7 @@ class ReportMessagePresenterTests { val initialState = awaitItem() assertThat(initialState.reason).isEmpty() assertThat(initialState.blockUser).isFalse() - assertThat(initialState.result).isInstanceOf(Async.Uninitialized::class.java) + assertThat(initialState.result).isInstanceOf(AsyncAction.Uninitialized::class.java) } } @@ -91,8 +90,8 @@ class ReportMessagePresenterTests { initialState.eventSink(ReportMessageEvents.ToggleBlockUser) skipItems(1) initialState.eventSink(ReportMessageEvents.Report) - assertThat(awaitItem().result).isInstanceOf(Async.Loading::class.java) - assertThat(awaitItem().result).isInstanceOf(Async.Success::class.java) + assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java) + assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java) assertThat(room.reportedContentCount).isEqualTo(1) } } @@ -106,8 +105,8 @@ class ReportMessagePresenterTests { }.test { val initialState = awaitItem() initialState.eventSink(ReportMessageEvents.Report) - assertThat(awaitItem().result).isInstanceOf(Async.Loading::class.java) - assertThat(awaitItem().result).isInstanceOf(Async.Success::class.java) + assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java) + assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java) assertThat(room.reportedContentCount).isEqualTo(1) } } @@ -123,13 +122,13 @@ class ReportMessagePresenterTests { }.test { val initialState = awaitItem() initialState.eventSink(ReportMessageEvents.Report) - assertThat(awaitItem().result).isInstanceOf(Async.Loading::class.java) + assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java) val resultState = awaitItem() - assertThat(resultState.result).isInstanceOf(Async.Failure::class.java) + assertThat(resultState.result).isInstanceOf(AsyncAction.Failure::class.java) assertThat(room.reportedContentCount).isEqualTo(1) resultState.eventSink(ReportMessageEvents.ClearError) - assertThat(awaitItem().result).isInstanceOf(Async.Uninitialized::class.java) + assertThat(awaitItem().result).isInstanceOf(AsyncAction.Uninitialized::class.java) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt index 295dc71a58..8f9842e309 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt @@ -90,7 +90,6 @@ import java.io.File @Suppress("LargeClass") class MessageComposerPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt index 0bfe497a0f..276544a057 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt @@ -28,7 +28,6 @@ import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class DefaultHtmlConverterProviderTest { - @get:Rule val composeTestRule = createComposeRule() @Test diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index 8bb3c0dcb8..5f68622c4d 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -35,7 +35,6 @@ import io.element.android.features.poll.api.actions.SendPollResponseAction import io.element.android.features.poll.test.actions.FakeEndPollAction import io.element.android.features.poll.test.actions.FakeSendPollResponseAction import io.element.android.libraries.featureflag.api.FeatureFlags -import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction @@ -65,7 +64,6 @@ import java.util.Date private const val FAKE_UNIQUE_ID = "FAKE_UNIQUE_ID" class TimelinePresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -89,7 +87,7 @@ class TimelinePresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - val initialState = awaitFirstItem() + val initialState = awaitItem() assertThat(initialState.paginationState.hasMoreToLoadBackwards).isTrue() assertThat(initialState.paginationState.isBackPaginating).isFalse() initialState.eventSink.invoke(TimelineEvents.LoadMore) @@ -133,8 +131,6 @@ class TimelinePresenterTest { }.test { assertThat(timeline.sendReadReceiptCount).isEqualTo(0) val initialState = awaitFirstItem() - // Wait for timeline items to be populated - skipItems(1) awaitWithLatch { latch -> timeline.sendReadReceiptLatch = latch initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0)) @@ -157,8 +153,6 @@ class TimelinePresenterTest { }.test { assertThat(timeline.sendReadReceiptCount).isEqualTo(0) val initialState = awaitFirstItem() - // Wait for timeline items to be populated - skipItems(1) awaitWithLatch { latch -> timeline.sendReadReceiptLatch = latch initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(1)) @@ -181,8 +175,6 @@ class TimelinePresenterTest { }.test { assertThat(timeline.sendReadReceiptCount).isEqualTo(0) val initialState = awaitFirstItem() - // Wait for timeline items to be populated - skipItems(1) awaitWithLatch { latch -> timeline.sendReadReceiptLatch = latch initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0)) @@ -354,8 +346,7 @@ class TimelinePresenterTest { presenter.present() }.test { assertThat(redactedVoiceMessageManager.invocations.size).isEqualTo(0) - awaitFirstItem() - awaitItem().let { + awaitFirstItem().let { assertThat(it.timelineItems).isNotEmpty() } assertThat(redactedVoiceMessageManager.invocations.size).isEqualTo(1) @@ -386,7 +377,6 @@ class TimelinePresenterTest { navigator = messagesNavigator, encryptionService = FakeEncryptionService(), verificationService = FakeSessionVerificationService(), - featureFlagService = FakeFeatureFlagService(), redactedVoiceMessageManager = redactedVoiceMessageManager, endPollAction = endPollAction, sendPollResponseAction = sendPollResponseAction, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTests.kt index a6ef642672..f7c26e02f7 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTests.kt @@ -29,7 +29,6 @@ import org.junit.Rule import org.junit.Test class CustomReactionPresenterTests { - @get:Rule val warmUpRule = WarmUpRule() @@ -40,7 +39,6 @@ class CustomReactionPresenterTests { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - val event = aTimelineItemEvent(eventId = AN_EVENT_ID) val initialState = awaitItem() assertThat(initialState.target).isEqualTo(CustomReactionState.Target.None) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/FakeEmojibaseProvider.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/FakeEmojibaseProvider.kt index 96513c8ab7..90fbfba746 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/FakeEmojibaseProvider.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/FakeEmojibaseProvider.kt @@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.timeline.components.customreac import io.element.android.emojibasebindings.EmojibaseStore -class FakeEmojibaseProvider: EmojibaseProvider { +class FakeEmojibaseProvider : EmojibaseProvider { override val emojibaseStore: EmojibaseStore get() = EmojibaseStore(mapOf()) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTests.kt index d7d51c5eb4..0159af02ec 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTests.kt @@ -35,12 +35,11 @@ import org.junit.Rule import org.junit.Test class ReactionSummaryPresenterTests { - @get:Rule val warmUpRule = WarmUpRule() private val aggregatedReaction = anAggregatedReaction(userId = A_USER_ID, key = "👍", isHighlighted = true) - private val roomMember = aRoomMember(userId = A_USER_ID, avatarUrl = AN_AVATAR_URL, displayName = A_USER_NAME) + private val roomMember = aRoomMember(userId = A_USER_ID, avatarUrl = AN_AVATAR_URL, displayName = A_USER_NAME) private val summaryEvent = ReactionSummaryEvents.ShowReactionSummary(AN_EVENT_ID, listOf(aggregatedReaction), aggregatedReaction.key) private val room = FakeMatrixRoom().apply { givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(roomMember))) @@ -81,5 +80,4 @@ class ReactionSummaryPresenterTests { assertThat(reactions?.first()?.senders?.first()?.user?.displayName).isEqualTo(A_USER_NAME) } } - } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTests.kt index ef98e3b3d9..5f65a9ad0a 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTests.kt @@ -27,7 +27,6 @@ import org.junit.Rule import org.junit.Test class ReadReceiptBottomSheetPresenterTests { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt index 4278679297..afed2d8b8f 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt @@ -29,7 +29,6 @@ import org.junit.Rule import org.junit.Test class RetrySendMenuPresenterTests { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt index 0fe79b8963..9a729b17a2 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt @@ -29,6 +29,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemNoticeContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent @@ -57,6 +58,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat import io.element.android.libraries.matrix.api.timeline.item.event.MessageType import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType @@ -74,7 +76,6 @@ import kotlin.time.Duration.Companion.minutes @RunWith(RobolectricTestRunner::class) class TimelineItemContentMessageFactoryTest { - @Test fun `test create OtherMessageType`() = runTest { val sut = createTimelineItemContentMessageFactory() @@ -438,6 +439,31 @@ class TimelineItemContentMessageFactoryTest { assertThat(result).isEqualTo(expected) } + @Test + fun `test create StickerMessageType`() = runTest { + val sut = createTimelineItemContentStickerFactory() + val result = sut.create( + content = createStickerContent( + "body", + ImageInfo(32, 32, "image/webp", 8192, null, MediaSource("thumbnail://url"), null), + "url" + ) + ) + val expected = TimelineItemStickerContent( + body = "body", + mediaSource = MediaSource(url = "url", json = null), + thumbnailSource = MediaSource(url = "thumbnail://url", json = null), + formattedFileSize = "8192 Bytes", + fileExtension = "", + mimeType = MimeTypes.WebP, + blurhash = null, + width = 32, + height = 32, + aspectRatio = 1.0f + ) + assertThat(result).isEqualTo(expected) + } + @Test fun `test create ImageMessageType with info`() = runTest { val sut = createTimelineItemContentMessageFactory() @@ -627,4 +653,20 @@ class TimelineItemContentMessageFactoryTest { featureFlagService = featureFlagService, htmlConverterProvider = FakeHtmlConverterProvider(htmlConverterTransform), ) + + private fun createStickerContent( + body: String = "Body", + inImageInfo: ImageInfo, + inUrl: String + ): StickerContent { + return StickerContent( + body = body, + info = inImageInfo, + url = inUrl + ) + } + private fun createTimelineItemContentStickerFactory() = TimelineItemContentStickerFactory( + fileSizeFormatter = FakeFileSizeFormatter(), + fileExtensionExtractor = FileExtensionExtractorWithoutValidation() + ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetailTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetailTest.kt index 33a88cb741..f07a73fd84 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetailTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetailTest.kt @@ -29,7 +29,6 @@ import io.element.android.libraries.matrix.test.A_USER_ID import org.junit.Test class InReplyToDetailTest { - @Test fun `map - with a not ready InReplyTo does not work`() { assertThat(InReplyTo.Pending.map()).isNull() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt index 4a659f4ce3..303fb366b9 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt @@ -57,7 +57,6 @@ import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds class VoiceMessageComposerPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -463,7 +462,6 @@ class VoiceMessageComposerPresenterTest { assertThat(showSendFailureDialog).isFalse() } - assertThat(matrixRoom.sendMediaCount).isEqualTo(0) testPauseAndDestroy(finalState) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/DefaultVoiceMessageMediaRepoTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/DefaultVoiceMessageMediaRepoTest.kt index 0208d06e95..a37f2e775f 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/DefaultVoiceMessageMediaRepoTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/DefaultVoiceMessageMediaRepoTest.kt @@ -29,7 +29,6 @@ import org.junit.rules.TemporaryFolder import java.io.File class DefaultVoiceMessageMediaRepoTest { - @get:Rule val temporaryFolder = TemporaryFolder() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/DefaultVoiceMessagePlayerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/DefaultVoiceMessagePlayerTest.kt index eb2c08d2be..f41bf7b801 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/DefaultVoiceMessagePlayerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/DefaultVoiceMessagePlayerTest.kt @@ -29,7 +29,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class DefaultVoiceMessagePlayerTest { - @Test fun `initial state`() = runTest { createDefaultVoiceMessagePlayer().state.test { @@ -144,7 +143,7 @@ class DefaultVoiceMessagePlayerTest { assertThat(it.currentPosition).isEqualTo(1000) assertThat(it.duration).isEqualTo(1000) } - awaitItem().let {// Additional spurious state due to MediaPlayer owner change. + awaitItem().let { // Additional spurious state due to MediaPlayer owner change. assertThat(it.isReady).isFalse() assertThat(it.isPlaying).isFalse() assertThat(it.isEnded).isFalse() @@ -164,7 +163,7 @@ class DefaultVoiceMessagePlayerTest { // Play player1 again. player1.state.test { - awaitItem().let {// Last previous state/ + awaitItem().let { // Last previous state/ assertThat(it.isReady).isFalse() assertThat(it.isPlaying).isFalse() assertThat(it.isEnded).isTrue() @@ -172,7 +171,7 @@ class DefaultVoiceMessagePlayerTest { assertThat(it.duration).isEqualTo(1000) } assertThat(player1.prepare().isSuccess).isTrue() - awaitItem().let {// Additional spurious state due to MediaPlayer owner change. + awaitItem().let { // Additional spurious state due to MediaPlayer owner change. assertThat(it.isReady).isFalse() assertThat(it.isPlaying).isFalse() assertThat(it.isEnded).isFalse() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/FakeRedactedVoiceMessageManager.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/FakeRedactedVoiceMessageManager.kt index 589847a3af..24ec179b51 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/FakeRedactedVoiceMessageManager.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/FakeRedactedVoiceMessageManager.kt @@ -19,7 +19,6 @@ package io.element.android.features.messages.impl.voicemessages.timeline import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem class FakeRedactedVoiceMessageManager : RedactedVoiceMessageManager { - private val _invocations: MutableList> = mutableListOf() val invocations: List> get() = _invocations diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/FakeVoiceMessageMediaRepo.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/FakeVoiceMessageMediaRepo.kt index 3fdef7eae8..96246be51c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/FakeVoiceMessageMediaRepo.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/FakeVoiceMessageMediaRepo.kt @@ -23,7 +23,6 @@ import java.io.File * A fake implementation of [VoiceMessageMediaRepo] for testing purposes. */ class FakeVoiceMessageMediaRepo : VoiceMessageMediaRepo { - var shouldFail = false override suspend fun getMediaFile(): Result = simulateLongTask { diff --git a/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/FakeHtmlConverterProvider.kt b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/FakeHtmlConverterProvider.kt index 0f7802632a..03de0f9f4d 100644 --- a/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/FakeHtmlConverterProvider.kt +++ b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/FakeHtmlConverterProvider.kt @@ -23,8 +23,7 @@ import io.element.android.wysiwyg.utils.HtmlConverter class FakeHtmlConverterProvider( private val transform: (String) -> CharSequence = { it }, -): HtmlConverterProvider { - +) : HtmlConverterProvider { @Composable override fun Update(currentUserId: UserId) = Unit diff --git a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt index 45f62f94b6..67721cab56 100644 --- a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt +++ b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt @@ -27,8 +27,8 @@ import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight /** * A view that displays a connectivity indicator when the device is offline, adding a default diff --git a/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt index ce881163f1..ddc75669fd 100644 --- a/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt +++ b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt @@ -50,7 +50,6 @@ class NetworkMonitorImpl @Inject constructor( @ApplicationContext context: Context, appCoroutineScope: CoroutineScope, ) : NetworkMonitor { - private val connectivityManager: ConnectivityManager = context.getSystemService(ConnectivityManager::class.java) override val connectivity: StateFlow = callbackFlow { @@ -61,7 +60,6 @@ class NetworkMonitorImpl @Inject constructor( * Debounce the result to avoid quick offline<->online changes. */ val callback = object : ConnectivityManager.NetworkCallback() { - private val activeNetworksCount = AtomicInteger(0) override fun onLost(network: Network) { diff --git a/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt b/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt index d183b05386..77898ca91f 100644 --- a/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt +++ b/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt @@ -22,7 +22,6 @@ import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint interface OnBoardingEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder interface NodeBuilder { diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/DefaultOnBoardingEntryPoint.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/DefaultOnBoardingEntryPoint.kt index b5ce63116d..504d400106 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/DefaultOnBoardingEntryPoint.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/DefaultOnBoardingEntryPoint.kt @@ -29,7 +29,6 @@ import javax.inject.Inject class DefaultOnBoardingEntryPoint @Inject constructor() : OnBoardingEntryPoint { override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): OnBoardingEntryPoint.NodeBuilder { return object : OnBoardingEntryPoint.NodeBuilder { - val plugins = ArrayList() override fun callback(callback: OnBoardingEntryPoint.Callback): OnBoardingEntryPoint.NodeBuilder { diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingConfig.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingConfig.kt index de164386b3..969ac382a4 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingConfig.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingConfig.kt @@ -17,6 +17,6 @@ package io.element.android.features.onboarding.impl object OnBoardingConfig { - const val canLoginWithQrCode = false - const val canCreateAccount = false + const val CAN_LOGIN_WITH_QR_CODE = false + const val CAN_CREATE_ACCOUNT = false } diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt index 21322657c1..f8653cc921 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt @@ -37,7 +37,6 @@ class OnBoardingNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins ) { - private fun onSignIn() { plugins().forEach { it.onSignIn() } } diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt index b26752fdbe..a514637026 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt @@ -33,8 +33,8 @@ class OnBoardingPresenter @Inject constructor( override fun present(): OnBoardingState { return OnBoardingState( isDebugBuild = buildMeta.buildType != BuildType.RELEASE, - canLoginWithQrCode = OnBoardingConfig.canLoginWithQrCode, - canCreateAccount = OnBoardingConfig.canCreateAccount, + canLoginWithQrCode = OnBoardingConfig.CAN_LOGIN_WITH_QR_CODE, + canCreateAccount = OnBoardingConfig.CAN_CREATE_ACCOUNT, ) } } diff --git a/features/onboarding/impl/src/main/res/values-es/translations.xml b/features/onboarding/impl/src/main/res/values-es/translations.xml index 2489344438..fb348b123d 100644 --- a/features/onboarding/impl/src/main/res/values-es/translations.xml +++ b/features/onboarding/impl/src/main/res/values-es/translations.xml @@ -1,5 +1,9 @@ + "Iniciar sesión manualmente" + "Iniciar sesión con un código QR" + "Crear cuenta" + "Bienvenido al Element más rápido de todos los tiempos. Diseñado para la velocidad y la simplicidad." "Bienvenido a %1$s. Vitaminado, para mayor rapidez y sencillez." - "Siéntente en tu Elemento" + "Siéntete en tu Elemento" diff --git a/features/onboarding/impl/src/main/res/values-it/translations.xml b/features/onboarding/impl/src/main/res/values-it/translations.xml index 652d9e6c22..738c4c8947 100644 --- a/features/onboarding/impl/src/main/res/values-it/translations.xml +++ b/features/onboarding/impl/src/main/res/values-it/translations.xml @@ -1,5 +1,9 @@ + "Accedi manualmente" + "Accedi con codice QR" + "Crea account" + "Benvenuti nell\'Element più veloce di sempre. Potenziato per velocità e semplicità." "Benvenuto su %1$s. Potenziato in velocità e semplicità." "Sii nel tuo elemento" diff --git a/features/onboarding/impl/src/main/res/values-ru/translations.xml b/features/onboarding/impl/src/main/res/values-ru/translations.xml index 31d09cd396..2a0ab37e1d 100644 --- a/features/onboarding/impl/src/main/res/values-ru/translations.xml +++ b/features/onboarding/impl/src/main/res/values-ru/translations.xml @@ -3,7 +3,7 @@ "Вход в систему вручную" "Войти с помощью QR-кода" "Создать учетную запись" - "Добро пожаловать в самый быстрый Element. Преимущество в скорости и простоте." - "Добро пожаловать в %1$s. Supercharged — это скорость и простота." - "Будь c element" + "Добро пожаловать в самый быстрый Element. Сверхзаряженность на скорость и простоту." + "Добро пожаловать в %1$s. Сверхзаряжен для скорости и простоты." + "Будьте в своем element" diff --git a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt index 9a356aa488..51d1ccc932 100644 --- a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt +++ b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt @@ -28,7 +28,6 @@ import org.junit.Rule import org.junit.Test class OnBoardingPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentView.kt index 706fa6e402..7b19c5da7e 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentView.kt @@ -102,14 +102,16 @@ fun PollContentView( var showConfirmation: Boolean by remember { mutableStateOf(false) } - if (showConfirmation) ConfirmationDialog( - content = stringResource(id = CommonStrings.common_poll_end_confirmation), - onSubmitClicked = { - onPollEnd() - showConfirmation = false - }, - onDismiss = { showConfirmation = false }, - ) + if (showConfirmation) { + ConfirmationDialog( + content = stringResource(id = CommonStrings.common_poll_end_confirmation), + onSubmitClicked = { + onPollEnd() + showConfirmation = false + }, + onDismiss = { showConfirmation = false }, + ) + } Column( modifier = modifier.fillMaxWidth(), diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultEndPollAction.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultEndPollAction.kt index 9959f1aecc..93e2cec78a 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultEndPollAction.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultEndPollAction.kt @@ -30,7 +30,6 @@ class DefaultEndPollAction @Inject constructor( private val room: MatrixRoom, private val analyticsService: AnalyticsService, ) : EndPollAction { - override suspend fun execute(pollStartId: EventId): Result { return room.endPoll( pollStartId = pollStartId, diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultSendPollResponseAction.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultSendPollResponseAction.kt index d6688ecb27..e60cb312af 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultSendPollResponseAction.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultSendPollResponseAction.kt @@ -30,7 +30,6 @@ class DefaultSendPollResponseAction @Inject constructor( private val room: MatrixRoom, private val analyticsService: AnalyticsService, ) : SendPollResponseAction { - override suspend fun execute(pollStartId: EventId, answerId: String): Result { return room.sendPollResponse( pollStartId = pollStartId, @@ -40,4 +39,3 @@ class DefaultSendPollResponseAction @Inject constructor( } } } - diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollException.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollException.kt index 20c10c4716..ccb94e3f32 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollException.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollException.kt @@ -18,10 +18,12 @@ package io.element.android.features.poll.impl.create internal sealed class CreatePollException : Exception() { data class GetPollFailed( - override val message: String?, override val cause: Throwable? + override val message: String?, + override val cause: Throwable? ) : CreatePollException() data class SavePollFailed( - override val message: String?, override val cause: Throwable? + override val message: String?, + override val cause: Throwable? ) : CreatePollException() } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt index a38ed3bb88..904303e672 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt @@ -39,7 +39,6 @@ class CreatePollNode @AssistedInject constructor( presenterFactory: CreatePollPresenter.Factory, analyticsService: AnalyticsService, ) : Node(buildContext, plugins = plugins) { - data class Inputs(val mode: CreatePollMode) : NodeInputs private val inputs: Inputs = inputs() diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt index 484e92eb15..046f953e48 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt @@ -52,7 +52,6 @@ class CreatePollPresenter @AssistedInject constructor( @Assisted private val navigateUp: () -> Unit, @Assisted private val mode: CreatePollMode, ) : Presenter { - @AssistedFactory interface Factory { fun create(backNavigator: () -> Unit, mode: CreatePollMode): CreatePollPresenter @@ -144,7 +143,7 @@ class CreatePollPresenter @AssistedInject constructor( poll = poll.withNewAnswer() } is CreatePollEvents.RemoveAnswer -> { - poll= poll.withAnswerRemoved(event.index) + poll = poll.withAnswerRemoved(event.index) } is CreatePollEvents.SetAnswer -> { poll = poll.withAnswerChanged(event.index, event.text) @@ -243,4 +242,3 @@ fun PollFormState.toUiAnswers(): ImmutableList { ) }.toImmutableList() } - diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/DefaultCreatePollEntryPoint.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/DefaultCreatePollEntryPoint.kt index 713fe8effc..df8c34ce57 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/DefaultCreatePollEntryPoint.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/DefaultCreatePollEntryPoint.kt @@ -31,7 +31,6 @@ class DefaultCreatePollEntryPoint @Inject constructor() : CreatePollEntryPoint { val plugins = ArrayList() return object : CreatePollEntryPoint.NodeBuilder { - override fun params(params: CreatePollEntryPoint.Params): CreatePollEntryPoint.NodeBuilder { plugins += CreatePollNode.Inputs(mode = params.mode) return this diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryFlowNode.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryFlowNode.kt index 918f6c7abb..5f1f62f18f 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryFlowNode.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryFlowNode.kt @@ -49,7 +49,6 @@ class PollHistoryFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins ) { - sealed interface NavTarget : Parcelable { @Parcelize data object Root : NavTarget @@ -83,5 +82,4 @@ class PollHistoryFlowNode @AssistedInject constructor( override fun View(modifier: Modifier) { BackstackView() } - } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryNode.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryNode.kt index 7e6ba37743..0d07f5597b 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryNode.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryNode.kt @@ -37,7 +37,6 @@ class PollHistoryNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins, ) { - interface Callback : Plugin { fun onEditPoll(pollStartEventId: EventId) } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt index 784f64c416..da37891d91 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt @@ -46,7 +46,6 @@ class PollHistoryPresenter @Inject constructor( private val endPollAction: EndPollAction, private val pollHistoryItemFactory: PollHistoryItemsFactory, ) : Presenter { - @Composable override fun present(): PollHistoryState { // TODO use room.rememberPollHistory() when working properly? @@ -87,7 +86,6 @@ class PollHistoryPresenter @Inject constructor( } } - return PollHistoryState( isLoading = isLoading, hasMoreToLoad = paginationState.hasMoreToLoadBackwards, diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryState.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryState.kt index 4d0f351bfd..8f6da18ff2 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryState.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryState.kt @@ -28,7 +28,6 @@ data class PollHistoryState( val pollHistoryItems: PollHistoryItems, val eventSink: (PollHistoryEvents) -> Unit, ) { - fun pollHistoryForFilter(filter: PollHistoryFilter): ImmutableList { return when (filter) { PollHistoryFilter.ONGOING -> pollHistoryItems.ongoing diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt index 19ca239725..4ea8717688 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt @@ -68,7 +68,6 @@ fun PollHistoryView( goBack: () -> Unit, modifier: Modifier = Modifier, ) { - fun onLoadMore() { state.eventSink(PollHistoryEvents.LoadMore) } @@ -133,7 +132,6 @@ fun PollHistoryView( onLoadMore = ::onLoadMore, modifier = Modifier.fillMaxSize(), ) - } } } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItemsFactory.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItemsFactory.kt index a85e4cada2..2c33328ea9 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItemsFactory.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItemsFactory.kt @@ -30,7 +30,6 @@ class PollHistoryItemsFactory @Inject constructor( private val daySeparatorFormatter: DaySeparatorFormatter, private val dispatchers: CoroutineDispatchers, ) { - suspend fun create(timelineItems: List): PollHistoryItems = withContext(dispatchers.computation) { val past = ArrayList() val ongoing = ArrayList() @@ -63,4 +62,3 @@ class PollHistoryItemsFactory @Inject constructor( } } } - diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/model/DefaultPollContentStateFactory.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/model/DefaultPollContentStateFactory.kt index cab06f79a1..3cbe132c85 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/model/DefaultPollContentStateFactory.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/model/DefaultPollContentStateFactory.kt @@ -32,7 +32,6 @@ import javax.inject.Inject class DefaultPollContentStateFactory @Inject constructor( private val matrixClient: MatrixClient, ) : PollContentStateFactory { - override suspend fun create( event: EventTimelineItem, content: PollContent diff --git a/features/poll/impl/src/main/res/values-es/translations.xml b/features/poll/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..2789740859 --- /dev/null +++ b/features/poll/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,19 @@ + + + "Añadir opción" + "Mostrar los resultados solo después de que finalice la encuesta" + "Ocultar votos" + "Opción %1$d" + "Los cambios no se han guardado. ¿Estás seguro de que quieres volver?" + "Pregunta o tema" + "¿De qué trata la encuesta?" + "Crear una Encuesta" + "¿Seguro que quieres eliminar esta encuesta?" + "Eliminar encuesta" + "Editar encuesta" + "No se pudo encontrar ninguna encuesta en curso." + "No se pudo encontrar ninguna encuesta anterior." + "En curso" + "Anteriores" + "Encuestas" + diff --git a/features/poll/impl/src/main/res/values-in/translations.xml b/features/poll/impl/src/main/res/values-in/translations.xml index b8982d9c13..f55dccd75d 100644 --- a/features/poll/impl/src/main/res/values-in/translations.xml +++ b/features/poll/impl/src/main/res/values-in/translations.xml @@ -11,4 +11,9 @@ "Apakah Anda yakin ingin menghapus pemungutan suara ini?" "Hapus pemungutan suara" "Sunting pemungutan suara" + "Tidak dapat menemukan pemungutan suara yang sedang berlangsung." + "Tidak dapat menemukan pemungutan suara sebelumnya." + "Sedang berlangsung" + "Masa lalu" + "Pemungutan suara" diff --git a/features/poll/impl/src/main/res/values-it/translations.xml b/features/poll/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..bf7190db23 --- /dev/null +++ b/features/poll/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,19 @@ + + + "Aggiungi opzione" + "Mostra i risultati solo al termine del sondaggio" + "Nascondi voti" + "Opzione %1$d" + "Le modifiche non sono state salvate. Vuoi davvero tornare indietro?" + "Domanda o argomento" + "Di cosa parla il sondaggio?" + "Crea sondaggio" + "Vuoi davvero eliminare questo sondaggio?" + "Elimina sondaggio" + "Modifica sondaggio" + "Impossibile trovare sondaggi in corso." + "Impossibile trovare sondaggi passati." + "In corso" + "Passato" + "Sondaggi" + diff --git a/features/poll/impl/src/main/res/values-ru/translations.xml b/features/poll/impl/src/main/res/values-ru/translations.xml index 65318d3830..2ed319bbeb 100644 --- a/features/poll/impl/src/main/res/values-ru/translations.xml +++ b/features/poll/impl/src/main/res/values-ru/translations.xml @@ -1,6 +1,6 @@ - "Добавить опцию" + "Добавить вариант" "Показывать результаты только после окончания опроса" "Анонимный опрос" "Настройка %1$d" @@ -14,6 +14,6 @@ "Не найдено текущих опросов." "Не найдено предыдущих опросов." "Текущие" - "Прошедшие" + "Прошлые" "Опросы" diff --git a/features/poll/impl/src/main/res/values-zh-rTW/translations.xml b/features/poll/impl/src/main/res/values-zh-rTW/translations.xml index 03bd96aa28..bb9f02353c 100644 --- a/features/poll/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/poll/impl/src/main/res/values-zh-rTW/translations.xml @@ -7,4 +7,7 @@ "問題或主題" "投什麼?" "建立投票" + "您確定要刪除投票嗎?" + "刪除投票" + "編輯投票" diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt index 5ee32efeb2..d5be6c2bb6 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt @@ -42,7 +42,6 @@ import org.junit.Rule import org.junit.Test class CreatePollPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateSaverTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateSaverTest.kt index 62b3918372..7c60c005db 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateSaverTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateSaverTest.kt @@ -25,6 +25,7 @@ class PollFormStateSaverTest { companion object { val CanSaveScope = SaverScope { true } } + @Test fun `test save and restore`() { val state = PollFormState( diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateTest.kt index cf97967f23..4438a6f734 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateTest.kt @@ -23,7 +23,6 @@ import kotlinx.collections.immutable.toPersistentList import org.junit.Test class PollFormStateTest { - @Test fun `with new answer`() { val state = PollFormState.Empty @@ -134,7 +133,6 @@ class PollFormStateTest { } } - private fun aValidPollFormState(): PollFormState { return PollFormState.Empty.copy( question = "question", diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt index 39494e1f34..d683652b3a 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt @@ -48,7 +48,6 @@ import org.junit.Rule import org.junit.Test class PollHistoryPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/pollcontent/PollContentStateFactoryTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/pollcontent/PollContentStateFactoryTest.kt index fb2f42abf3..d39064d3a0 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/pollcontent/PollContentStateFactoryTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/pollcontent/PollContentStateFactoryTest.kt @@ -48,7 +48,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class PollContentStateFactoryTest { - private val factory = DefaultPollContentStateFactory(FakeMatrixClient()) private val eventTimelineItem = anEventTimelineItem() @@ -63,7 +62,8 @@ class PollContentStateFactoryTest { fun `Disclosed poll - not ended, some votes, including one from current user`() = runTest { val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() val state = factory.create( - eventTimelineItem, aPollContent(votes = votes) + eventTimelineItem, + aPollContent(votes = votes) ) val expectedState = aPollContentState( answerItems = listOf( @@ -92,7 +92,8 @@ class PollContentStateFactoryTest { fun `Disclosed poll - ended, some votes, including one from current user (winner)`() = runTest { val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() val state = factory.create( - eventTimelineItem, aPollContent(votes = votes, endTime = 1UL) + eventTimelineItem, + aPollContent(votes = votes, endTime = 1UL) ) val expectedState = aPollContentState( answerItems = listOf( @@ -110,7 +111,8 @@ class PollContentStateFactoryTest { fun `Disclosed poll - ended, some votes, including one from current user (not winner) and two winning votes`() = runTest { val votes = OTHER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() val state = factory.create( - eventTimelineItem, aPollContent(votes = votes, endTime = 1UL) + eventTimelineItem, + aPollContent(votes = votes, endTime = 1UL) ) val expectedState = aPollContentState( answerItems = listOf( @@ -139,7 +141,8 @@ class PollContentStateFactoryTest { fun `Undisclosed poll - not ended, some votes, including one from current user`() = runTest { val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() val state = factory.create( - eventTimelineItem, aPollContent(PollKind.Undisclosed, votes = votes) + eventTimelineItem, + aPollContent(PollKind.Undisclosed, votes = votes) ) val expectedState = aPollContentState( pollKind = PollKind.Undisclosed, @@ -171,7 +174,8 @@ class PollContentStateFactoryTest { fun `Undisclosed poll - ended, some votes, including one from current user (winner)`() = runTest { val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() val state = factory.create( - eventTimelineItem, aPollContent(PollKind.Undisclosed, votes = votes, endTime = 1UL) + eventTimelineItem, + aPollContent(PollKind.Undisclosed, votes = votes, endTime = 1UL) ) val expectedState = aPollContentState( pollKind = PollKind.Undisclosed, @@ -190,7 +194,8 @@ class PollContentStateFactoryTest { fun `Undisclosed poll - ended, some votes, including one from current user (not winner) and two winning votes`() = runTest { val votes = OTHER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() val state = factory.create( - eventTimelineItem, aPollContent(PollKind.Undisclosed, votes = votes, endTime = 1UL) + eventTimelineItem, + aPollContent(PollKind.Undisclosed, votes = votes, endTime = 1UL) ) val expectedState = aPollContentState( pollKind = PollKind.Undisclosed, @@ -275,15 +280,20 @@ class PollContentStateFactoryTest { private val MY_USER_WINNING_VOTES = persistentMapOf( A_POLL_ANSWER_1 to persistentListOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4), - A_POLL_ANSWER_2 to persistentListOf(A_USER_ID /* my vote */, A_USER_ID_5, A_USER_ID_6, A_USER_ID_7, A_USER_ID_8, A_USER_ID_9), // winner + // First item (A_USER_ID) is for my vote + // winner + A_POLL_ANSWER_2 to persistentListOf(A_USER_ID, A_USER_ID_5, A_USER_ID_6, A_USER_ID_7, A_USER_ID_8, A_USER_ID_9), A_POLL_ANSWER_3 to persistentListOf(), A_POLL_ANSWER_4 to persistentListOf(A_USER_ID_10), ) private val OTHER_WINNING_VOTES = persistentMapOf( - A_POLL_ANSWER_1 to persistentListOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4, A_USER_ID_5), // winner - A_POLL_ANSWER_2 to persistentListOf(A_USER_ID /* my vote */, A_USER_ID_6), + // A winner + A_POLL_ANSWER_1 to persistentListOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4, A_USER_ID_5), + // First item (A_USER_ID) is for my vote + A_POLL_ANSWER_2 to persistentListOf(A_USER_ID, A_USER_ID_6), A_POLL_ANSWER_3 to persistentListOf(), - A_POLL_ANSWER_4 to persistentListOf(A_USER_ID_7, A_USER_ID_8, A_USER_ID_9, A_USER_ID_10), // winner + // Other winner + A_POLL_ANSWER_4 to persistentListOf(A_USER_ID_7, A_USER_ID_8, A_USER_ID_9, A_USER_ID_10), ) } } diff --git a/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeEndPollAction.kt b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeEndPollAction.kt index 285db277e4..d56faf0ad7 100644 --- a/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeEndPollAction.kt +++ b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeEndPollAction.kt @@ -20,7 +20,6 @@ import io.element.android.features.poll.api.actions.EndPollAction import io.element.android.libraries.matrix.api.core.EventId class FakeEndPollAction : EndPollAction { - private var executionCount = 0 fun verifyExecutionCount(count: Int) { diff --git a/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeSendPollResponseAction.kt b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeSendPollResponseAction.kt index f8fa3316d9..bae43448e0 100644 --- a/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeSendPollResponseAction.kt +++ b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeSendPollResponseAction.kt @@ -20,7 +20,6 @@ import io.element.android.features.poll.api.actions.SendPollResponseAction import io.element.android.libraries.matrix.api.core.EventId class FakeSendPollResponseAction : SendPollResponseAction { - private var executionCount = 0 fun verifyExecutionCount(count: Int) { diff --git a/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/pollcontent/FakePollContentStateFactory.kt b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/pollcontent/FakePollContentStateFactory.kt index b8377afe46..6ec6b8b2cd 100644 --- a/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/pollcontent/FakePollContentStateFactory.kt +++ b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/pollcontent/FakePollContentStateFactory.kt @@ -24,7 +24,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import kotlinx.collections.immutable.toImmutableList class FakePollContentStateFactory : PollContentStateFactory { - override suspend fun create(event: EventTimelineItem, content: PollContent): PollContentState { return PollContentState( eventId = event.eventId, diff --git a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt index 9d087e254e..0577412604 100644 --- a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt +++ b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt @@ -26,10 +26,10 @@ import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.parcelize.Parcelize interface PreferencesEntryPoint : FeatureEntryPoint { - sealed interface InitialTarget : Parcelable { @Parcelize data object Root : InitialTarget + @Parcelize data object NotificationSettings : InitialTarget } @@ -38,7 +38,6 @@ interface PreferencesEntryPoint : FeatureEntryPoint { fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder interface NodeBuilder { - fun params(params: Params): NodeBuilder fun callback(callback: Callback): NodeBuilder fun build(): Node 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 index 2ffe480518..5d78d82477 100644 --- 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 @@ -28,7 +28,6 @@ import javax.inject.Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class DefaultCacheService @Inject constructor() : CacheService { - private val _clearedCacheEventFlow = MutableSharedFlow(0) override val clearedCacheEventFlow: Flow = _clearedCacheEventFlow diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt index cab79b61e4..ab665acc17 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt @@ -62,7 +62,6 @@ class PreferencesFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins ) { - sealed interface NavTarget : Parcelable { @Parcelize data object Root : NavTarget diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutNode.kt index 423ae6c793..5936b26b1a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutNode.kt @@ -26,9 +26,9 @@ import com.bumble.appyx.core.plugin.Plugin import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.di.SessionScope -import io.element.android.compound.theme.ElementTheme @ContributesNode(SessionScope::class) class AboutNode @AssistedInject constructor( @@ -36,7 +36,6 @@ class AboutNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: AboutPresenter, ) : Node(buildContext, plugins = plugins) { - private fun onElementLegalClicked( activity: Activity, darkTheme: Boolean, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutPresenter.kt index 556c62ec73..76c3054f61 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutPresenter.kt @@ -21,10 +21,8 @@ import io.element.android.libraries.architecture.Presenter import javax.inject.Inject class AboutPresenter @Inject constructor() : Presenter { - @Composable override fun present(): AboutState { - return AboutState( elementLegals = getAllLegals(), ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutView.kt index a16eb72281..c581811b6b 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutView.kt @@ -20,10 +20,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter -import io.element.android.libraries.designsystem.components.preferences.PreferenceText import io.element.android.libraries.designsystem.components.preferences.PreferencePage -import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.components.preferences.PreferenceText import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.ui.strings.CommonStrings @Composable diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsNode.kt index f7f0fd2eb8..45d2a07a71 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsNode.kt @@ -32,7 +32,6 @@ class AdvancedSettingsNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: AdvancedSettingsPresenter, ) : Node(buildContext, plugins = plugins) { - @Composable override fun View(modifier: Modifier) { val state = presenter.present() diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt index 46a4bd0bbd..ce5de0b8e8 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt @@ -33,7 +33,6 @@ import javax.inject.Inject class AdvancedSettingsPresenter @Inject constructor( private val preferencesStore: PreferencesStore, ) : Presenter { - @Composable override fun present(): AdvancedSettingsState { val localCoroutineScope = rememberCoroutineScope() diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt index 011500a739..d28b9b2380 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt @@ -20,6 +20,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.compound.theme.Theme +import io.element.android.compound.theme.themes import io.element.android.features.preferences.impl.R import io.element.android.libraries.designsystem.components.dialogs.ListOption import io.element.android.libraries.designsystem.components.dialogs.SingleSelectionDialog @@ -29,8 +31,6 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.Theme -import io.element.android.compound.theme.themes import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt index adc917b7e6..1ef0ec10e6 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt @@ -32,7 +32,6 @@ class AnalyticsSettingsNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: AnalyticsSettingsPresenter, ) : Node(buildContext, plugins = plugins) { - @Composable override fun View(modifier: Modifier) { val state = presenter.present() diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenter.kt index 1ef344403d..b412c85732 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenter.kt @@ -24,7 +24,6 @@ import javax.inject.Inject class AnalyticsSettingsPresenter @Inject constructor( private val analyticsPresenter: AnalyticsPreferencesPresenter, ) : Presenter { - @Composable override fun present(): AnalyticsSettingsState { val analyticsState = analyticsPresenter.present() diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt index 751d513385..c41249b1e2 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt @@ -22,8 +22,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesView import io.element.android.libraries.designsystem.components.preferences.PreferencePage -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.ui.strings.CommonStrings @Composable 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 376f14f7bf..58f7ac7c02 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 @@ -21,5 +21,5 @@ import io.element.android.libraries.featureflag.ui.model.FeatureUiModel sealed interface DeveloperSettingsEvents { data class UpdateEnabledFeature(val feature: FeatureUiModel, val isEnabled: Boolean) : DeveloperSettingsEvents data class SetCustomElementCallBaseUrl(val baseUrl: String?) : DeveloperSettingsEvents - data object ClearCache: DeveloperSettingsEvents + data object ClearCache : DeveloperSettingsEvents } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt index 96339e7bb4..8cec8fa85f 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt @@ -37,7 +37,6 @@ class DeveloperSettingsNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: DeveloperSettingsPresenter, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun openConfigureTracing() } 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 df0ea43ce9..c70a95c4fe 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 @@ -32,7 +32,7 @@ import io.element.android.features.preferences.api.store.PreferencesStore import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.bool.orFalse @@ -53,7 +53,6 @@ class DeveloperSettingsPresenter @Inject constructor( private val rageshakePresenter: RageshakePreferencesPresenter, private val preferencesStore: PreferencesStore, ) : Presenter { - @Composable override fun present(): DeveloperSettingsState { val rageshakeState = rageshakePresenter.present() @@ -65,10 +64,10 @@ class DeveloperSettingsPresenter @Inject constructor( mutableStateMapOf() } val cacheSize = remember { - mutableStateOf>(Async.Uninitialized) + mutableStateOf>(AsyncData.Uninitialized) } val clearCacheAction = remember { - mutableStateOf>(Async.Uninitialized) + mutableStateOf>(AsyncData.Uninitialized) } val customElementCallBaseUrl by preferencesStore .getCustomElementCallBaseUrlFlow() @@ -154,13 +153,13 @@ class DeveloperSettingsPresenter @Inject constructor( } } - private fun CoroutineScope.computeCacheSize(cacheSize: MutableState>) = launch { + private fun CoroutineScope.computeCacheSize(cacheSize: MutableState>) = launch { suspend { computeCacheSizeUseCase() }.runCatchingUpdatingState(cacheSize) } - private fun CoroutineScope.clearCache(clearCacheAction: MutableState>) = launch { + private fun CoroutineScope.clearCache(clearCacheAction: MutableState>) = launch { suspend { clearCacheUseCase() }.runCatchingUpdatingState(clearCacheAction) @@ -175,6 +174,3 @@ private fun customElementCallUrlValidator(url: String?): Boolean { if (parsedUrl.host.isNullOrBlank()) error("Missing host") }.isSuccess } - - - 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 d49e94b309..9a12823686 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 @@ -17,15 +17,15 @@ package io.element.android.features.preferences.impl.developer import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.featureflag.ui.model.FeatureUiModel import kotlinx.collections.immutable.ImmutableList data class DeveloperSettingsState( val features: ImmutableList, - val cacheSize: Async, + val cacheSize: AsyncData, val rageshakeState: RageshakePreferencesState, - val clearCacheAction: Async, + val clearCacheAction: AsyncData, val customElementCallBaseUrlState: CustomElementCallBaseUrlState, 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 719b736c09..e4643182f4 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 @@ -18,14 +18,14 @@ package io.element.android.features.preferences.impl.developer import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.rageshake.api.preferences.aRageshakePreferencesState -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData 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()), + aDeveloperSettingsState().copy(clearCacheAction = AsyncData.Loading()), aDeveloperSettingsState().copy( customElementCallBaseUrlState = CustomElementCallBaseUrlState( baseUrl = "https://call.element.ahoy", @@ -39,8 +39,8 @@ open class DeveloperSettingsStateProvider : PreviewParameterProvider, private val presenter: ConfigureTracingPresenter, ) : Node(buildContext, plugins = plugins) { - @Composable override fun View(modifier: Modifier) { val state = presenter.present() diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingPresenter.kt index b0d2243c70..19a9e69e17 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingPresenter.kt @@ -27,7 +27,6 @@ class ConfigureTracingPresenter @Inject constructor( private val tracingConfigurationStore: TracingConfigurationStore, private val targetLogLevelMapBuilder: TargetLogLevelMapBuilder, ) : Presenter { - @Composable override fun present(): ConfigureTracingState { val modifiedMap = remember { mutableStateOf(targetLogLevelMapBuilder.getCurrentMap()) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt index 0e3861c5ec..122d13a817 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt @@ -33,7 +33,6 @@ class NotificationSettingsNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: NotificationSettingsPresenter, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun editDefaultNotificationMode(isOneToOne: Boolean) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt index 6252be1e5c..7083b9f88b 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt @@ -23,7 +23,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.matrix.api.MatrixClient @@ -52,7 +52,7 @@ class NotificationSettingsPresenter @Inject constructor( val systemNotificationsEnabled: MutableState = remember { mutableStateOf(systemNotificationsEnabledProvider.notificationsEnabled()) } - val changeNotificationSettingAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + val changeNotificationSettingAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val localCoroutineScope = rememberCoroutineScope() val appNotificationsEnabled = userPushStore @@ -87,7 +87,7 @@ class NotificationSettingsPresenter @Inject constructor( NotificationSettingsEvents.RefreshSystemNotificationsEnabled -> { systemNotificationsEnabled.value = systemNotificationsEnabledProvider.notificationsEnabled() } - NotificationSettingsEvents.ClearNotificationChangeError -> changeNotificationSettingAction.value = Async.Uninitialized + NotificationSettingsEvents.ClearNotificationChangeError -> changeNotificationSettingAction.value = AsyncAction.Uninitialized } } @@ -119,7 +119,7 @@ class NotificationSettingsPresenter @Inject constructor( val oneToOneDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = true).getOrThrow() val encryptedOneToOneDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = true).getOrThrow() - if(groupDefaultMode != encryptedGroupDefaultMode || oneToOneDefaultMode != encryptedOneToOneDefaultMode) { + if (groupDefaultMode != encryptedGroupDefaultMode || oneToOneDefaultMode != encryptedOneToOneDefaultMode) { target.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false) return@launch } @@ -168,19 +168,19 @@ class NotificationSettingsPresenter @Inject constructor( ) } - private fun CoroutineScope.setAtRoomNotificationsEnabled(enabled: Boolean, action: MutableState>) = launch { + private fun CoroutineScope.setAtRoomNotificationsEnabled(enabled: Boolean, action: MutableState>) = launch { suspend { notificationSettingsService.setRoomMentionEnabled(enabled).getOrThrow() }.runCatchingUpdatingState(action) } - private fun CoroutineScope.setCallNotificationsEnabled(enabled: Boolean, action: MutableState>) = launch { + private fun CoroutineScope.setCallNotificationsEnabled(enabled: Boolean, action: MutableState>) = launch { suspend { notificationSettingsService.setCallEnabled(enabled).getOrThrow() }.runCatchingUpdatingState(action) } - private fun CoroutineScope.setInviteForMeNotificationsEnabled(enabled: Boolean, action: MutableState>) = launch { + private fun CoroutineScope.setInviteForMeNotificationsEnabled(enabled: Boolean, action: MutableState>) = launch { suspend { notificationSettingsService.setInviteForMeEnabled(enabled).getOrThrow() }.runCatchingUpdatingState(action) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt index 581f0e8475..5c23e74cd5 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt @@ -17,18 +17,18 @@ package io.element.android.features.preferences.impl.notifications import androidx.compose.runtime.Immutable -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.room.RoomNotificationMode @Immutable data class NotificationSettingsState( val matrixSettings: MatrixSettings, val appSettings: AppSettings, - val changeNotificationSettingAction: Async, + val changeNotificationSettingAction: AsyncAction, val eventSink: (NotificationSettingsEvents) -> Unit, ) { sealed interface MatrixSettings { - data object Uninitialized : MatrixSettings + data object Uninitialized : MatrixSettings data class Valid( val atRoomNotificationsEnabled: Boolean, val callNotificationsEnabled: Boolean, @@ -39,7 +39,7 @@ data class NotificationSettingsState( data class Invalid( val fixFailed: Boolean - ) : MatrixSettings + ) : MatrixSettings } data class AppSettings( @@ -47,7 +47,3 @@ data class NotificationSettingsState( val appNotificationsEnabled: Boolean, ) } - - - - diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt index f121a2fe60..2dc0c02145 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt @@ -17,20 +17,20 @@ package io.element.android.features.preferences.impl.notifications import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.room.RoomNotificationMode open class NotificationSettingsStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aNotificationSettingsState(), - aNotificationSettingsState(changeNotificationSettingAction = Async.Loading(Unit)), - aNotificationSettingsState(changeNotificationSettingAction = Async.Failure(Throwable("error"))), + aNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Loading), + aNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Failure(Throwable("error"))), ) } fun aNotificationSettingsState( - changeNotificationSettingAction: Async = Async.Uninitialized, + changeNotificationSettingAction: AsyncAction = AsyncAction.Uninitialized, ) = NotificationSettingsState( matrixSettings = NotificationSettingsState.MatrixSettings.Valid( atRoomNotificationsEnabled = true, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt index f1066b2e9d..2226049efe 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt @@ -27,7 +27,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.preferences.impl.R import io.element.android.libraries.androidutils.system.startNotificationSettingsIntent import io.element.android.libraries.designsystem.atomic.molecules.DialogLikeBannerMolecule -import io.element.android.libraries.designsystem.components.async.AsyncView +import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferencePage @@ -60,7 +60,6 @@ fun NotificationSettingsView( onBackPressed = onBackPressed, title = stringResource(id = R.string.screen_notification_settings_title) ) { - when (state.matrixSettings) { is NotificationSettingsState.MatrixSettings.Invalid -> InvalidNotificationSettingsView( showError = state.matrixSettings.fixFailed, @@ -80,7 +79,7 @@ fun NotificationSettingsView( onInviteForMeNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(it)) }, ) } - AsyncView( + AsyncActionView( async = state.changeNotificationSettingAction, errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, onErrorDismiss = { state.eventSink(NotificationSettingsEvents.ClearNotificationChangeError) }, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/SystemNotificationsEnabledProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/SystemNotificationsEnabledProvider.kt index f33b1cb773..9aaec23e94 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/SystemNotificationsEnabledProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/SystemNotificationsEnabledProvider.kt @@ -27,13 +27,13 @@ import javax.inject.Inject interface SystemNotificationsEnabledProvider { fun notificationsEnabled(): Boolean } + @SingleIn(AppScope::class) @ContributesBinding(AppScope::class, boundType = SystemNotificationsEnabledProvider::class) class DefaultSystemNotificationsEnabledProvider @Inject constructor( @ApplicationContext private val context: Context, -): SystemNotificationsEnabledProvider { +) : SystemNotificationsEnabledProvider { override fun notificationsEnabled(): Boolean { return NotificationManagerCompat.from(context).areNotificationsEnabled() } } - diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/DefaultNotificationSettingOption.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/DefaultNotificationSettingOption.kt index 1d07589a24..b5a79947af 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/DefaultNotificationSettingOption.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/DefaultNotificationSettingOption.kt @@ -20,12 +20,12 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import io.element.android.features.preferences.impl.R -import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.room.RoomNotificationMode -import io.element.android.libraries.designsystem.components.list.ListItemContent -import io.element.android.libraries.designsystem.theme.components.ListItem @Composable fun DefaultNotificationSettingOption( diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingNode.kt index 535203e35e..de365878ad 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingNode.kt @@ -36,7 +36,6 @@ class EditDefaultNotificationSettingNode @AssistedInject constructor( @Assisted plugins: List, presenterFactory: EditDefaultNotificationSettingPresenter.Factory ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun openRoomNotificationSettings(roomId: RoomId) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt index 7617e68dd6..04accef4ea 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt @@ -27,7 +27,7 @@ import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.matrix.api.MatrixClient @@ -55,6 +55,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( interface Factory { fun create(oneToOne: Boolean): EditDefaultNotificationSettingPresenter } + @Composable override fun present(): EditDefaultNotificationSettingState { var displayMentionsOnlyDisclaimer by remember { mutableStateOf(false) } @@ -63,7 +64,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( mutableStateOf(null) } - val changeNotificationSettingAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + val changeNotificationSettingAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val roomsWithUserDefinedMode: MutableState> = remember { mutableStateOf(listOf()) @@ -82,7 +83,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( is EditDefaultNotificationSettingStateEvents.SetNotificationMode -> { localCoroutineScope.setDefaultNotificationMode(event.mode, changeNotificationSettingAction) } - EditDefaultNotificationSettingStateEvents.ClearError -> changeNotificationSettingAction.value = Async.Uninitialized + EditDefaultNotificationSettingStateEvents.ClearError -> changeNotificationSettingAction.value = AsyncAction.Uninitialized } } @@ -132,17 +133,16 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( roomWithUserDefinedRules.contains(it.identifier()) && isOneToOne == room.isOneToOne } // locale sensitive sorting - .sortedWith(compareBy(Collator.getInstance()){ it.details.name }) + .sortedWith(compareBy(Collator.getInstance()) { it.details.name }) roomsWithUserDefinedMode.value = sortedSummaries } - private fun CoroutineScope.setDefaultNotificationMode(mode: RoomNotificationMode, action: MutableState>) = launch { + private fun CoroutineScope.setDefaultNotificationMode(mode: RoomNotificationMode, action: MutableState>) = launch { suspend { // On modern clients, we don't have different settings for encrypted and non-encrypted rooms (Legacy clients did). notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, mode = mode, isOneToOne = isOneToOne).getOrThrow() notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, mode = mode, isOneToOne = isOneToOne).getOrThrow() }.runCatchingUpdatingState(action) } - } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt index 7086835478..68b29232b0 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt @@ -16,7 +16,7 @@ package io.element.android.features.preferences.impl.notifications.edit -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.roomlist.RoomSummary import kotlinx.collections.immutable.ImmutableList @@ -25,7 +25,7 @@ data class EditDefaultNotificationSettingState( val isOneToOne: Boolean, val mode: RoomNotificationMode?, val roomsWithUserDefinedMode: ImmutableList, - val changeNotificationSettingAction: Async, + val changeNotificationSettingAction: AsyncAction, val displayMentionsOnlyDisclaimer: Boolean, val eventSink: (EditDefaultNotificationSettingStateEvents) -> Unit, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvents.kt index f5774f1d78..f5b03c6433 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvents.kt @@ -19,6 +19,6 @@ package io.element.android.features.preferences.impl.notifications.edit import io.element.android.libraries.matrix.api.room.RoomNotificationMode sealed interface EditDefaultNotificationSettingStateEvents { - data class SetNotificationMode(val mode: RoomNotificationMode): EditDefaultNotificationSettingStateEvents - data object ClearError: EditDefaultNotificationSettingStateEvents + data class SetNotificationMode(val mode: RoomNotificationMode) : EditDefaultNotificationSettingStateEvents + data object ClearError : EditDefaultNotificationSettingStateEvents } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt index dc11b85e6d..0c4ef95c6c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt @@ -17,27 +17,27 @@ package io.element.android.features.preferences.impl.notifications.edit import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails import kotlinx.collections.immutable.persistentListOf -open class EditDefaultNotificationSettingStateProvider: PreviewParameterProvider { +open class EditDefaultNotificationSettingStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( anEditDefaultNotificationSettingsState(), anEditDefaultNotificationSettingsState(isOneToOne = true), - anEditDefaultNotificationSettingsState(changeNotificationSettingAction = Async.Loading(Unit)), - anEditDefaultNotificationSettingsState(changeNotificationSettingAction = Async.Failure(Throwable("error"))), + anEditDefaultNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Loading), + anEditDefaultNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Failure(Throwable("error"))), anEditDefaultNotificationSettingsState(displayMentionsOnlyDisclaimer = true), ) } private fun anEditDefaultNotificationSettingsState( isOneToOne: Boolean = false, - changeNotificationSettingAction: Async = Async.Uninitialized, + changeNotificationSettingAction: AsyncAction = AsyncAction.Uninitialized, displayMentionsOnlyDisclaimer: Boolean = false, ) = EditDefaultNotificationSettingState( isOneToOne = isOneToOne, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt index 7b619a7322..e67eb280af 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt @@ -23,7 +23,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.features.preferences.impl.R -import io.element.android.libraries.designsystem.components.async.AsyncView +import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize @@ -59,7 +59,6 @@ fun EditDefaultNotificationSettingView( onBackPressed = onBackPressed, title = stringResource(id = title) ) { - // Only ALL_MESSAGES and MENTIONS_AND_KEYWORDS_ONLY are valid global defaults. val validModes = listOf(RoomNotificationMode.ALL_MESSAGES, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) @@ -69,7 +68,6 @@ fun EditDefaultNotificationSettingView( R.string.screen_notification_settings_edit_screen_group_section_header } PreferenceCategory(title = stringResource(id = categoryTitle)) { - if (state.mode != null) { Column(modifier = Modifier.selectableGroup()) { validModes.forEach { item -> @@ -117,7 +115,7 @@ fun EditDefaultNotificationSettingView( } } } - AsyncView( + AsyncActionView( async = state.changeNotificationSettingAction, errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, onErrorDismiss = { state.eventSink(EditDefaultNotificationSettingStateEvents.ClearError) }, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt index ecb2f7f070..cbdf2418ce 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt @@ -27,18 +27,21 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.compound.theme.ElementTheme +import io.element.android.features.logout.api.direct.DirectLogoutEvents +import io.element.android.features.logout.api.direct.DirectLogoutView import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.user.MatrixUser -import io.element.android.compound.theme.ElementTheme +import timber.log.Timber @ContributesNode(SessionScope::class) class PreferencesRootNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, private val presenter: PreferencesRootPresenter, + private val directLogoutView: DirectLogoutView, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun onOpenBugReport() fun onVerifyClicked() @@ -95,6 +98,13 @@ class PreferencesRootNode @AssistedInject constructor( } } + private fun onSuccessLogout(activity: Activity, url: String?) { + Timber.d("Success (direct) logout with result url: $url") + url?.let { + activity.openUrlInChromeCustomTab(null, false, it) + } + } + private fun onOpenNotificationSettings() { plugins().forEach { it.onOpenNotificationSettings() } } @@ -131,7 +141,20 @@ class PreferencesRootNode @AssistedInject constructor( onOpenNotificationSettings = this::onOpenNotificationSettings, onOpenLockScreenSettings = this::onOpenLockScreenSettings, onOpenUserProfile = this::onOpenUserProfile, - onSignOutClicked = this::onSignOutClicked, + onSignOutClicked = { + if (state.directLogoutState.canDoDirectSignOut) { + state.directLogoutState.eventSink(DirectLogoutEvents.Logout(ignoreSdkError = false)) + } else { + onSignOutClicked() + } + }, + ) + + directLogoutView.Render( + state = state.directLogoutState, + onSuccessLogout = { + onSuccessLogout(activity, it) + } ) } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt index 3295e3a59a..4ad2a95df4 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable +import io.element.android.features.logout.api.direct.DirectLogoutPresenter import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher @@ -50,8 +51,8 @@ class PreferencesRootPresenter @Inject constructor( private val snackbarDispatcher: SnackbarDispatcher, private val featureFlagService: FeatureFlagService, private val indicatorService: IndicatorService, + private val directLogoutPresenter: DirectLogoutPresenter, ) : Presenter { - @Composable override fun present(): PreferencesRootState { val matrixUser: MutableState = rememberSaveable { @@ -88,6 +89,8 @@ class PreferencesRootPresenter @Inject constructor( mutableStateOf(null) } + val directLogoutState = directLogoutPresenter.present() + LaunchedEffect(Unit) { initAccountManagementUrl(accountManagementUrl, devicesManagementUrl) } @@ -105,6 +108,7 @@ class PreferencesRootPresenter @Inject constructor( showDeveloperSettings = showDeveloperSettings, showNotificationSettings = showNotificationSettings.value, showLockScreenSettings = showLockScreenSettings.value, + directLogoutState = directLogoutState, snackbarMessage = snackbarMessage, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt index fec09f150f..0da537f26a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt @@ -16,6 +16,7 @@ package io.element.android.features.preferences.impl.root +import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.user.MatrixUser @@ -31,5 +32,6 @@ data class PreferencesRootState( val showDeveloperSettings: Boolean, val showLockScreenSettings: Boolean, val showNotificationSettings: Boolean, + val directLogoutState: DirectLogoutState, val snackbarMessage: SnackbarMessage?, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt index 74d8b0f0c9..b99c197d69 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt @@ -16,6 +16,8 @@ package io.element.android.features.preferences.impl.root +import io.element.android.features.logout.api.direct.DirectLogoutState +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.ui.strings.CommonStrings @@ -32,4 +34,11 @@ fun aPreferencesRootState() = PreferencesRootState( showNotificationSettings = true, showLockScreenSettings = true, snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete), + directLogoutState = aDirectLogoutState(), +) + +fun aDirectLogoutState() = DirectLogoutState( + canDoDirectSignOut = true, + logoutAction = AsyncAction.Uninitialized, + eventSink = {}, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index 230648a96a..f25cb515e9 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -57,7 +57,7 @@ fun PreferencesRootView( onManageAccountClicked: (url: String) -> Unit, onOpenAnalytics: () -> Unit, onOpenRageShake: () -> Unit, - onOpenLockScreenSettings: ()->Unit, + onOpenLockScreenSettings: () -> Unit, onOpenAbout: () -> Unit, onOpenDeveloperSettings: () -> Unit, onOpenAdvancedSettings: () -> Unit, @@ -91,7 +91,7 @@ fun PreferencesRootView( if (state.showSecureBackup) { ListItem( headlineContent = { Text(stringResource(id = CommonStrings.common_chat_backup)) }, - leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_key_filled),), + leadingContent = ListItemContent.Icon(IconSource.Resource(CommonDrawables.ic_key_filled)), trailingContent = ListItemContent.Badge.takeIf { state.showSecureBackupBadge }, onClick = onSecureBackupClicked, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/UserPreferences.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/UserPreferences.kt index 6020b83231..59c8e72cc6 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/UserPreferences.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/UserPreferences.kt @@ -19,8 +19,8 @@ package io.element.android.features.preferences.impl.user import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.ui.components.MatrixUserHeader import io.element.android.libraries.matrix.ui.components.MatrixUserWithNullProvider diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileNode.kt index 738ae4ec6d..4fcbb94cf1 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileNode.kt @@ -35,7 +35,6 @@ class EditUserProfileNode @AssistedInject constructor( @Assisted plugins: List, presenterFactory: EditUserProfilePresenter.Factory, ) : Node(buildContext, plugins = plugins) { - data class Inputs( val matrixUser: MatrixUser ) : NodeInputs diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenter.kt index be0a5f4cca..e62f1077d8 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenter.kt @@ -31,7 +31,7 @@ import androidx.core.net.toUri import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.mimetype.MimeTypes @@ -54,7 +54,6 @@ class EditUserProfilePresenter @AssistedInject constructor( private val mediaPreProcessor: MediaPreProcessor, permissionsPresenterFactory: PermissionsPresenter.Factory, ) : Presenter { - private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA) private var pendingPermissionRequest = false @@ -92,7 +91,7 @@ class EditUserProfilePresenter @AssistedInject constructor( } } - val saveAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + val saveAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val localCoroutineScope = rememberCoroutineScope() fun handleEvents(event: EditUserProfileEvents) { when (event) { @@ -111,7 +110,7 @@ class EditUserProfilePresenter @AssistedInject constructor( } is EditUserProfileEvents.UpdateDisplayName -> userDisplayName = event.name - EditUserProfileEvents.CancelSaveChanges -> saveAction.value = Async.Uninitialized + EditUserProfileEvents.CancelSaveChanges -> saveAction.value = AsyncAction.Uninitialized } } @@ -126,7 +125,7 @@ class EditUserProfilePresenter @AssistedInject constructor( displayName = userDisplayName.orEmpty(), userAvatarUrl = userAvatarUri, avatarActions = avatarActions, - saveButtonEnabled = canSave && saveAction.value !is Async.Loading, + saveButtonEnabled = canSave && saveAction.value !is AsyncAction.Loading, saveAction = saveAction.value, cameraPermissionState = cameraPermissionState, eventSink = { handleEvents(it) }, @@ -140,7 +139,12 @@ class EditUserProfilePresenter @AssistedInject constructor( // Need to call `toUri()?.toString()` to make the test pass (we mockk Uri) avatarUri?.toString()?.trim() != currentUser.avatarUrl?.toUri()?.toString()?.trim() - private fun CoroutineScope.saveChanges(name: String?, avatarUri: Uri?, currentUser: MatrixUser, action: MutableState>) = launch { + private fun CoroutineScope.saveChanges( + name: String?, + avatarUri: Uri?, + currentUser: MatrixUser, + action: MutableState>, + ) = launch { val results = mutableListOf>() suspend { if (!name.isNullOrEmpty() && name.trim() != currentUser.displayName.orEmpty().trim()) { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileState.kt index 9561c8609b..c37dba8c3a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileState.kt @@ -17,7 +17,7 @@ package io.element.android.features.preferences.impl.user.editprofile import android.net.Uri -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.ui.media.AvatarAction import io.element.android.libraries.permissions.api.PermissionsState @@ -29,7 +29,7 @@ data class EditUserProfileState( val userAvatarUrl: Uri?, val avatarActions: ImmutableList, val saveButtonEnabled: Boolean, - val saveAction: Async, + val saveAction: AsyncAction, val cameraPermissionState: PermissionsState, val eventSink: (EditUserProfileEvents) -> Unit ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileStateProvider.kt index c4a8431f4f..9d0d027433 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileStateProvider.kt @@ -17,7 +17,7 @@ package io.element.android.features.preferences.impl.user.editprofile import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.permissions.api.aPermissionsState import kotlinx.collections.immutable.persistentListOf @@ -35,7 +35,7 @@ fun aEditUserProfileState() = EditUserProfileState( displayName = "John Doe", userAvatarUrl = null, avatarActions = persistentListOf(), - saveAction = Async.Uninitialized, + saveAction = AsyncAction.Uninitialized, saveButtonEnabled = true, cameraPermissionState = aPermissionsState(showDialog = false), eventSink = {} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt index 66aaeb6c40..92294f27de 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt @@ -41,9 +41,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.features.preferences.impl.R import io.element.android.libraries.designsystem.components.LabelledOutlinedTextField -import io.element.android.libraries.designsystem.components.async.AsyncView +import io.element.android.libraries.designsystem.components.async.AsyncActionView +import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview @@ -56,7 +58,6 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet import io.element.android.libraries.matrix.ui.components.EditableAvatarView import io.element.android.libraries.permissions.api.PermissionsView -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.coroutines.launch @@ -147,9 +148,13 @@ fun EditUserProfileView( onActionSelected = { state.eventSink(EditUserProfileEvents.HandleAvatarAction(it)) } ) - AsyncView( + AsyncActionView( async = state.saveAction, - progressText = stringResource(R.string.screen_edit_profile_updating_details), + progressDialog = { + AsyncActionViewDefaults.ProgressDialog( + progressText = stringResource(R.string.screen_edit_profile_updating_details), + ) + }, onSuccess = { onProfileEdited() }, errorTitle = { stringResource(R.string.screen_edit_profile_error_title) }, errorMessage = { stringResource(R.string.screen_edit_profile_error) }, @@ -178,4 +183,3 @@ internal fun EditUserProfileViewPreview(@PreviewParameter(EditUserProfileStatePr state = state, ) } - diff --git a/features/preferences/impl/src/main/res/values-de/translations.xml b/features/preferences/impl/src/main/res/values-de/translations.xml index cacc15a633..fd5d203471 100644 --- a/features/preferences/impl/src/main/res/values-de/translations.xml +++ b/features/preferences/impl/src/main/res/values-de/translations.xml @@ -17,16 +17,17 @@ "Audio- und Videoanrufe" "Konfiguration stimmt nicht überein" "Wir haben die Einstellungen für Benachrichtigungen vereinfacht, damit die Optionen leichter zu finden sind. Einige benutzerdefinierte Einstellungen, die du in der Vergangenheit gewählt hast, werden hier nicht angezeigt, sind aber immer noch aktiv. Wenn du fortfährst, können sich einige deiner Einstellungen ändern." - "Direkte Chats" + "Direktnachrichten" "Benutzerdefinierte Einstellung pro Chat" "Beim Aktualisieren der Benachrichtigungseinstellungen ist ein Fehler aufgetreten." "Alle Nachrichten" "Nur Erwähnungen und Schlüsselwörter" - "Bei direkten Chats, benachrichtige mich bei" + "Benachrichtige mich bei Direktnachrichten über " "Bei Gruppenchats benachrichtige mich bei" "Benachrichtigungen auf diesem Gerät aktivieren" "Die Konfiguration wurde nicht korrigiert, bitte versuche es erneut." "Gruppenchats" + "Einladungen" "Dein Homeserver unterstützt diese Option in verschlüsselten Räumen nicht. In einigen Räumen wirst du möglicherweise nicht benachrichtigt." "Erwähnungen" "Alle" diff --git a/features/preferences/impl/src/main/res/values-es/translations.xml b/features/preferences/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..0c67143999 --- /dev/null +++ b/features/preferences/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,43 @@ + + + "URL base personalizada de Element Call" + "Define una URL base personalizada para Element Call." + "URL no válida, asegúrate de incluir el protocolo (http/https) y la dirección correcta." + "Modo desarrollador" + "Habilita para tener acceso a características y funcionalidades para desarrolladores." + "Desactiva el editor de texto enriquecido para escribir Markdown manualmente." + "Habilita la opción para ver el contenido en bruto del mensaje en la cronología." + "Nombre público" + "Tu nombre visible" + "Se encontró un error desconocido y no se pudo cambiar la información." + "No se puede actualizar el perfil" + "Editar perfil" + "Actualizando perfil…" + "Ajustes adicionales" + "Llamadas de audio y vídeo" + "La configuración no coincide" + "Hemos simplificado la Configuración de Notificaciones para hacer las opciones más fáciles de encontrar. Algunas configuraciones personalizadas que has elegido en el pasado no se muestran aquí, pero siguen activas. + +Si continúas, es posible que algunos de tus ajustes cambien." + "Chats directos" + "Configuración personalizada por chat" + "Se ha producido un error al actualizar la configuración de notificaciones." + "Todos los mensajes" + "Únicamente Menciones y Palabras clave" + "En los chats directos, notifícame por" + "En los chats grupales, notifícame por" + "Habilitar las notificaciones en este dispositivo" + "La configuración no se ha corregido, por favor inténtalo de nuevo." + "Chats grupales" + "Invitaciones" + "Tu servidor principal no admite esta opción en salas cifradas, puede que no recibas notificaciones en algunas salas." + "Menciones" + "Todos" + "Menciones" + "Notificarme para" + "Notificarme con @room" + "Para recibir notificaciones, cambia tus %1$s." + "ajustes del sistema" + "Notificaciones del sistema desactivadas" + "Notificaciones" + diff --git a/features/preferences/impl/src/main/res/values-hu/translations.xml b/features/preferences/impl/src/main/res/values-hu/translations.xml index d5286dc323..5abf6a83f8 100644 --- a/features/preferences/impl/src/main/res/values-hu/translations.xml +++ b/features/preferences/impl/src/main/res/values-hu/translations.xml @@ -2,10 +2,10 @@ "Egyéni Element Call alapwebcím" "Egyéni alapwebcím beállítása az Element Callhoz." - "Érvénytelen webcím, győződjön meg arról, hogy szerepel benne a protokoll (http/https), és hogy helyes a cím." + "Érvénytelen webcím, győződj meg arról, hogy szerepel-e benne a protokoll (http/https), és hogy helyes-e a cím." "Fejlesztői mód" - "Engedélyezze, hogy elérje a fejlesztőknek szánt funkciókat." - "A formázott szöveges szerkesztő letiltása, hogy kézzel írhasson Markdownt." + "Engedélyezd, hogy elérd a fejlesztőknek szánt funkciókat." + "A formázott szöveges szerkesztő letiltása, hogy kézzel írhass Markdownt." "Engedélyezze a beállítást az üzenet forrásának megjelenítéséhez az idővonalon." "Megjelenítendő név" "Saját megjelenítendő név" diff --git a/features/preferences/impl/src/main/res/values-it/translations.xml b/features/preferences/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..a77e35738a --- /dev/null +++ b/features/preferences/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,43 @@ + + + "URL base di Element Call personalizzato" + "Imposta un URL di base personalizzato per Element Call." + "URL non valido, assicurati di includere il protocollo (http/https) e l\'indirizzo corretto." + "Modalità sviluppatore" + "Attiva per avere accesso a caratteristiche e funzionalità per sviluppatori." + "Disattiva l\'editor in rich text per digitare Markdown manualmente." + "Attiva l\'opzione per visualizzare il sorgente del messaggio nella linea temporale." + "Nome da mostrare" + "Il tuo nome da mostrare" + "Si è verificato un errore sconosciuto e non è stato possibile modificare le informazioni." + "Impossibile aggiornare il profilo" + "Modifica profilo" + "Aggiornamento del profilo…" + "Impostazioni aggiuntive" + "Chiamate audio e video" + "Mancata corrispondenza della configurazione" + "Abbiamo semplificato le impostazioni di notifica per rendere le opzioni più facili da trovare. Alcune impostazioni personalizzate che hai scelto in passato non sono mostrate qui, ma sono ancora attive. + +Se procedi, alcune delle tue impostazioni potrebbero cambiare." + "Chat dirette" + "Impostazione personalizzata per chat" + "Si è verificato un errore durante l\'aggiornamento delle impostazioni di notifica." + "Tutti i messaggi" + "Solo menzioni e parole chiave" + "Nelle chat dirette, avvisami per" + "Nelle chat di gruppo, avvisami per" + "Attiva le notifiche su questo dispositivo" + "La configurazione non è stata corretta, riprova." + "Chat di gruppo" + "Inviti" + "Il tuo homeserver non supporta questa opzione nelle stanze criptate, quindi potresti non ricevere notifiche in alcune stanze." + "Menzioni" + "Tutto" + "Menzioni" + "Avvisami per" + "Avvisami su @room" + "Per ricevere notifiche, modifica le tue %1$s." + "impostazioni di sistema" + "Notifiche di sistema disattivate" + "Notifiche" + diff --git a/features/preferences/impl/src/main/res/values-ru/translations.xml b/features/preferences/impl/src/main/res/values-ru/translations.xml index bbf5f426ea..979644cec9 100644 --- a/features/preferences/impl/src/main/res/values-ru/translations.xml +++ b/features/preferences/impl/src/main/res/values-ru/translations.xml @@ -6,7 +6,7 @@ "Режим разработчика" "Предоставьте разработчикам доступ к функциям и функциональным возможностям." "Отключить редактор форматированного текста и включить Markdown." - "Включить опцию просмотра источника сообщения на временной шкале." + "Включить опцию просмотра источника сообщения в ленте." "Отображаемое имя" "Ваше отображаемое имя" "Произошла неизвестная ошибка, изменить информацию не удалось." @@ -20,7 +20,7 @@ Если вы продолжите, некоторые настройки могут быть изменены." "Прямые чаты" - "Индивидуальные настройки для каждого чата" + "Персональные настройки для каждого чата" "При обновлении настроек уведомления произошла ошибка." "Все сообщения" "Только упоминания и ключевые слова" diff --git a/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml b/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml index 1282f45e4a..1110887cce 100644 --- a/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml @@ -13,6 +13,7 @@ "僅限提及與關鍵字" "在這個裝置上開啟通知" "群組聊天" + "邀請" "提及" "提及" "系統設定" diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutPresenterTest.kt index 2cfd73b614..be2742c80e 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutPresenterTest.kt @@ -26,7 +26,6 @@ import org.junit.Rule import org.junit.Test class AboutPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt index 22ef0011a6..6d7877de32 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt @@ -20,8 +20,8 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore import io.element.android.compound.theme.Theme +import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.awaitLastSequentialItem import kotlinx.coroutines.test.runTest @@ -29,7 +29,6 @@ import org.junit.Rule import org.junit.Test class AdvancedSettingsPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenterTest.kt index 9570258092..f09e667747 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenterTest.kt @@ -29,7 +29,6 @@ import org.junit.Rule import org.junit.Test class AnalyticsSettingsPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() 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 07705446d9..50bf1ab426 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 @@ -26,7 +26,7 @@ import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUs import io.element.android.features.rageshake.impl.preferences.DefaultRageshakePreferencesPresenter import io.element.android.features.rageshake.test.rageshake.FakeRageShake import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore @@ -37,7 +37,6 @@ import org.junit.Rule import org.junit.Test class DeveloperSettingsPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -49,8 +48,8 @@ class DeveloperSettingsPresenterTest { }.test { val initialState = awaitItem() assertThat(initialState.features).isEmpty() - assertThat(initialState.clearCacheAction).isEqualTo(Async.Uninitialized) - assertThat(initialState.cacheSize).isEqualTo(Async.Uninitialized) + assertThat(initialState.clearCacheAction).isEqualTo(AsyncData.Uninitialized) + assertThat(initialState.cacheSize).isEqualTo(AsyncData.Uninitialized) assertThat(initialState.customElementCallBaseUrlState).isNotNull() assertThat(initialState.customElementCallBaseUrlState.baseUrl).isNull() val loadedState = awaitItem() @@ -105,9 +104,9 @@ class DeveloperSettingsPresenterTest { assertThat(clearCacheUseCase.executeHasBeenCalled).isFalse() initialState.eventSink(DeveloperSettingsEvents.ClearCache) val stateAfterEvent = awaitItem() - assertThat(stateAfterEvent.clearCacheAction).isInstanceOf(Async.Loading::class.java) + assertThat(stateAfterEvent.clearCacheAction).isInstanceOf(AsyncData.Loading::class.java) skipItems(1) - assertThat(awaitItem().clearCacheAction).isInstanceOf(Async.Success::class.java) + assertThat(awaitItem().clearCacheAction).isInstanceOf(AsyncData.Success::class.java) assertThat(clearCacheUseCase.executeHasBeenCalled).isTrue() cancelAndIgnoreRemainingEvents() } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingPresenterTest.kt index 5ca874b6d6..4dd0c01e56 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/tracing/ConfigureTracingPresenterTest.kt @@ -29,9 +29,9 @@ import org.junit.Rule import org.junit.Test class ConfigureTracingPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() + @Test fun `present - initial state`() = runTest { val store = InMemoryTracingConfigurationStore() diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt index 330dea4e94..25b5aa4532 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt @@ -140,5 +140,4 @@ class EditDefaultNotificationSettingsPresenterTests { matrixClient = matrixClient ) } - } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/FakeSystemNotificationsEnabledProvider.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/FakeSystemNotificationsEnabledProvider.kt index 1a7c1b5004..9859ca21e0 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/FakeSystemNotificationsEnabledProvider.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/FakeSystemNotificationsEnabledProvider.kt @@ -16,7 +16,7 @@ package io.element.android.features.preferences.impl.notifications -class FakeSystemNotificationsEnabledProvider: SystemNotificationsEnabledProvider { +class FakeSystemNotificationsEnabledProvider : SystemNotificationsEnabledProvider { override fun notificationsEnabled(): Boolean { return true } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTests.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTests.kt index 96eca14746..e058d4691b 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTests.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTests.kt @@ -232,7 +232,7 @@ class NotificationSettingsPresenterTests { private fun createNotificationSettingsPresenter( notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService() - ) : NotificationSettingsPresenter { + ): NotificationSettingsPresenter { val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService) return NotificationSettingsPresenter( notificationSettingsService = notificationSettingsService, diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt index f914c21100..983a820509 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt @@ -16,10 +16,14 @@ package io.element.android.features.preferences.impl.root +import androidx.compose.runtime.Composable import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.features.logout.api.direct.DirectLogoutPresenter +import io.element.android.features.logout.api.direct.DirectLogoutState +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.featureflag.test.FakeFeatureFlagService @@ -37,27 +41,36 @@ import org.junit.Rule import org.junit.Test class PreferencesRootPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() + private val aDirectLogoutState = DirectLogoutState( + canDoDirectSignOut = true, + logoutAction = AsyncAction.Uninitialized, + eventSink = {}, + ) + @Test fun `present - initial state`() = runTest { val matrixClient = FakeMatrixClient() val sessionVerificationService = FakeSessionVerificationService() val presenter = PreferencesRootPresenter( - matrixClient, - sessionVerificationService, - FakeAnalyticsService(), - BuildType.DEBUG, - FakeVersionFormatter(), - SnackbarDispatcher(), - FakeFeatureFlagService(), - DefaultIndicatorService( + matrixClient = matrixClient, + sessionVerificationService = sessionVerificationService, + analyticsService = FakeAnalyticsService(), + buildType = BuildType.DEBUG, + versionFormatter = FakeVersionFormatter(), + snackbarDispatcher = SnackbarDispatcher(), + featureFlagService = FakeFeatureFlagService(), + indicatorService = DefaultIndicatorService( sessionVerificationService = sessionVerificationService, encryptionService = FakeEncryptionService(), featureFlagService = FakeFeatureFlagService(), ), + directLogoutPresenter = object : DirectLogoutPresenter { + @Composable + override fun present() = aDirectLogoutState + } ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -73,10 +86,18 @@ class PreferencesRootPresenterTest { avatarUrl = AN_AVATAR_URL ) ) - assertThat(loadedState.showDeveloperSettings).isTrue() - assertThat(loadedState.showAnalyticsSettings).isFalse() + assertThat(initialState.version).isEqualTo("A Version") + assertThat(loadedState.showCompleteVerification).isTrue() + assertThat(loadedState.showSecureBackup).isFalse() + assertThat(loadedState.showSecureBackupBadge).isTrue() assertThat(loadedState.accountManagementUrl).isNull() assertThat(loadedState.devicesManagementUrl).isNull() + assertThat(loadedState.showAnalyticsSettings).isFalse() + assertThat(loadedState.showDeveloperSettings).isTrue() + assertThat(loadedState.showLockScreenSettings).isTrue() + assertThat(loadedState.showNotificationSettings).isTrue() + assertThat(loadedState.directLogoutState).isEqualTo(aDirectLogoutState) + assertThat(loadedState.snackbarMessage).isNull() } } } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt index 3e32ed675f..43d9648684 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt @@ -21,7 +21,7 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.AN_AVATAR_URL @@ -52,7 +52,6 @@ import java.io.File @ExperimentalCoroutinesApi class EditUserProfilePresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -110,7 +109,7 @@ class EditUserProfilePresenterTest { AvatarAction.Remove ) assertThat(initialState.saveButtonEnabled).isFalse() - assertThat(initialState.saveAction).isInstanceOf(Async.Uninitialized::class.java) + assertThat(initialState.saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java) } } @@ -359,7 +358,7 @@ class EditUserProfilePresenterTest { initialState.eventSink(EditUserProfileEvents.Save) skipItems(2) assertThat(matrixClient.uploadAvatarCalled).isFalse() - assertThat(awaitItem().saveAction).isInstanceOf(Async.Failure::class.java) + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) } } @@ -406,9 +405,9 @@ class EditUserProfilePresenterTest { initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("foo")) initialState.eventSink(EditUserProfileEvents.Save) skipItems(2) - assertThat(awaitItem().saveAction).isInstanceOf(Async.Failure::class.java) + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) initialState.eventSink(EditUserProfileEvents.CancelSaveChanges) - assertThat(awaitItem().saveAction).isInstanceOf(Async.Uninitialized::class.java) + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java) } } @@ -421,8 +420,8 @@ class EditUserProfilePresenterTest { initialState.eventSink(event) initialState.eventSink(EditUserProfileEvents.Save) skipItems(1) - assertThat(awaitItem().saveAction).isInstanceOf(Async.Loading::class.java) - assertThat(awaitItem().saveAction).isInstanceOf(Async.Failure::class.java) + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Loading::class.java) + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) } } diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/bugreport/BugReportEntryPoint.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/bugreport/BugReportEntryPoint.kt index 5a59cb0622..cebc94f31d 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/bugreport/BugReportEntryPoint.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/bugreport/BugReportEntryPoint.kt @@ -22,7 +22,6 @@ import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint interface BugReportEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder interface NodeBuilder { diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionView.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionView.kt index 3d49d637e5..48c10a75c4 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionView.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionView.kt @@ -20,8 +20,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import io.element.android.features.rageshake.api.R import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.utils.LogCompositions import io.element.android.libraries.ui.strings.CommonStrings @@ -56,7 +56,8 @@ private fun CrashDetectionContent( ) { ConfirmationDialog( title = stringResource(id = CommonStrings.action_report_bug), - content = stringResource(id = R.string.crash_detection_dialog_content, /* TODO App name */ "Element"), + // TODO Replace with app name + content = stringResource(id = R.string.crash_detection_dialog_content, "Element"), submitText = stringResource(id = CommonStrings.action_yes), cancelText = stringResource(id = CommonStrings.action_no), onCancelClicked = onNoClicked, diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt index 0672978a04..996392d879 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt @@ -27,8 +27,8 @@ import io.element.android.features.rageshake.api.screenshot.ImageResult import io.element.android.features.rageshake.api.screenshot.screenshot import io.element.android.libraries.androidutils.hardware.vibrate import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.utils.LogCompositions import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.libraries.ui.strings.CommonStrings diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt index d3c830a821..0ce48b3d08 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt @@ -26,8 +26,8 @@ import io.element.android.libraries.designsystem.components.preferences.Preferen import io.element.android.libraries.designsystem.components.preferences.PreferenceSlide import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch import io.element.android.libraries.designsystem.components.preferences.PreferenceText -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -56,7 +56,8 @@ fun RageshakePreferencesView( // summary = stringResource(id = CommonStrings.settings_rageshake_detection_threshold_summary), value = state.sensitivity, enabled = state.isEnabled, - steps = 3 /* 5 possible values - steps are in ]0, 1[ */, + // 5 possible values - steps are in ]0, 1[ + steps = 3, onValueChange = ::onSensitivityChanged ) } else { diff --git a/features/rageshake/api/src/main/res/values-hu/translations.xml b/features/rageshake/api/src/main/res/values-hu/translations.xml index 559594ed05..5b228df7a0 100644 --- a/features/rageshake/api/src/main/res/values-hu/translations.xml +++ b/features/rageshake/api/src/main/res/values-hu/translations.xml @@ -1,7 +1,7 @@ - "Az %1$s összeomlott a legutóbbi használata óta. Megosztja velünk az összeomlás-jelentést?" - "Úgy tűnik, mintha mérgében a telefont rázná. Megnyitja a hibajelentési képernyőt?" + "Az %1$s összeomlott a legutóbbi használata óta. Megosztod velünk az összeomlás-jelentést?" + "Úgy tűnik, mintha dühösen ráznád a telefont. Megnyitod a hibajelentési képernyőt?" "Ideges rázás" "Észlelési küszöb" diff --git a/features/rageshake/api/src/main/res/values-ru/translations.xml b/features/rageshake/api/src/main/res/values-ru/translations.xml index 24ba792de6..f8fd569add 100644 --- a/features/rageshake/api/src/main/res/values-ru/translations.xml +++ b/features/rageshake/api/src/main/res/values-ru/translations.xml @@ -2,6 +2,6 @@ "При последнем использовании %1$s произошел сбой. Хотите поделиться отчетом о сбое?" "Похоже, что вы трясете телефон. Хотите открыть экран сообщения об ошибке?" - "Rageshake" + "Встряхните" "Порог обнаружения" diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportNode.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportNode.kt index db9c2b5aa1..90a81e279d 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportNode.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportNode.kt @@ -38,7 +38,6 @@ class BugReportNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: BugReportPresenter, ) : Node(buildContext, plugins = plugins) { - @Composable override fun View(modifier: Modifier) { val state = presenter.present() diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt index e90787ab5c..fea6bc95b1 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt @@ -30,7 +30,7 @@ import io.element.android.features.rageshake.api.reporter.BugReporter import io.element.android.features.rageshake.api.reporter.BugReporterListener import io.element.android.features.rageshake.api.screenshot.ScreenshotHolder import io.element.android.features.rageshake.impl.logs.VectorFileLogger -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -42,30 +42,28 @@ class BugReportPresenter @Inject constructor( private val screenshotHolder: ScreenshotHolder, private val appCoroutineScope: CoroutineScope, ) : Presenter { - private class BugReporterUploadListener( private val sendingProgress: MutableFloatState, - private val sendingAction: MutableState> + private val sendingAction: MutableState> ) : BugReporterListener { - override fun onUploadCancelled() { sendingProgress.floatValue = 0f - sendingAction.value = Async.Uninitialized + sendingAction.value = AsyncAction.Uninitialized } override fun onUploadFailed(reason: String?) { sendingProgress.floatValue = 0f - sendingAction.value = Async.Failure(Exception(reason)) + sendingAction.value = AsyncAction.Failure(Exception(reason)) } override fun onProgress(progress: Int) { sendingProgress.floatValue = progress.toFloat() / 100 - sendingAction.value = Async.Loading() + sendingAction.value = AsyncAction.Loading } override fun onUploadSucceed() { sendingProgress.floatValue = 0f - sendingAction.value = Async.Success(Unit) + sendingAction.value = AsyncAction.Success(Unit) } } @@ -83,8 +81,8 @@ class BugReportPresenter @Inject constructor( val sendingProgress = remember { mutableFloatStateOf(0f) } - val sendingAction: MutableState> = remember { - mutableStateOf(Async.Uninitialized) + val sendingAction: MutableState> = remember { + mutableStateOf(AsyncAction.Uninitialized) } val formState: MutableState = remember { mutableStateOf(BugReportFormState.Default) @@ -109,7 +107,7 @@ class BugReportPresenter @Inject constructor( } BugReportEvents.ClearError -> { sendingProgress.floatValue = 0f - sendingAction.value = Async.Uninitialized + sendingAction.value = AsyncAction.Uninitialized } } } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportState.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportState.kt index b8bbe62dc6..c3bfbec888 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportState.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportState.kt @@ -17,7 +17,7 @@ package io.element.android.features.rageshake.impl.bugreport import android.os.Parcelable -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import kotlinx.parcelize.Parcelize data class BugReportState( @@ -25,11 +25,11 @@ data class BugReportState( val hasCrashLogs: Boolean, val screenshotUri: String?, val sendingProgress: Float, - val sending: Async, + val sending: AsyncAction, val eventSink: (BugReportEvents) -> Unit ) { val submitEnabled = - formState.description.length > 10 && sending !is Async.Loading + formState.description.length > 10 && sending !is AsyncAction.Loading } @Parcelize diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportStateProvider.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportStateProvider.kt index 3601c2a259..bb0d273de4 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportStateProvider.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportStateProvider.kt @@ -17,7 +17,7 @@ package io.element.android.features.rageshake.impl.bugreport import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction open class BugReportStateProvider : PreviewParameterProvider { override val values: Sequence @@ -31,8 +31,8 @@ open class BugReportStateProvider : PreviewParameterProvider { hasCrashLogs = true, screenshotUri = "aUri" ), - aBugReportState().copy(sending = Async.Loading()), - aBugReportState().copy(sending = Async.Success(Unit)), + aBugReportState().copy(sending = AsyncAction.Loading), + aBugReportState().copy(sending = AsyncAction.Success(Unit)), ) } @@ -41,6 +41,6 @@ fun aBugReportState() = BugReportState( hasCrashLogs = false, screenshotUri = null, sendingProgress = 0F, - sending = Async.Uninitialized, + sending = AsyncAction.Uninitialized, eventSink = {} ) diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt index 7833c21764..11843553e8 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt @@ -37,8 +37,8 @@ import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import coil.request.ImageRequest import io.element.android.features.rageshake.impl.R -import io.element.android.libraries.architecture.Async -import io.element.android.libraries.designsystem.components.async.AsyncView +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.form.textFieldState import io.element.android.libraries.designsystem.components.preferences.PreferencePage import io.element.android.libraries.designsystem.components.preferences.PreferenceRow @@ -67,7 +67,7 @@ fun BugReportView( title = stringResource(id = CommonStrings.common_report_a_problem), onBackPressed = onBackPressed ) { - val isFormEnabled = state.sending !is Async.Loading + val isFormEnabled = state.sending !is AsyncAction.Loading var descriptionFieldState by textFieldState( stateValue = state.formState.description ) @@ -150,9 +150,9 @@ fun BugReportView( } } - AsyncView( + AsyncActionView( async = state.sending, - showProgressDialog = false, + progressDialog = { }, onSuccess = { eventSink(BugReportEvents.ResetAll) onDone() diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/DefaultBugReportEntryPoint.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/DefaultBugReportEntryPoint.kt index 5abaec94b6..8b6bd0723a 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/DefaultBugReportEntryPoint.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/DefaultBugReportEntryPoint.kt @@ -28,7 +28,6 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultBugReportEntryPoint @Inject constructor() : BugReportEntryPoint { override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): BugReportEntryPoint.NodeBuilder { - val plugins = ArrayList() return object : BugReportEntryPoint.NodeBuilder { diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt index 6a15553bdb..1097c79939 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt @@ -20,10 +20,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.rememberCoroutineScope import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.rageshake.api.crash.CrashDataStore import io.element.android.features.rageshake.api.crash.CrashDetectionEvents import io.element.android.features.rageshake.api.crash.CrashDetectionPresenter import io.element.android.features.rageshake.api.crash.CrashDetectionState -import io.element.android.features.rageshake.api.crash.CrashDataStore import io.element.android.libraries.di.AppScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -32,7 +32,6 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultCrashDetectionPresenter @Inject constructor(private val crashDataStore: CrashDataStore) : CrashDetectionPresenter { - @Composable override fun present(): CrashDetectionState { val localCoroutineScope = rememberCoroutineScope() diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt index 58a49611be..2741505e3e 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt @@ -44,7 +44,6 @@ class DefaultRageshakeDetectionPresenter @Inject constructor( private val rageShake: RageShake, private val preferencesPresenter: RageshakePreferencesPresenter, ) : RageshakeDetectionPresenter { - @Composable override fun present(): RageshakeDetectionState { val localCoroutineScope = rememberCoroutineScope() diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/LogFormatter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/LogFormatter.kt index 45c2d0a65b..e7381c1c58 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/LogFormatter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/LogFormatter.kt @@ -26,7 +26,6 @@ import java.util.logging.Formatter import java.util.logging.LogRecord internal class LogFormatter : Formatter() { - override fun format(r: LogRecord): String { if (!isTimeZoneSet) { DATE_FORMAT.timeZone = TimeZone.getTimeZone("UTC") diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/VectorFileLogger.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/VectorFileLogger.kt index a4d06564a8..859f1c961a 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/VectorFileLogger.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/VectorFileLogger.kt @@ -41,7 +41,6 @@ class VectorFileLogger( // private val vectorPreferences: VectorPreferences private val dispatcher: CoroutineDispatcher = Dispatchers.IO, ) : Timber.Tree() { - companion object { fun getFromTimber(): VectorFileLogger? { return Timber.forest().filterIsInstance().firstOrNull() @@ -82,7 +81,7 @@ class VectorFileLogger( init { for (i in 0..15) { - val file = File(cacheDirectory, "elementLogs.${i}.txt") + val file = File(cacheDirectory, "elementLogs.$i.txt") file.safeDelete() } @@ -119,14 +118,12 @@ class VectorFileLogger( } private fun skipLog(priority: Int): Boolean { - /* - return if (vectorPreferences.labAllowedExtendedLogging()) { - false - } else { - // Exclude verbose logs - priority < Log.DEBUG - } - */ + // return if (vectorPreferences.labAllowedExtendedLogging()) { + // false + // } else { + // // Exclude verbose logs + // priority < Log.DEBUG + // } // Exclude verbose logs return priority < Log.DEBUG } @@ -144,7 +141,7 @@ class VectorFileLogger( ?.flush() ?.let { 0 until logRotationCount } ?.mapNotNull { index -> - File(cacheDirectory, "$fileNamePrefix.${index}.txt") + File(cacheDirectory, "$fileNamePrefix.$index.txt") .takeIf { it.exists() } } } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt index 82a411ec95..2880d908a0 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt @@ -38,7 +38,6 @@ class DefaultRageshakePreferencesPresenter @Inject constructor( private val rageshake: RageShake, private val rageshakeDataStore: RageshakeDataStore, ) : RageshakePreferencesPresenter { - @Composable override fun present(): RageshakePreferencesState { val localCoroutineScope = rememberCoroutineScope() diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/DefaultRageShake.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/DefaultRageShake.kt index 1d9ff6ee5a..9da634b0fe 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/DefaultRageShake.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/DefaultRageShake.kt @@ -33,7 +33,6 @@ import javax.inject.Inject class DefaultRageShake @Inject constructor( @ApplicationContext context: Context, ) : ShakeDetector.Listener, RageShake { - private var sensorManager = context.getSystemService() private var shakeDetector: ShakeDetector? = null private var interceptor: (() -> Unit)? = null 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 5acf5b4103..2fc834086a 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 @@ -88,6 +88,7 @@ class DefaultBugReporter @Inject constructor( // the pending bug report call private var bugReportCall: Call? = null + // boolean to cancel the bug report private val isCancelled = false private val logcatCommandDebug = arrayOf("logcat", "-d", "-v", "threadtime", "*:*") @@ -187,7 +188,8 @@ class DefaultBugReporter @Inject constructor( try { builder.addFormDataPart( "file", - screenshotFile.name, screenshotFile.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()) + screenshotFile.name, + screenshotFile.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()) ) } catch (e: Exception) { Timber.e(e, "## sendBugReport() : fail to write screenshot") diff --git a/features/rageshake/impl/src/main/res/values-es/translations.xml b/features/rageshake/impl/src/main/res/values-es/translations.xml index 4191f67596..3c0d78b659 100644 --- a/features/rageshake/impl/src/main/res/values-es/translations.xml +++ b/features/rageshake/impl/src/main/res/values-es/translations.xml @@ -1,14 +1,15 @@ "Adjuntar captura de pantalla" - "Podéis poneros en contacto conmigo para resolver dudas relacionadas" + "Pueden ponerse en contacto conmigo si tienen alguna pregunta relacionada." + "Contáctame" "Editar captura de pantalla" - "Describe el problema. ¿Qué hiciste? ¿Qué esperabas que ocurriera? ¿Qué ocurrió en realidad? Por favor, detállalo todo lo que puedas." - "Describe el error…" + "Describe el problema. ¿Qué has hecho? ¿Qué esperabas que ocurriera? ¿Qué ocurrió realmente? Por favor, detállalo todo lo que puedas." + "Describe el problema…" "Si es posible, escriba la descripción en inglés." "Enviar registros de fallos" - "Enviar registros para ayudar" + "Permitir registros" "Enviar captura de pantalla" - "Para comprobar que todo funciona correctamente, se enviarán registros de fallos con su mensaje. Serán privados. Para enviar sólo tu mensaje, desactiva esta opción." + "Los registros se incluirán con su mensaje para asegurarse de que todo funciona correctamente. Para enviar tu mensaje sin registros, desactiva esta configuración." "%1$s se cerró inesperadamente la última vez que se lo usaste. ¿Quieres compartir un informe de error con nosotros?" diff --git a/features/rageshake/impl/src/main/res/values-hu/translations.xml b/features/rageshake/impl/src/main/res/values-hu/translations.xml index bc09e541ab..85f22f8449 100644 --- a/features/rageshake/impl/src/main/res/values-hu/translations.xml +++ b/features/rageshake/impl/src/main/res/values-hu/translations.xml @@ -11,5 +11,5 @@ "Naplók engedélyezése" "Képernyőkép küldése" "A naplók szerepelni fognak az üzenetben, hogy megbizonyosodhassunk arról, hogy minden megfelelően működik-e. Ha naplók nélkül szeretné elküldeni az üzenetet, akkor kapcsolja ki ezt a beállítást." - "Az %1$s összeomlott a legutóbbi használata óta. Megosztja velünk az összeomlás-jelentést?" + "Az %1$s összeomlott a legutóbbi használata óta. Megosztod velünk az összeomlás-jelentést?" diff --git a/features/rageshake/impl/src/main/res/values-it/translations.xml b/features/rageshake/impl/src/main/res/values-it/translations.xml index 2c95849db0..852a8f68ff 100644 --- a/features/rageshake/impl/src/main/res/values-it/translations.xml +++ b/features/rageshake/impl/src/main/res/values-it/translations.xml @@ -1,14 +1,15 @@ "Allega istantanea schermo" - "Potete contattarmi per qualsiasi altra domanda" + "Potete contattarmi per qualsiasi altra domanda." + "Contattami" "Modifica istantanea schermo" - "Descrivi il bug. Che cosa hai fatto? Cosa ti aspettavi che accadesse? Cosa è effettivamente accaduto. Si prega di inserire il maggior numero di dettagli possibile." + "Descrivi il problema. Che cosa hai fatto? Cosa ti aspettavi che accadesse? Cosa è effettivamente accaduto. Si prega di inserire il maggior numero di dettagli possibile." "Descrivi il problema…" "Se possibile, scrivere la descrizione in inglese." "Invia i log degli arresti anomali" - "Invia i log per aiutarci" + "Consenti i log" "Invia istantanea schermo" - "Per verificare che le cose funzionino come previsto, i log verranno inviati con il tuo messaggio. Questi saranno privati. Per inviare solo il tuo messaggio, disattiva questa impostazione." + "Per verificare che le cose funzionino come previsto, i log verranno inviati con il tuo messaggio. Per inviare solo il tuo messaggio, disattiva questa impostazione." "%1$s si è chiuso inaspettatamente l\'ultima volta che è stato usato. Vuoi condividere con noi un rapporto sull\'arresto anomalo?" diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt index 3360ce3dd3..e36cc5b841 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt @@ -24,7 +24,7 @@ import io.element.android.features.rageshake.test.crash.A_CRASH_DATA import io.element.android.features.rageshake.test.crash.FakeCrashDataStore import io.element.android.features.rageshake.test.screenshot.A_SCREENSHOT_URI import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.test.A_FAILURE_REASON import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest @@ -35,7 +35,6 @@ const val A_SHORT_DESCRIPTION = "bug!" const val A_LONG_DESCRIPTION = "I have seen a bug!" class BugReportPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -53,7 +52,7 @@ class BugReportPresenterTest { val initialState = awaitItem() assertThat(initialState.hasCrashLogs).isFalse() assertThat(initialState.formState).isEqualTo(BugReportFormState.Default) - assertThat(initialState.sending).isEqualTo(Async.Uninitialized) + assertThat(initialState.sending).isEqualTo(AsyncAction.Uninitialized) assertThat(initialState.screenshotUri).isNull() assertThat(initialState.sendingProgress).isEqualTo(0f) assertThat(initialState.submitEnabled).isFalse() @@ -174,13 +173,13 @@ class BugReportPresenterTest { initialState.eventSink.invoke(BugReportEvents.SendBugReport) skipItems(1) val progressState = awaitItem() - assertThat(progressState.sending).isEqualTo(Async.Loading(null)) + assertThat(progressState.sending).isEqualTo(AsyncAction.Loading) assertThat(progressState.sendingProgress).isEqualTo(0f) assertThat(progressState.submitEnabled).isFalse() assertThat(awaitItem().sendingProgress).isEqualTo(0.5f) assertThat(awaitItem().sendingProgress).isEqualTo(1f) skipItems(1) - assertThat(awaitItem().sending).isEqualTo(Async.Success(Unit)) + assertThat(awaitItem().sending).isEqualTo(AsyncAction.Success(Unit)) } } @@ -199,17 +198,17 @@ class BugReportPresenterTest { initialState.eventSink.invoke(BugReportEvents.SendBugReport) skipItems(1) val progressState = awaitItem() - assertThat(progressState.sending).isEqualTo(Async.Loading(null)) + assertThat(progressState.sending).isEqualTo(AsyncAction.Loading) assertThat(progressState.sendingProgress).isEqualTo(0f) assertThat(awaitItem().sendingProgress).isEqualTo(0.5f) // Failure assertThat(awaitItem().sendingProgress).isEqualTo(0f) - assertThat((awaitItem().sending as Async.Failure).error.message).isEqualTo(A_FAILURE_REASON) + assertThat((awaitItem().sending as AsyncAction.Failure).error.message).isEqualTo(A_FAILURE_REASON) // Reset failure initialState.eventSink.invoke(BugReportEvents.ClearError) val lastItem = awaitItem() assertThat(lastItem.sendingProgress).isEqualTo(0f) - assertThat(lastItem.sending).isInstanceOf(Async.Uninitialized::class.java) + assertThat(lastItem.sending).isInstanceOf(AsyncAction.Uninitialized::class.java) } } @@ -228,12 +227,12 @@ class BugReportPresenterTest { initialState.eventSink.invoke(BugReportEvents.SendBugReport) skipItems(1) val progressState = awaitItem() - assertThat(progressState.sending).isEqualTo(Async.Loading(null)) + assertThat(progressState.sending).isEqualTo(AsyncAction.Loading) assertThat(progressState.sendingProgress).isEqualTo(0f) assertThat(awaitItem().sendingProgress).isEqualTo(0.5f) // Cancelled assertThat(awaitItem().sendingProgress).isEqualTo(0f) - assertThat(awaitItem().sending).isEqualTo(Async.Uninitialized) + assertThat(awaitItem().sending).isEqualTo(AsyncAction.Uninitialized) } } } diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt index 716f424ce3..917e427d9e 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt @@ -30,7 +30,6 @@ import org.junit.Rule import org.junit.Test class CrashDetectionPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -58,7 +57,6 @@ class CrashDetectionPresenterTest { skipItems(1) val initialState = awaitItem() assertThat(initialState.crashDetected).isTrue() - } } diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt index a8a80fdc25..ac8bbd6d86 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt @@ -37,7 +37,6 @@ import org.junit.Rule import org.junit.Test class RageshakeDetectionPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -202,4 +201,3 @@ class RageshakeDetectionPresenterTest { } } } - diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt index c639adb98f..a2aa895784 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt @@ -30,7 +30,6 @@ import org.junit.Rule import org.junit.Test class RageshakePreferencesPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -102,4 +101,3 @@ class RageshakePreferencesPresenterTest { } } } - diff --git a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/rageshake/FakeRageShake.kt b/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/rageshake/FakeRageShake.kt index d127c360cf..b404f85b62 100644 --- a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/rageshake/FakeRageShake.kt +++ b/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/rageshake/FakeRageShake.kt @@ -21,7 +21,6 @@ import io.element.android.features.rageshake.api.rageshake.RageShake class FakeRageShake( private var isAvailableValue: Boolean = true ) : RageShake { - private var interceptor: (() -> Unit)? = null override fun isAvailable() = isAvailableValue diff --git a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/rageshake/FakeRageshakeDataStore.kt b/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/rageshake/FakeRageshakeDataStore.kt index 698e0a3cd8..e5f564bda2 100644 --- a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/rageshake/FakeRageshakeDataStore.kt +++ b/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/rageshake/FakeRageshakeDataStore.kt @@ -26,7 +26,6 @@ class FakeRageshakeDataStore( isEnabled: Boolean = false, sensitivity: Float = A_SENSITIVITY, ) : RageshakeDataStore { - private val isEnabledFlow = MutableStateFlow(isEnabled) override fun isEnabled(): Flow = isEnabledFlow diff --git a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt index bd1a414294..bde560189b 100644 --- a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt +++ b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt @@ -27,7 +27,6 @@ import io.element.android.libraries.matrix.api.core.UserId import kotlinx.parcelize.Parcelize interface RoomDetailsEntryPoint : FeatureEntryPoint { - sealed interface InitialTarget : Parcelable { @Parcelize data object RoomDetails : InitialTarget diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt index 108ef088f0..a5c674416c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt @@ -29,7 +29,6 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultRoomDetailsEntryPoint @Inject constructor() : RoomDetailsEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomDetailsEntryPoint.NodeBuilder { return object : RoomDetailsEntryPoint.NodeBuilder { val plugins = ArrayList() diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 76a130b681..959d209d88 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -61,7 +61,6 @@ class RoomDetailsFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins, ) { - sealed interface NavTarget : Parcelable { @Parcelize data object RoomDetails : NavTarget diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt index dad02d0d07..d57a067a95 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt @@ -46,7 +46,6 @@ class RoomDetailsNode @AssistedInject constructor( private val room: MatrixRoom, private val analyticsService: AnalyticsService, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun openRoomMemberList() fun openInviteMembers() diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 34a0b2b2e1..e9aebe104b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -58,7 +58,6 @@ class RoomDetailsPresenter @Inject constructor( private val leaveRoomPresenter: LeaveRoomPresenter, private val dispatchers: CoroutineDispatchers, ) : Presenter { - @Composable override fun present(): RoomDetailsState { val scope = rememberCoroutineScope() diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt index ad76f78515..b84302463f 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt @@ -90,5 +90,6 @@ fun aRoomDetailsState() = RoomDetailsState( ) fun aDmRoomDetailsState(isDmMemberIgnored: Boolean = false) = aRoomDetailsState().copy( - roomType = RoomDetailsType.Dm(aDmRoomMember(isIgnored = isDmMemberIgnored)), roomMemberDetailsState = aRoomMemberDetailsState() + roomType = RoomDetailsType.Dm(aDmRoomMember(isIgnored = isDmMemberIgnored)), + roomMemberDetailsState = aRoomMemberDetailsState() ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index 2879995d82..bbdccbc302 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -190,9 +190,10 @@ fun RoomDetailsView( BlockUserDialogs(roomMemberState) } - OtherActionsSection(onLeaveRoom = { - state.eventSink(RoomDetailsEvent.LeaveRoom) - }) + OtherActionsSection( + isDm = state.roomType is RoomDetailsType.Dm, + onLeaveRoom = { state.eventSink(RoomDetailsEvent.LeaveRoom) } + ) } } } @@ -410,10 +411,17 @@ private fun SecuritySection(modifier: Modifier = Modifier) { } @Composable -private fun OtherActionsSection(onLeaveRoom: () -> Unit, modifier: Modifier = Modifier) { +private fun OtherActionsSection(isDm: Boolean, onLeaveRoom: () -> Unit, modifier: Modifier = Modifier) { PreferenceCategory(showDivider = false, modifier = modifier) { ListItem( - headlineContent = { Text(stringResource(R.string.screen_room_details_leave_room_title)) }, + headlineContent = { + val leaveText = stringResource(id = if (isDm) { + R.string.screen_room_details_leave_conversation_title + } else { + R.string.screen_room_details_leave_room_title + }) + Text(leaveText) + }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Leave)), style = ListItemStyle.Destructive, onClick = onLeaveRoom, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/blockuser/BlockUserSection.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/blockuser/BlockUserSection.kt index b99319812b..7ecf4ef1f9 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/blockuser/BlockUserSection.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/blockuser/BlockUserSection.kt @@ -26,7 +26,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.roomdetails.impl.R import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.designsystem.components.dialogs.RetryDialog import io.element.android.libraries.designsystem.components.list.ListItemContent @@ -42,13 +42,13 @@ import io.element.android.libraries.ui.strings.CommonStrings internal fun BlockUserSection(state: RoomMemberDetailsState, modifier: Modifier = Modifier) { PreferenceCategory(showDivider = false, modifier = modifier) { when (state.isBlocked) { - is Async.Failure -> PreferenceBlockUser(isBlocked = state.isBlocked.prevData, isLoading = false, eventSink = state.eventSink) - is Async.Loading -> PreferenceBlockUser(isBlocked = state.isBlocked.prevData, isLoading = true, eventSink = state.eventSink) - is Async.Success -> PreferenceBlockUser(isBlocked = state.isBlocked.data, isLoading = false, eventSink = state.eventSink) - Async.Uninitialized -> PreferenceBlockUser(isBlocked = null, isLoading = true, eventSink = state.eventSink) + is AsyncData.Failure -> PreferenceBlockUser(isBlocked = state.isBlocked.prevData, isLoading = false, eventSink = state.eventSink) + is AsyncData.Loading -> PreferenceBlockUser(isBlocked = state.isBlocked.prevData, isLoading = true, eventSink = state.eventSink) + is AsyncData.Success -> PreferenceBlockUser(isBlocked = state.isBlocked.data, isLoading = false, eventSink = state.eventSink) + AsyncData.Uninitialized -> PreferenceBlockUser(isBlocked = null, isLoading = true, eventSink = state.eventSink) } } - if (state.isBlocked is Async.Failure) { + if (state.isBlocked is AsyncData.Failure) { RetryDialog( content = stringResource(CommonStrings.error_unknown), onDismiss = { state.eventSink(RoomMemberDetailsEvents.ClearBlockUserError) }, @@ -56,7 +56,8 @@ internal fun BlockUserSection(state: RoomMemberDetailsState, modifier: Modifier val event = when (state.isBlocked.prevData) { true -> RoomMemberDetailsEvents.UnblockUser(needsConfirmation = false) false -> RoomMemberDetailsEvents.BlockUser(needsConfirmation = false) - null -> /*Should not happen */ RoomMemberDetailsEvents.ClearBlockUserError + // null case Should not happen + null -> RoomMemberDetailsEvents.ClearBlockUserError } state.eventSink(event) }, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt index c65b63432e..79aa5c6315 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt @@ -29,7 +29,6 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom @Module @ContributesTo(RoomScope::class) object RoomMemberModule { - @Provides fun provideRoomMemberDetailsPresenterFactory( matrixClient: MatrixClient, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditNode.kt index e81cd84c24..34f6be1b7b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditNode.kt @@ -36,7 +36,6 @@ class RoomDetailsEditNode @AssistedInject constructor( private val presenter: RoomDetailsEditPresenter, private val analyticsService: AnalyticsService, ) : Node(buildContext, plugins = plugins) { - init { lifecycle.subscribe( onResume = { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt index 40df3791d4..32f36a891b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt @@ -29,7 +29,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.core.net.toUri -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.mimetype.MimeTypes @@ -53,7 +53,6 @@ class RoomDetailsEditPresenter @Inject constructor( private val mediaPreProcessor: MediaPreProcessor, permissionsPresenterFactory: PermissionsPresenter.Factory, ) : Presenter { - private val cameraPermissionPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA) private var pendingPermissionRequest = false @@ -76,9 +75,9 @@ class RoomDetailsEditPresenter @Inject constructor( roomAvatarUri, ) { derivedStateOf { - roomAvatarUri?.toString()?.trim() != room.avatarUrl?.toUri()?.toString()?.trim() - || roomName.trim() != (room.name ?: room.displayName).trim() - || roomTopic.orEmpty().trim() != room.topic.orEmpty().trim() + roomAvatarUri?.toString()?.trim() != room.avatarUrl?.toUri()?.toString()?.trim() || + roomName.trim() != (room.name ?: room.displayName).trim() || + roomTopic.orEmpty().trim() != room.topic.orEmpty().trim() } } @@ -116,7 +115,7 @@ class RoomDetailsEditPresenter @Inject constructor( } } - val saveAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + val saveAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val localCoroutineScope = rememberCoroutineScope() fun handleEvents(event: RoomDetailsEditEvents) { when (event) { @@ -136,7 +135,7 @@ class RoomDetailsEditPresenter @Inject constructor( is RoomDetailsEditEvents.UpdateRoomName -> roomName = event.name is RoomDetailsEditEvents.UpdateRoomTopic -> roomTopic = event.topic.takeUnless { it.isEmpty() } - RoomDetailsEditEvents.CancelSaveChanges -> saveAction.value = Async.Uninitialized + RoomDetailsEditEvents.CancelSaveChanges -> saveAction.value = AsyncAction.Uninitialized } } @@ -156,7 +155,12 @@ class RoomDetailsEditPresenter @Inject constructor( ) } - private fun CoroutineScope.saveChanges(name: String, topic: String?, avatarUri: Uri?, action: MutableState>) = launch { + private fun CoroutineScope.saveChanges( + name: String, + topic: String?, + avatarUri: Uri?, + action: MutableState>, + ) = launch { val results = mutableListOf>() suspend { if (topic.orEmpty().trim() != room.topic.orEmpty().trim()) { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditState.kt index 9258d882fc..d85450b59f 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditState.kt @@ -17,7 +17,7 @@ package io.element.android.features.roomdetails.impl.edit import android.net.Uri -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.ui.media.AvatarAction import io.element.android.libraries.permissions.api.PermissionsState import kotlinx.collections.immutable.ImmutableList @@ -32,7 +32,7 @@ data class RoomDetailsEditState( val canChangeAvatar: Boolean, val avatarActions: ImmutableList, val saveButtonEnabled: Boolean, - val saveAction: Async, + val saveAction: AsyncAction, val cameraPermissionState: PermissionsState, val eventSink: (RoomDetailsEditEvents) -> Unit ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt index 730acaa1c5..d70c83b9a0 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt @@ -18,7 +18,7 @@ package io.element.android.features.roomdetails.impl.edit import android.net.Uri import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.permissions.api.aPermissionsState import kotlinx.collections.immutable.persistentListOf @@ -30,8 +30,8 @@ open class RoomDetailsEditStateProvider : PreviewParameterProvider { - @Composable override fun present(): RoomInviteMembersState { - val roomMembers = remember { mutableStateOf>>(Async.Loading()) } + val roomMembers = remember { mutableStateOf>>(AsyncData.Loading()) } val selectedUsers = remember { mutableStateOf>(persistentListOf()) } - val searchResults = remember { mutableStateOf>>(SearchBarResultState.NotSearching()) } + val searchResults = remember { mutableStateOf>>(SearchBarResultState.Initial()) } var searchQuery by rememberSaveable { mutableStateOf("") } var searchActive by rememberSaveable { mutableStateOf(false) } + var showSearchLoader = rememberSaveable { mutableStateOf(false) } LaunchedEffect(Unit) { fetchMembers(roomMembers) } - LaunchedEffect(searchQuery, roomMembers) { - performSearch(searchResults, roomMembers, selectedUsers, searchQuery) + performSearch( + searchResults = searchResults, + roomMembers = roomMembers, + selectedUsers = selectedUsers, + showSearchLoader = showSearchLoader, + searchQuery = searchQuery + ) } return RoomInviteMembersState( @@ -68,6 +75,7 @@ class RoomInviteMembersPresenter @Inject constructor( searchQuery = searchQuery, isSearchActive = searchActive, searchResults = searchResults.value, + showSearchLoader = showSearchLoader.value, eventSink = { when (it) { is RoomInviteMembersEvents.OnSearchActiveChanged -> { @@ -115,18 +123,21 @@ class RoomInviteMembersPresenter @Inject constructor( private suspend fun performSearch( searchResults: MutableState>>, - roomMembers: MutableState>>, + roomMembers: MutableState>>, selectedUsers: MutableState>, + showSearchLoader: MutableState, searchQuery: String, ) = withContext(coroutineDispatchers.io) { - searchResults.value = SearchBarResultState.NotSearching() - + searchResults.value = SearchBarResultState.Initial() + showSearchLoader.value = false val joinedMembers = roomMembers.value.dataOrNull().orEmpty() - userRepository.search(searchQuery).collect { + userRepository.search(searchQuery).onEach { state -> + showSearchLoader.value = state.isSearching searchResults.value = when { - it.isEmpty() -> SearchBarResultState.NoResults() - else -> SearchBarResultState.Results(it.map { result -> + state.results.isEmpty() && state.isSearching -> SearchBarResultState.Initial() + state.results.isEmpty() && !state.isSearching -> SearchBarResultState.NoResultsFound() + else -> SearchBarResultState.Results(state.results.map { result -> val existingMembership = joinedMembers.firstOrNull { j -> j.userId == result.matrixUser.userId }?.membership val isJoined = existingMembership == RoomMembershipState.JOIN val isInvited = existingMembership == RoomMembershipState.INVITE @@ -139,10 +150,10 @@ class RoomInviteMembersPresenter @Inject constructor( ) }.toImmutableList()) } - } + }.launchIn(this) } - private suspend fun fetchMembers(roomMembers: MutableState>>) { + private suspend fun fetchMembers(roomMembers: MutableState>>) { suspend { withContext(coroutineDispatchers.io) { roomMemberListDataSource.search("").toImmutableList() @@ -150,4 +161,3 @@ class RoomInviteMembersPresenter @Inject constructor( }.runCatchingUpdatingState(roomMembers) } } - diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersState.kt index 16436debf0..84fb1b0986 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersState.kt @@ -23,6 +23,7 @@ import kotlinx.collections.immutable.ImmutableList data class RoomInviteMembersState( val canInvite: Boolean, val searchQuery: String, + val showSearchLoader: Boolean, val searchResults: SearchBarResultState>, val selectedUsers: ImmutableList, val isSearchActive: Boolean, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersStateProvider.kt index f44d518fb5..ca4e4ac7d2 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersStateProvider.kt @@ -32,7 +32,7 @@ internal class RoomInviteMembersStateProvider : PreviewParameterProvider> = SearchBarResultState.NotSearching(), + searchResults: SearchBarResultState> = SearchBarResultState.Initial(), selectedUsers: ImmutableList = persistentListOf(), isSearchActive: Boolean = false, + showSearchLoader: Boolean = false, ): RoomInviteMembersState { return RoomInviteMembersState( canInvite = canInvite, @@ -80,6 +92,7 @@ private fun aRoomInviteMembersState( searchResults = searchResults, selectedUsers = selectedUsers, isSearchActive = isSearchActive, + showSearchLoader = showSearchLoader, eventSink = {}, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt index 6c19b45686..a66813a699 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt @@ -30,7 +30,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.features.roomdetails.impl.R +import io.element.android.libraries.designsystem.components.async.AsyncLoading import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview @@ -49,7 +51,6 @@ import io.element.android.libraries.matrix.ui.components.CheckableUserRow import io.element.android.libraries.matrix.ui.components.SelectedUsersList import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.getBestName -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList @@ -86,6 +87,7 @@ fun RoomInviteMembersView( RoomInviteMembersSearchBar( modifier = Modifier.fillMaxWidth(), query = state.searchQuery, + showLoader = state.showSearchLoader, selectedUsers = state.selectedUsers, state = state.searchResults, active = state.isSearchActive, @@ -139,6 +141,7 @@ private fun RoomInviteMembersTopBar( private fun RoomInviteMembersSearchBar( query: String, state: SearchBarResultState>, + showLoader: Boolean, selectedUsers: ImmutableList, active: Boolean, onActiveChanged: (Boolean) -> Unit, @@ -167,6 +170,11 @@ private fun RoomInviteMembersSearchBar( }, showBackButton = false, resultState = state, + contentSuffix = { + if (showLoader) { + AsyncLoading() + } + }, resultHandler = { results -> Text( text = stringResource(id = CommonStrings.common_search_results), diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt index 5a9c30698b..867ecd991f 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt @@ -31,7 +31,6 @@ class RoomMemberListDataSource @Inject constructor( private val room: MatrixRoom, private val coroutineDispatchers: CoroutineDispatchers, ) { - suspend fun search(query: String): List = withContext(coroutineDispatchers.io) { val roomMembers = room.membersStateFlow .dropWhile { it !is MatrixRoomMembersState.Ready } @@ -42,11 +41,10 @@ class RoomMemberListDataSource @Inject constructor( roomMembers } else { roomMembers.filter { member -> - member.userId.value.contains(query, ignoreCase = true) - || member.displayName?.contains(query, ignoreCase = true).orFalse() + member.userId.value.contains(query, ignoreCase = true) || + member.displayName?.contains(query, ignoreCase = true).orFalse() } } filteredMembers } - } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt index 6145ca79d8..8148caaa5b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt @@ -38,7 +38,6 @@ class RoomMemberListNode @AssistedInject constructor( private val presenter: RoomMemberListPresenter, private val analyticsService: AnalyticsService, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun openRoomMemberDetails(roomMemberId: UserId) fun openInviteMembers() diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index 49ae479d40..6fc5b6ece7 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -25,7 +25,7 @@ import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.theme.components.SearchBarResultState @@ -41,13 +41,12 @@ class RoomMemberListPresenter @Inject constructor( private val roomMemberListDataSource: RoomMemberListDataSource, private val coroutineDispatchers: CoroutineDispatchers, ) : Presenter { - @Composable override fun present(): RoomMemberListState { - var roomMembers by remember { mutableStateOf>(Async.Loading()) } + var roomMembers by remember { mutableStateOf>(AsyncData.Loading()) } var searchQuery by rememberSaveable { mutableStateOf("") } var searchResults by remember { - mutableStateOf>(SearchBarResultState.NotSearching()) + mutableStateOf>(SearchBarResultState.Initial()) } var isSearchActive by rememberSaveable { mutableStateOf(false) } @@ -59,7 +58,7 @@ class RoomMemberListPresenter @Inject constructor( LaunchedEffect(Unit) { withContext(coroutineDispatchers.io) { val members = roomMemberListDataSource.search("").groupBy { it.membership } - roomMembers = Async.Success( + roomMembers = AsyncData.Success( RoomMembers( invited = members.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(), joined = members.getOrDefault(RoomMembershipState.JOIN, emptyList()).toImmutableList(), @@ -71,16 +70,19 @@ class RoomMemberListPresenter @Inject constructor( LaunchedEffect(searchQuery) { withContext(coroutineDispatchers.io) { searchResults = if (searchQuery.isEmpty()) { - SearchBarResultState.NotSearching() + SearchBarResultState.Initial() } else { val results = roomMemberListDataSource.search(searchQuery).groupBy { it.membership } - if (results.isEmpty()) SearchBarResultState.NoResults() - else SearchBarResultState.Results( - RoomMembers( - invited = results.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(), - joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList()).toImmutableList(), + if (results.isEmpty()) { + SearchBarResultState.NoResultsFound() + } else { + SearchBarResultState.Results( + RoomMembers( + invited = results.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(), + joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList()).toImmutableList(), + ) ) - ) + } } } } @@ -100,4 +102,3 @@ class RoomMemberListPresenter @Inject constructor( ) } } - diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt index f718db703b..aa2d94cc65 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt @@ -16,13 +16,13 @@ package io.element.android.features.roomdetails.impl.members -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.room.RoomMember import kotlinx.collections.immutable.ImmutableList data class RoomMemberListState( - val roomMembers: Async, + val roomMembers: AsyncData, val searchQuery: String, val searchResults: SearchBarResultState, val isSearchActive: Boolean, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt index 4954af8efc..5d9549808c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt @@ -17,7 +17,7 @@ package io.element.android.features.roomdetails.impl.members import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomMember @@ -28,14 +28,14 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider get() = sequenceOf( aRoomMemberListState( - roomMembers = Async.Success( + roomMembers = AsyncData.Success( RoomMembers( invited = persistentListOf(aVictor(), aWalter()), joined = persistentListOf(anAlice(), aBob()), ) ) ), - aRoomMemberListState(roomMembers = Async.Loading()), + aRoomMemberListState(roomMembers = AsyncData.Loading()), aRoomMemberListState().copy(canInvite = true), aRoomMemberListState().copy(isSearchActive = false), aRoomMemberListState().copy(isSearchActive = true), @@ -53,14 +53,14 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider = Async.Uninitialized, - searchResults: SearchBarResultState = SearchBarResultState.NotSearching(), + roomMembers: AsyncData = AsyncData.Uninitialized, + searchResults: SearchBarResultState = SearchBarResultState.Initial(), ) = RoomMemberListState( roomMembers = roomMembers, searchQuery = "", diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt index 0e48a95ec1..0fe08ef02c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt @@ -20,7 +20,6 @@ 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.ExperimentalLayoutApi import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -38,12 +37,13 @@ import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.features.roomdetails.impl.R -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.aliasScreenTitle import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.Scaffold @@ -56,11 +56,9 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.ui.components.MatrixUserRow -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList -@OptIn(ExperimentalLayoutApi::class) @Composable fun RoomMemberListView( state: RoomMemberListState, @@ -69,7 +67,6 @@ fun RoomMemberListView( onMemberSelected: (UserId) -> Unit, modifier: Modifier = Modifier, ) { - fun onUserSelected(roomMember: RoomMember) { onMemberSelected(roomMember.userId) } @@ -105,7 +102,7 @@ fun RoomMemberListView( ) if (!state.isSearchActive) { - if (state.roomMembers is Async.Success) { + if (state.roomMembers is AsyncData.Success) { RoomMemberList( roomMembers = state.roomMembers.data, showMembersCount = true, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt index 3037136f63..bdf7385661 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt @@ -30,7 +30,7 @@ import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.anvilannotations.ContributesNode import io.element.android.features.roomdetails.impl.R import io.element.android.libraries.androidutils.system.startSharePlainTextIntent -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.RoomScope @@ -48,7 +48,6 @@ class RoomMemberDetailsNode @AssistedInject constructor( private val analyticsService: AnalyticsService, presenterFactory: RoomMemberDetailsPresenter.Factory, ) : Node(buildContext, plugins = plugins) { - interface Callback : NodeInputs { fun openAvatarPreview(username: String, avatarUrl: String) fun onStartDM(roomId: RoomId) @@ -95,7 +94,7 @@ class RoomMemberDetailsNode @AssistedInject constructor( val state = presenter.present() LaunchedEffect(state.startDmActionState) { - if (state.startDmActionState is Async.Success) { + if (state.startDmActionState is AsyncAction.Success) { onStartDM(state.startDmActionState.data) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt index e82b5f0cb5..f3f3488018 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt @@ -29,7 +29,8 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.features.createroom.api.StartDMAction import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState.ConfirmationDialog -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.matrix.api.MatrixClient @@ -46,7 +47,6 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( private val room: MatrixRoom, private val startDMAction: StartDMAction, ) : Presenter { - interface Factory { fun create(roomMemberId: UserId): RoomMemberDetailsPresenter } @@ -56,14 +56,14 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( val coroutineScope = rememberCoroutineScope() var confirmationDialog by remember { mutableStateOf(null) } val roomMember by room.getRoomMemberAsState(roomMemberId) - val startDmActionState: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + val startDmActionState: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } // the room member is not really live... - val isBlocked: MutableState> = remember(roomMember) { + val isBlocked: MutableState> = remember(roomMember) { val isIgnored = roomMember?.isIgnored if (isIgnored == null) { - mutableStateOf(Async.Uninitialized) + mutableStateOf(AsyncData.Uninitialized) } else { - mutableStateOf(Async.Success(isIgnored)) + mutableStateOf(AsyncData.Success(isIgnored)) } } LaunchedEffect(Unit) { @@ -90,7 +90,7 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( } RoomMemberDetailsEvents.ClearConfirmationDialog -> confirmationDialog = null RoomMemberDetailsEvents.ClearBlockUserError -> { - isBlocked.value = Async.Success(isBlocked.value.dataOrNull().orFalse()) + isBlocked.value = AsyncData.Success(isBlocked.value.dataOrNull().orFalse()) } RoomMemberDetailsEvents.StartDM -> { coroutineScope.launch { @@ -98,7 +98,7 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( } } RoomMemberDetailsEvents.ClearStartDMState -> { - startDmActionState.value = Async.Uninitialized + startDmActionState.value = AsyncAction.Uninitialized } } } @@ -127,30 +127,30 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( ) } - private fun CoroutineScope.blockUser(userId: UserId, isBlockedState: MutableState>) = launch { - isBlockedState.value = Async.Loading(false) + private fun CoroutineScope.blockUser(userId: UserId, isBlockedState: MutableState>) = launch { + isBlockedState.value = AsyncData.Loading(false) client.ignoreUser(userId) .fold( onSuccess = { - isBlockedState.value = Async.Success(true) + isBlockedState.value = AsyncData.Success(true) room.updateMembers() }, onFailure = { - isBlockedState.value = Async.Failure(it, false) + isBlockedState.value = AsyncData.Failure(it, false) } ) } - private fun CoroutineScope.unblockUser(userId: UserId, isBlockedState: MutableState>) = launch { - isBlockedState.value = Async.Loading(true) + private fun CoroutineScope.unblockUser(userId: UserId, isBlockedState: MutableState>) = launch { + isBlockedState.value = AsyncData.Loading(true) client.unignoreUser(userId) .fold( onSuccess = { - isBlockedState.value = Async.Success(false) + isBlockedState.value = AsyncData.Success(false) room.updateMembers() }, onFailure = { - isBlockedState.value = Async.Failure(it, true) + isBlockedState.value = AsyncData.Failure(it, true) } ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsState.kt index ee9cfe388f..535ab79280 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsState.kt @@ -16,20 +16,22 @@ package io.element.android.features.roomdetails.impl.members.details -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.RoomId data class RoomMemberDetailsState( val userId: String, val userName: String?, val avatarUrl: String?, - val isBlocked: Async, - val startDmActionState: Async, + val isBlocked: AsyncData, + val startDmActionState: AsyncAction, val displayConfirmationDialog: ConfirmationDialog?, val isCurrentUser: Boolean, val eventSink: (RoomMemberDetailsEvents) -> Unit ) { enum class ConfirmationDialog { - Block, Unblock + Block, + Unblock } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt index c5710986a6..ff4b9ee1b3 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt @@ -17,18 +17,19 @@ package io.element.android.features.roomdetails.impl.members.details import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData open class RoomMemberDetailsStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aRoomMemberDetailsState(), aRoomMemberDetailsState().copy(userName = null), - aRoomMemberDetailsState().copy(isBlocked = Async.Success(true)), + aRoomMemberDetailsState().copy(isBlocked = AsyncData.Success(true)), aRoomMemberDetailsState().copy(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Block), aRoomMemberDetailsState().copy(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Unblock), - aRoomMemberDetailsState().copy(isBlocked = Async.Loading(true)), - aRoomMemberDetailsState().copy(startDmActionState = Async.Loading()), + aRoomMemberDetailsState().copy(isBlocked = AsyncData.Loading(true)), + aRoomMemberDetailsState().copy(startDmActionState = AsyncAction.Loading), // Add other states here ) } @@ -37,8 +38,8 @@ fun aRoomMemberDetailsState() = RoomMemberDetailsState( userId = "@daniel:domain.com", userName = "Daniel", avatarUrl = null, - isBlocked = Async.Success(false), - startDmActionState = Async.Uninitialized, + isBlocked = AsyncData.Success(false), + startDmActionState = AsyncAction.Uninitialized, displayConfirmationDialog = null, isCurrentUser = false, eventSink = {}, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsView.kt index adf7bc52ed..8463c98ee0 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsView.kt @@ -33,7 +33,8 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.roomdetails.impl.R import io.element.android.features.roomdetails.impl.blockuser.BlockUserDialogs import io.element.android.features.roomdetails.impl.blockuser.BlockUserSection -import io.element.android.libraries.designsystem.components.async.AsyncView +import io.element.android.libraries.designsystem.components.async.AsyncActionView +import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreviewDark @@ -88,9 +89,13 @@ fun RoomMemberDetailsView( BlockUserSection(state) BlockUserDialogs(state) } - AsyncView( + AsyncActionView( async = state.startDmActionState, - progressText = stringResource(CommonStrings.common_starting_chat), + progressDialog = { + AsyncActionViewDefaults.ProgressDialog( + progressText = stringResource(CommonStrings.common_starting_chat), + ) + }, onSuccess = onDMStarted, errorMessage = { stringResource(R.string.screen_start_chat_error_starting_chat) }, onRetry = { state.eventSink(RoomMemberDetailsEvents.StartDM) }, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt index 8b3c25d267..0ce6e56176 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt @@ -20,8 +20,8 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode sealed interface RoomNotificationSettingsEvents { data class RoomNotificationModeChanged(val mode: RoomNotificationMode) : RoomNotificationSettingsEvents - data class SetNotificationMode(val isDefault: Boolean): RoomNotificationSettingsEvents - data object DeleteCustomNotification: RoomNotificationSettingsEvents - data object ClearSetNotificationError: RoomNotificationSettingsEvents - data object ClearRestoreDefaultError: RoomNotificationSettingsEvents + data class SetNotificationMode(val isDefault: Boolean) : RoomNotificationSettingsEvents + data object DeleteCustomNotification : RoomNotificationSettingsEvents + data object ClearSetNotificationError : RoomNotificationSettingsEvents + data object ClearRestoreDefaultError : RoomNotificationSettingsEvents } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt index 8ccdd3b158..3b65bdf2fd 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt @@ -39,7 +39,6 @@ class RoomNotificationSettingsNode @AssistedInject constructor( presenterFactory: RoomNotificationSettingsPresenter.Factory, private val analyticsService: AnalyticsService, ) : Node(buildContext, plugins = plugins) { - data class RoomNotificationSettingInput( val showUserDefinedSettingStyle: Boolean ) : NodeInputs diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt index c3994e160c..84831fe92b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt @@ -28,7 +28,8 @@ import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService @@ -48,7 +49,6 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( private val notificationSettingsService: NotificationSettingsService, @Assisted private val showUserDefinedSettingStyle: Boolean, ) : Presenter { - @AssistedFactory interface Factory { fun create(showUserDefinedSettingStyle: Boolean): RoomNotificationSettingsPresenter @@ -61,11 +61,11 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( mutableStateOf(null) } val localCoroutineScope = rememberCoroutineScope() - val setNotificationSettingAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } - val restoreDefaultAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + val setNotificationSettingAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } + val restoreDefaultAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - val roomNotificationSettings: MutableState> = remember { - mutableStateOf(Async.Uninitialized) + val roomNotificationSettings: MutableState> = remember { + mutableStateOf(AsyncData.Uninitialized) } // We store state of which mode the user has set via the notification service before the new push settings have been updated. @@ -103,7 +103,7 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( localCoroutineScope.restoreDefaultRoomNotificationMode(restoreDefaultAction, pendingSetDefault) } else { defaultRoomNotificationMode.value?.let { - localCoroutineScope.setRoomNotificationMode(it, pendingRoomNotificationMode, pendingSetDefault, setNotificationSettingAction) + localCoroutineScope.setRoomNotificationMode(it, pendingRoomNotificationMode, pendingSetDefault, setNotificationSettingAction) } } } @@ -111,10 +111,10 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( localCoroutineScope.restoreDefaultRoomNotificationMode(restoreDefaultAction, pendingSetDefault) } RoomNotificationSettingsEvents.ClearSetNotificationError -> { - setNotificationSettingAction.value = Async.Uninitialized + setNotificationSettingAction.value = AsyncAction.Uninitialized } RoomNotificationSettingsEvents.ClearRestoreDefaultError -> { - restoreDefaultAction.value = Async.Uninitialized + restoreDefaultAction.value = AsyncAction.Uninitialized } } } @@ -136,7 +136,7 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( @OptIn(FlowPreview::class) private fun CoroutineScope.observeNotificationSettings( pendingModeState: MutableState, - roomNotificationSettings: MutableState> + roomNotificationSettings: MutableState> ) { notificationSettingsService.notificationSettingsChangeFlow .debounce(0.5.seconds) @@ -148,7 +148,7 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( private fun CoroutineScope.fetchNotificationSettings( pendingModeState: MutableState, - roomNotificationSettings: MutableState> + roomNotificationSettings: MutableState> ) = launch { suspend { pendingModeState.value = null @@ -169,7 +169,7 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( mode: RoomNotificationMode, pendingModeState: MutableState, pendingDefaultState: MutableState, - action: MutableState> + action: MutableState> ) = launch { suspend { pendingModeState.value = mode @@ -184,7 +184,7 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( } private fun CoroutineScope.restoreDefaultRoomNotificationMode( - action: MutableState>, + action: MutableState>, pendingDefaultState: MutableState ) = launch { suspend { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt index 4c23c0e2ae..6f0ec6ea77 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt @@ -16,19 +16,20 @@ package io.element.android.features.roomdetails.impl.notificationsettings -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationSettings data class RoomNotificationSettingsState( val showUserDefinedSettingStyle: Boolean, val roomName: String, - val roomNotificationSettings: Async, + val roomNotificationSettings: AsyncData, val pendingRoomNotificationMode: RoomNotificationMode?, val pendingSetDefault: Boolean?, val defaultRoomNotificationMode: RoomNotificationMode?, - val setNotificationSettingAction: Async, - val restoreDefaultAction: Async, + val setNotificationSettingAction: AsyncAction, + val restoreDefaultAction: AsyncAction, val displayMentionsOnlyDisclaimer: Boolean, val eventSink: (RoomNotificationSettingsEvents) -> Unit ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt index aee68383f7..7a1cc6c63e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt @@ -17,7 +17,8 @@ package io.element.android.features.roomdetails.impl.notificationsettings import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationSettings @@ -26,25 +27,26 @@ internal class RoomNotificationSettingsStateProvider : PreviewParameterProvider< get() = sequenceOf( aRoomNotificationSettingsState(), aRoomNotificationSettingsState(isDefault = false), - aRoomNotificationSettingsState(setNotificationSettingAction = Async.Loading(Unit)), - aRoomNotificationSettingsState(setNotificationSettingAction = Async.Failure(Throwable("error"))), - aRoomNotificationSettingsState(restoreDefaultAction = Async.Loading(Unit)), - aRoomNotificationSettingsState(restoreDefaultAction = Async.Failure(Throwable("error"))), + aRoomNotificationSettingsState(setNotificationSettingAction = AsyncAction.Loading), + aRoomNotificationSettingsState(setNotificationSettingAction = AsyncAction.Failure(Throwable("error"))), + aRoomNotificationSettingsState(restoreDefaultAction = AsyncAction.Loading), + aRoomNotificationSettingsState(restoreDefaultAction = AsyncAction.Failure(Throwable("error"))), aRoomNotificationSettingsState(displayMentionsOnlyDisclaimer = true) ) private fun aRoomNotificationSettingsState( isDefault: Boolean = true, - setNotificationSettingAction: Async = Async.Uninitialized, - restoreDefaultAction: Async = Async.Uninitialized, + setNotificationSettingAction: AsyncAction = AsyncAction.Uninitialized, + restoreDefaultAction: AsyncAction = AsyncAction.Uninitialized, displayMentionsOnlyDisclaimer: Boolean = false, ): RoomNotificationSettingsState { return RoomNotificationSettingsState( showUserDefinedSettingStyle = false, roomName = "Room 1", - Async.Success(RoomNotificationSettings( + AsyncData.Success(RoomNotificationSettings( mode = RoomNotificationMode.MUTE, - isDefault = isDefault)), + isDefault = isDefault + )), pendingRoomNotificationMode = null, pendingSetDefault = null, defaultRoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt index 6bba2436b3..f534fb17f4 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt @@ -31,9 +31,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.features.roomdetails.impl.R import io.element.android.libraries.core.bool.orTrue -import io.element.android.libraries.designsystem.components.async.AsyncView +import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch @@ -45,7 +46,6 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.room.RoomNotificationMode -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -133,8 +133,8 @@ private fun RoomSpecificNotificationSettingsView( } RoomNotificationMode.MUTE -> stringResource(id = CommonStrings.common_mute) } - val displayMentionsOnlyDisclaimer = state.displayMentionsOnlyDisclaimer - && state.defaultRoomNotificationMode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY + val displayMentionsOnlyDisclaimer = state.displayMentionsOnlyDisclaimer && + state.defaultRoomNotificationMode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY RoomNotificationSettingsOption( roomNotificationSettingsItem = RoomNotificationSettingsItem(state.defaultRoomNotificationMode, defaultModeTitle), isSelected = true, @@ -157,14 +157,14 @@ private fun RoomSpecificNotificationSettingsView( } } - AsyncView( + AsyncActionView( async = state.setNotificationSettingAction, onSuccess = {}, errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) }, ) - AsyncView( + AsyncActionView( async = state.restoreDefaultAction, onSuccess = {}, errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt index a3ff7f23d7..bb037c2ab5 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt @@ -17,7 +17,8 @@ package io.element.android.features.roomdetails.impl.notificationsettings import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationSettings @@ -27,16 +28,17 @@ internal class UserDefinedRoomNotificationSettingsStateProvider : PreviewParamet RoomNotificationSettingsState( showUserDefinedSettingStyle = false, roomName = "Room 1", - Async.Success( + AsyncData.Success( RoomNotificationSettings( mode = RoomNotificationMode.MUTE, - isDefault = false) + isDefault = false + ) ), pendingRoomNotificationMode = null, pendingSetDefault = null, defaultRoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, - setNotificationSettingAction = Async.Uninitialized, - restoreDefaultAction = Async.Uninitialized, + setNotificationSettingAction = AsyncAction.Uninitialized, + restoreDefaultAction = AsyncAction.Uninitialized, displayMentionsOnlyDisclaimer = false, eventSink = { }, ), diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt index 9b982542fd..df1a41ea87 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt @@ -29,7 +29,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.features.roomdetails.impl.R import io.element.android.libraries.core.bool.orTrue -import io.element.android.libraries.designsystem.components.async.AsyncView +import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -81,14 +81,14 @@ fun UserDefinedRoomNotificationSettingsView( } ) - AsyncView( + AsyncActionView( async = state.setNotificationSettingAction, onSuccess = {}, errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) }, ) - AsyncView( + AsyncActionView( async = state.restoreDefaultAction, onSuccess = { onBackPressed() }, errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, diff --git a/features/roomdetails/impl/src/main/res/values-de/translations.xml b/features/roomdetails/impl/src/main/res/values-de/translations.xml index 9a096e55ae..dbf2742e10 100644 --- a/features/roomdetails/impl/src/main/res/values-de/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-de/translations.xml @@ -13,7 +13,7 @@ "Raum bearbeiten" "Es ist ein unbekannter Fehler aufgetreten und die Informationen konnten nicht geändert werden." "Raum kann nicht aktualisiert werden" - "Nachrichten sind mit Schlössern gesichert. Nur du und die Empfänger haben die eindeutigen Schlüssel, um sie zu entsperren." + "Nachrichten und Anrufe sind Ende-zu-Ende verschlüsselt. Nur du und die Empfänger haben die eindeutigen Schlüssel, um sie zu entsperren." "Nachrichtenverschlüsselung aktiviert" "Beim Laden der Benachrichtigungseinstellungen ist ein Fehler aufgetreten." "Die Stummschaltung dieses Raums ist fehlgeschlagen, bitte versuche es erneut." @@ -27,10 +27,10 @@ "Raum wird aktualisiert…" "Ausstehend" "Raummitglieder" - "Benutzerdefinierte Einstellung zulassen" + "Benutzerdefinierte Einstellungen verwenden" "Wenn du diese Option aktivierst, wird deine Standardeinstellung außer Kraft gesetzt." "Benachrichtige mich in diesem Chat bei" - "Du kannst das in deinem %1$s ändern." + "Zum Anpassen der Standardeinstellungen gehe zu: %1$s" "Globale Einstellungen" "Standardeinstellung" "Benutzerdefinierte Einstellung entfernen" @@ -41,12 +41,12 @@ "Alle Nachrichten" "Benachrichtige mich in diesem Raum bei" "Beim Versuch, einen Chat zu starten, ist ein Fehler aufgetreten" - "Sperren" - "Gesperrte Benutzer können dir keine Nachrichten senden und alle ihre Nachrichten werden ausgeblendet. Du kannst sie jederzeit entsperren." - "Benutzer sperren" - "Entsperren" + "Blockieren" + "Blockierte Benutzer können Dir keine Nachrichten senden und alle ihre alten Nachrichten werden ausgeblendet. Die Blockierung kann jederzeit aufgehoben werden." + "Benutzer blockieren" + "Blockierung aufheben" "Du kannst dann wieder alle Nachrichten von ihnen sehen." - "Benutzer entsperren" + "Blockierung aufheben" "Raum verlassen" "Sicherheit" "Thema" diff --git a/features/roomdetails/impl/src/main/res/values-es/translations.xml b/features/roomdetails/impl/src/main/res/values-es/translations.xml index 60353e3185..f4e598ed71 100644 --- a/features/roomdetails/impl/src/main/res/values-es/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-es/translations.xml @@ -4,18 +4,51 @@ "Una persona" "%1$d personas" + "Se ha producido un error al actualizar la configuración de notificaciones." + "Tu servidor principal no admite esta opción en salas cifradas, puede que no recibas notificaciones en algunas salas." + "Encuestas" + "Añadir tema" + "Ya eres miembro" + "Ya estás invitado" + "Editar sala" + "Se ha producido un error desconocido y no se ha podido cambiar la información." + "No se puede actualizar la sala" "Los mensajes están protegidos con \"candados\". Sólo tú y los destinatarios tenéis las llaves únicas para abrirlos." "Cifrado de mensajes activado" + "Se ha producido un error al cargar la configuración de las notificaciones." + "No se ha podido silenciar esta sala, inténtalo de nuevo." + "Error al dejar de silenciar esta sala, por favor inténtalo de nuevo." "Invitar a otras personas" + "Personalizado" + "Por defecto" + "Notificaciones" + "Nombre de la sala" "Compartir sala" + "Actualizando la sala…" + "Pendiente" + "Miembros de la sala" + "Permitir configuración personalizada" + "Si activas esta opción, anularás tu configuración por defecto" + "Notificarme en este chat para" + "Puedes cambiarlo en tus %1$s." + "ajustes globales" + "Ajustes predeterminados" + "Eliminar configuración personalizada" + "Se ha producido un error al cargar la configuración de las notificaciones." + "No se pudo restaurar el modo predeterminado, por favor vuelve a intentarlo de nuevo." + "No se pudo cambiar el modo, por favor inténtalo de nuevo." + "Tu servidor principal no admite esta opción en salas cifradas, no recibirás notificaciones en esta sala." + "Todos los mensajes" + "En esta sala, notificarme por" "Se ha producido un error al intentar iniciar un chat" "Bloquear" - "Los usuarios bloqueados no podrán enviarte mensajes y se ocultarán todos sus mensajes. Puede revertir esta acción en cualquier momento." + "Los usuarios bloqueados no podrán enviarte mensajes y todos sus mensajes se ocultarán. Puedes desbloquearlos cuando quieras." "Bloquear usuario" "Desbloquear" - "Al desbloquear al usuario, podrás volver a ver todos sus mensajes." + "Podrás ver todos sus mensajes de nuevo." "Desbloquear usuario" "Salir de la sala" "Seguridad" "Tema" + "Únicamente Menciones y Palabras clave" diff --git a/features/roomdetails/impl/src/main/res/values-in/translations.xml b/features/roomdetails/impl/src/main/res/values-in/translations.xml index d941939c41..d44f24342b 100644 --- a/features/roomdetails/impl/src/main/res/values-in/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-in/translations.xml @@ -5,6 +5,7 @@ "Terjadi kesalahan saat memperbarui pengaturan pemberitahuan." "Homeserver Anda tidak mendukung opsi ini dalam ruangan terenkripsi, Anda mungkin tidak diberi tahu dalam beberapa ruangan." + "Pemungutan suara" "Tambahkan topik" "Sudah menjadi anggota" "Sudah diundang" diff --git a/features/roomdetails/impl/src/main/res/values-it/translations.xml b/features/roomdetails/impl/src/main/res/values-it/translations.xml index 3217efe862..9b4672a51f 100644 --- a/features/roomdetails/impl/src/main/res/values-it/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-it/translations.xml @@ -4,18 +4,51 @@ "1 persona" "%1$d persone" + "Si è verificato un errore durante l\'aggiornamento delle impostazioni di notifica." + "Il tuo homeserver non supporta questa opzione nelle stanze criptate, quindi potresti non ricevere notifiche in alcune stanze." + "Sondaggi" + "Aggiungi argomento" + "Già membro" + "Già invitato" + "Modifica stanza" + "Si è verificato un errore sconosciuto e non è stato possibile modificare le informazioni." + "Impossibile aggiornare la stanza" "I messaggi sono protetti da lucchetti. Solo tu e i destinatari avete le chiavi univoche per sbloccarli." "Crittografia messaggi abilitata" + "Si è verificato un errore durante il caricamento delle impostazioni di notifica." + "Impostazione del silenzioso fallita per questa stanza, riprova." + "Disattivazione del silenzioso di questa stanza fallita, riprova." "Invita persone" + "Personalizzato" + "Predefinito" + "Notifiche" + "Nome stanza" "Condividi stanza" + "Aggiornamento della stanza…" + "In attesa" + "Membri della stanza" + "Consenti impostazione personalizzata" + "L\'attivazione di questa opzione sovrascriverà l\'impostazione predefinita" + "Avvisami in questa chat per" + "Puoi cambiarlo nelle tue %1$s." + "impostazioni globali" + "Impostazione predefinita" + "Rimuovi l\'impostazione personalizzata" + "Si è verificato un errore durante il caricamento delle impostazioni di notifica." + "Ripristino della modalità predefinita fallito, riprova." + "Impossibile impostare la modalità, riprova." + "Il tuo homeserver non supporta questa opzione nelle stanze criptate, quindi non riceverai notifiche in questa stanza." + "Tutti i messaggi" + "In questa stanza, avvisami per" "Si è verificato un errore durante il tentativo di avviare una chat" "Blocca" - "Gli utenti bloccati non saranno in grado di inviarti nuovi messaggi e tutti quelli già esistenti saranno nascosti. Potrai annullare questa azione in qualsiasi momento." + "Gli utenti bloccati non saranno in grado di inviarti messaggi e tutti quelli già ricevuti saranno nascosti. Puoi sbloccarli in qualsiasi momento." "Blocca utente" "Sblocca" - "Dopo aver sbloccato l\'utente, potrai vedere nuovamente tutti i suoi messaggi." + "Potrai vedere di nuovo tutti i suoi messaggi." "Sblocca utente" "Esci dalla stanza" "Sicurezza" "Oggetto" + "Solo menzioni e parole chiave" diff --git a/features/roomdetails/impl/src/main/res/values-ru/translations.xml b/features/roomdetails/impl/src/main/res/values-ru/translations.xml index d83820dbbf..9f586230b2 100644 --- a/features/roomdetails/impl/src/main/res/values-ru/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ru/translations.xml @@ -12,7 +12,7 @@ "Уже зарегистрирован" "Уже приглашены" "Редактировать комнату" - "Произошла неизвестная ошибка, и информацию нельзя было изменить." + "Произошла неизвестная ошибка и информацию не удалось изменить." "Не удалось обновить комнату" "Сообщения зашифрованы. Только у вас и у получателей есть уникальные ключи для их разблокировки." "Шифрование сообщений включено" diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index 13219abaf8..2693b40edb 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -47,6 +47,7 @@ "Unblock" "You\'ll be able to see all messages from them again." "Unblock user" + "Leave conversation" "Leave room" "Security" "Topic" diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt index 6ffba16377..0a25434339 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt @@ -59,7 +59,6 @@ import kotlin.time.Duration.Companion.milliseconds @ExperimentalCoroutinesApi class RoomDetailsPresenterTests { - @get:Rule val warmUpRule = WarmUpRule() @@ -384,7 +383,6 @@ class RoomDetailsPresenterTests { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - notificationSettingsService.setRoomNotificationMode(room.roomId, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) val updatedState = consumeItemsUntilPredicate { it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY @@ -453,4 +451,3 @@ fun aMatrixRoom( isDirect = isDirect, notificationSettingsService = notificationSettingsService ) - diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt index 2c1b399da8..f2f6f9b1b4 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt @@ -24,7 +24,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.roomdetails.aMatrixRoom import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditEvents import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditPresenter -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.test.AN_AVATAR_URL @@ -50,7 +50,6 @@ import java.io.File @ExperimentalCoroutinesApi class RoomDetailsEditPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -108,7 +107,7 @@ class RoomDetailsEditPresenterTest { AvatarAction.Remove ) assertThat(initialState.saveButtonEnabled).isFalse() - assertThat(initialState.saveAction).isInstanceOf(Async.Uninitialized::class.java) + assertThat(initialState.saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java) } } @@ -533,7 +532,7 @@ class RoomDetailsEditPresenterTest { assertThat(room.newAvatarData).isNull() assertThat(room.removedAvatar).isFalse() - assertThat(awaitItem().saveAction).isInstanceOf(Async.Failure::class.java) + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) } } @@ -594,10 +593,10 @@ class RoomDetailsEditPresenterTest { initialState.eventSink(RoomDetailsEditEvents.Save) skipItems(2) - assertThat(awaitItem().saveAction).isInstanceOf(Async.Failure::class.java) + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) initialState.eventSink(RoomDetailsEditEvents.CancelSaveChanges) - assertThat(awaitItem().saveAction).isInstanceOf(Async.Uninitialized::class.java) + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java) } } @@ -613,8 +612,8 @@ class RoomDetailsEditPresenterTest { initialState.eventSink(RoomDetailsEditEvents.Save) skipItems(1) - assertThat(awaitItem().saveAction).isInstanceOf(Async.Loading::class.java) - assertThat(awaitItem().saveAction).isInstanceOf(Async.Failure::class.java) + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Loading::class.java) + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) } } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt index 9763ed280b..f80cb773e3 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt @@ -29,14 +29,17 @@ import io.element.android.libraries.designsystem.theme.components.SearchBarResul import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.RoomMembershipState +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.libraries.usersearch.api.UserSearchResult +import io.element.android.libraries.usersearch.api.UserSearchResultState import io.element.android.libraries.usersearch.test.FakeUserRepository import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -46,7 +49,6 @@ import org.junit.Rule import org.junit.Test internal class RoomInviteMembersPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() @@ -63,7 +65,7 @@ internal class RoomInviteMembersPresenterTest { }.test { val initialState = awaitItem() - assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java) + assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) assertThat(initialState.isSearchActive).isFalse() assertThat(initialState.canInvite).isFalse() assertThat(initialState.searchQuery).isEmpty() @@ -94,7 +96,7 @@ internal class RoomInviteMembersPresenterTest { } @Test - fun `present - performs search and handles no results`() = runTest { + fun `present - performs search and handles empty result list`() = runTest { val repository = FakeUserRepository() val presenter = RoomInviteMembersPresenter( userRepository = repository, @@ -105,17 +107,18 @@ internal class RoomInviteMembersPresenterTest { presenter.present() }.test { val initialState = awaitItem() - skipItems(1) - initialState.eventSink(RoomInviteMembersEvents.UpdateSearchQuery("some query")) - skipItems(1) - assertThat(repository.providedQuery).isEqualTo("some query") - repository.emitResult(emptyList()) - skipItems(1) - - val resultState = awaitItem() - assertThat(resultState.searchResults).isInstanceOf(SearchBarResultState.NoResults::class.java) + repository.emitState(UserSearchResultState(results = emptyList(), isSearching = true)) + consumeItemsUntilPredicate { it.showSearchLoader }.last().also { state -> + assertThat(state.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) + assertThat(state.showSearchLoader).isTrue() + } + repository.emitState(results = emptyList(), isSearching = false) + consumeItemsUntilPredicate { !it.showSearchLoader }.last().also { state -> + assertThat(state.searchResults).isInstanceOf(SearchBarResultState.NoResultsFound::class.java) + assertThat(state.showSearchLoader).isFalse() + } } } @@ -137,7 +140,7 @@ internal class RoomInviteMembersPresenterTest { skipItems(1) assertThat(repository.providedQuery).isEqualTo("some query") - repository.emitResult(aMatrixUserList().map { UserSearchResult(it) }) + repository.emitStateWithUsers(users = aMatrixUserList()) skipItems(1) val resultState = awaitItem() @@ -189,7 +192,7 @@ internal class RoomInviteMembersPresenterTest { skipItems(1) assertThat(repository.providedQuery).isEqualTo("some query") - repository.emitResult(aMatrixUserList().map { UserSearchResult(it) }) + repository.emitStateWithUsers(users = aMatrixUserList()) skipItems(1) val resultState = awaitItem() @@ -250,7 +253,7 @@ internal class RoomInviteMembersPresenterTest { assertThat(repository.providedQuery).isEqualTo("some query") val unresolvedUser = UserSearchResult(aMatrixUser(id = A_USER_ID.value), isUnresolved = true) - repository.emitResult(listOf(unresolvedUser) + aMatrixUserList().map { UserSearchResult(it) }) + repository.emitState(listOf(unresolvedUser) + aMatrixUserList().map { UserSearchResult(it) }) skipItems(1) val resultState = awaitItem() @@ -318,7 +321,7 @@ internal class RoomInviteMembersPresenterTest { skipItems(1) assertThat(repository.providedQuery).isEqualTo("some query") - repository.emitResult((aMatrixUserList() + selectedUser).map { UserSearchResult(it) }) + repository.emitStateWithUsers(users = aMatrixUserList() + selectedUser) skipItems(2) val resultState = awaitItem() @@ -358,7 +361,7 @@ internal class RoomInviteMembersPresenterTest { skipItems(1) assertThat(repository.providedQuery).isEqualTo("some query") - repository.emitResult((aMatrixUserList() + selectedUser).map { UserSearchResult(it) }) + repository.emitStateWithUsers(users = aMatrixUserList() + selectedUser) skipItems(2) // And then a user is toggled @@ -381,6 +384,27 @@ internal class RoomInviteMembersPresenterTest { } } + private suspend fun FakeUserRepository.emitStateWithUsers( + users: List, + isSearching: Boolean = false + ) { + emitState( + results = users.map { UserSearchResult(it) }, + isSearching = isSearching, + ) + } + + private suspend fun FakeUserRepository.emitState( + results: List, + isSearching: Boolean = false + ) { + val state = UserSearchResultState( + results = results, + isSearching = isSearching + ) + emitState(state) + } + private fun TestScope.createDataSource( matrixRoom: MatrixRoom = aMatrixRoom().apply { givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList())) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt index 097919a5e1..a869bb5e04 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt @@ -26,7 +26,7 @@ import io.element.android.features.roomdetails.impl.members.RoomMemberListPresen import io.element.android.features.roomdetails.impl.members.aRoomMemberList import io.element.android.features.roomdetails.impl.members.aVictor import io.element.android.features.roomdetails.impl.members.aWalter -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.room.MatrixRoom @@ -42,7 +42,6 @@ import org.junit.Test @ExperimentalCoroutinesApi class RoomMemberListPresenterTests { - @get:Rule val warmUpRule = WarmUpRule() @@ -53,14 +52,14 @@ class RoomMemberListPresenterTests { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.roomMembers).isInstanceOf(Async.Loading::class.java) + assertThat(initialState.roomMembers).isInstanceOf(AsyncData.Loading::class.java) assertThat(initialState.searchQuery).isEmpty() - assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java) + assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) assertThat(initialState.isSearchActive).isFalse() val loadedState = awaitItem() - assertThat(loadedState.roomMembers).isInstanceOf(Async.Success::class.java) - assertThat((loadedState.roomMembers as Async.Success).data.invited).isEqualTo(listOf(aVictor(), aWalter())) - assertThat((loadedState.roomMembers as Async.Success).data.joined).isNotEmpty() + assertThat(loadedState.roomMembers).isInstanceOf(AsyncData.Success::class.java) + assertThat((loadedState.roomMembers as AsyncData.Success).data.invited).isEqualTo(listOf(aVictor(), aWalter())) + assertThat((loadedState.roomMembers as AsyncData.Success).data.joined).isNotEmpty() } } @@ -92,7 +91,7 @@ class RoomMemberListPresenterTests { val searchQueryUpdatedState = awaitItem() assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("something") val searchSearchResultDelivered = awaitItem() - assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.NoResults::class.java) + assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.NoResultsFound::class.java) } } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt index ac429e262d..e1f0d76ec0 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt @@ -27,9 +27,9 @@ import io.element.android.features.roomdetails.impl.members.aRoomMember import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.RoomMember @@ -45,7 +45,6 @@ import org.junit.Test @ExperimentalCoroutinesApi class RoomMemberDetailsPresenterTests { - @get:Rule val warmUpRule = WarmUpRule() @@ -68,7 +67,7 @@ class RoomMemberDetailsPresenterTests { assertThat(initialState.userId).isEqualTo(roomMember.userId.value) assertThat(initialState.userName).isEqualTo(roomMember.displayName) assertThat(initialState.avatarUrl).isEqualTo(roomMember.avatarUrl) - assertThat(initialState.isBlocked).isEqualTo(Async.Success(roomMember.isIgnored)) + assertThat(initialState.isBlocked).isEqualTo(AsyncData.Success(roomMember.isIgnored)) skipItems(1) val loadedState = awaitItem() assertThat(loadedState.userName).isEqualTo("A custom name") @@ -173,7 +172,7 @@ class RoomMemberDetailsPresenterTests { assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(A_THROWABLE) // Clear error initialState.eventSink(RoomMemberDetailsEvents.ClearBlockUserError) - assertThat(awaitItem().isBlocked).isEqualTo(Async.Success(false)) + assertThat(awaitItem().isBlocked).isEqualTo(AsyncData.Success(false)) } } @@ -204,14 +203,14 @@ class RoomMemberDetailsPresenterTests { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.startDmActionState).isInstanceOf(Async.Uninitialized::class.java) - val startDMSuccessResult = Async.Success(A_ROOM_ID) - val startDMFailureResult = Async.Failure(A_THROWABLE) + assertThat(initialState.startDmActionState).isInstanceOf(AsyncAction.Uninitialized::class.java) + val startDMSuccessResult = AsyncAction.Success(A_ROOM_ID) + val startDMFailureResult = AsyncAction.Failure(A_THROWABLE) // Failure startDMAction.givenExecuteResult(startDMFailureResult) initialState.eventSink(RoomMemberDetailsEvents.StartDM) - assertThat(awaitItem().startDmActionState).isInstanceOf(Async.Loading::class.java) + assertThat(awaitItem().startDmActionState).isInstanceOf(AsyncAction.Loading::class.java) awaitItem().also { state -> assertThat(state.startDmActionState).isEqualTo(startDMFailureResult) state.eventSink(RoomMemberDetailsEvents.ClearStartDMState) @@ -220,10 +219,10 @@ class RoomMemberDetailsPresenterTests { // Success startDMAction.givenExecuteResult(startDMSuccessResult) awaitItem().also { state -> - assertThat(state.startDmActionState).isEqualTo(Async.Uninitialized) + assertThat(state.startDmActionState).isEqualTo(AsyncAction.Uninitialized) state.eventSink(RoomMemberDetailsEvents.StartDM) } - assertThat(awaitItem().startDmActionState).isInstanceOf(Async.Loading::class.java) + assertThat(awaitItem().startDmActionState).isInstanceOf(AsyncAction.Loading::class.java) awaitItem().also { state -> assertThat(state.startDmActionState).isEqualTo(startDMSuccessResult) } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt index 41311a4086..23bd504bf8 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt @@ -57,7 +57,7 @@ class RoomNotificationSettingsPresenterTests { }.test { awaitItem().eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)) val updatedState = consumeItemsUntilPredicate { - it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY + it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY }.last() assertThat(updatedState.roomNotificationSettings.dataOrNull()?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) cancelAndIgnoreRemainingEvents() @@ -73,7 +73,7 @@ class RoomNotificationSettingsPresenterTests { }.test { notificationSettingsService.setRoomNotificationMode(A_ROOM_ID, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) val updatedState = consumeItemsUntilPredicate { - it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY + it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY }.last() assertThat(updatedState.roomNotificationSettings.dataOrNull()?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) } @@ -195,7 +195,7 @@ class RoomNotificationSettingsPresenterTests { private fun createRoomNotificationSettingsPresenter( notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(), room: FakeMatrixRoom = aMatrixRoom(notificationSettingsService = notificationSettingsService), - ): RoomNotificationSettingsPresenter{ + ): RoomNotificationSettingsPresenter { return RoomNotificationSettingsPresenter( room = room, notificationSettingsService = notificationSettingsService, diff --git a/features/roomlist/api/src/main/kotlin/io/element/android/features/roomlist/api/RoomListEntryPoint.kt b/features/roomlist/api/src/main/kotlin/io/element/android/features/roomlist/api/RoomListEntryPoint.kt index 7fe01aa64e..1b357c794c 100644 --- a/features/roomlist/api/src/main/kotlin/io/element/android/features/roomlist/api/RoomListEntryPoint.kt +++ b/features/roomlist/api/src/main/kotlin/io/element/android/features/roomlist/api/RoomListEntryPoint.kt @@ -23,7 +23,6 @@ import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.matrix.api.core.RoomId interface RoomListEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder interface NodeBuilder { fun callback(callback: Callback): NodeBuilder @@ -40,4 +39,3 @@ interface RoomListEntryPoint : FeatureEntryPoint { fun onReportBugClicked() } } - diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultRoomListEntryPoint.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultRoomListEntryPoint.kt index 25016e7a2f..be8890980e 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultRoomListEntryPoint.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultRoomListEntryPoint.kt @@ -27,13 +27,10 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultRoomListEntryPoint @Inject constructor() : RoomListEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomListEntryPoint.NodeBuilder { - val plugins = ArrayList() return object : RoomListEntryPoint.NodeBuilder { - override fun callback(callback: RoomListEntryPoint.Callback): RoomListEntryPoint.NodeBuilder { plugins += callback return this @@ -45,4 +42,3 @@ class DefaultRoomListEntryPoint @Inject constructor() : RoomListEntryPoint { } } } - diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/InvitesEntryPointView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/InvitesEntryPointView.kt index d00ac783dc..ea2b5d71a3 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/InvitesEntryPointView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/InvitesEntryPointView.kt @@ -33,11 +33,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom -import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.ui.strings.CommonStrings @Composable diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt index 04eb6164a4..1d2cf2c910 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt @@ -95,7 +95,14 @@ private fun RoomListModalBottomSheetContent( style = ListItemStyle.Primary, ) ListItem( - headlineContent = { Text(text = stringResource(id = CommonStrings.action_leave_room)) }, + headlineContent = { + val leaveText = stringResource(id = if (contextMenu.isDm) { + CommonStrings.action_leave_conversation + } else { + CommonStrings.action_leave_room + }) + Text(text = leaveText) + }, modifier = Modifier.clickable { onLeaveRoomClicked(contextMenu.roomId) }, leadingContent = ListItemContent.Icon( iconSource = IconSource.Vector( @@ -117,7 +124,22 @@ internal fun RoomListModalBottomSheetContentPreview() = ElementPreview { RoomListModalBottomSheetContent( contextMenu = RoomListState.ContextMenu.Shown( roomId = RoomId(value = "!aRoom:aDomain"), - roomName = "aRoom" + roomName = "aRoom", + isDm = false, + ), + onRoomSettingsClicked = {}, + onLeaveRoomClicked = {} + ) +} + +@PreviewsDayNight +@Composable +internal fun RoomListModalBottomSheetContentForDmPreview() = ElementPreview { + RoomListModalBottomSheetContent( + contextMenu = RoomListState.ContextMenu.Shown( + roomId = RoomId(value = "!aRoom:aDomain"), + roomName = "aRoom", + isDm = true, ), onRoomSettingsClicked = {}, onLeaveRoomClicked = {} diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListNode.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListNode.kt index 6e1bbd64d3..f505020151 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListNode.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListNode.kt @@ -44,7 +44,6 @@ class RoomListNode @AssistedInject constructor( private val inviteFriendsUseCase: InviteFriendsUseCase, private val analyticsService: AnalyticsService, ) : Node(buildContext, plugins = plugins) { - init { lifecycle.subscribe( onResume = { diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index a587af0692..8ab1bde3f4 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -62,7 +62,6 @@ class RoomListPresenter @Inject constructor( private val featureFlagService: FeatureFlagService, private val indicatorService: IndicatorService, ) : Presenter { - @Composable override fun present(): RoomListState { val leaveRoomState = leaveRoomPresenter.present() @@ -120,7 +119,8 @@ class RoomListPresenter @Inject constructor( is RoomListEvents.ShowContextMenu -> { contextMenu = RoomListState.ContextMenu.Shown( roomId = event.roomListRoomSummary.roomId, - roomName = event.roomListRoomSummary.name + roomName = event.roomListRoomSummary.name, + isDm = event.roomListRoomSummary.isDm, ) } is RoomListEvents.HideContextMenu -> contextMenu = RoomListState.ContextMenu.Hidden diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt index b0c87b88aa..7003da36e5 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt @@ -46,6 +46,7 @@ data class RoomListState( data class Shown( val roomId: RoomId, val roomName: String, + val isDm: Boolean, ) : ContextMenu } } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt index 6610136f5d..bf18bc60cc 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt @@ -43,7 +43,9 @@ open class RoomListStateProvider : PreviewParameterProvider { aRoomListState().copy(displaySearchResults = true), aRoomListState().copy( contextMenu = RoomListState.ContextMenu.Shown( - roomId = RoomId("!aRoom:aDomain"), roomName = "A nice room name" + roomId = RoomId("!aRoom:aDomain"), + roomName = "A nice room name", + isDm = false, ) ), aRoomListState().copy(displayRecoveryKeyPrompt = true), diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt index 42192a3376..0a9f4f523b 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt @@ -18,7 +18,6 @@ package io.element.android.features.roomlist.impl import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize @@ -123,7 +122,7 @@ fun RoomListView( } } -@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun RoomListContent( state: RoomListState, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt index eee2868378..e6aae4da6e 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt @@ -180,10 +180,14 @@ private fun DefaultRoomListTopBar( }, blurSize = DpSize(avatarBloomSize, avatarBloomSize), offset = DpOffset(24.dp, 24.dp + statusBarPadding), - clipToSize = if (appBarHeight > 0) DpSize( - avatarBloomSize, - appBarHeight.toDp() - ) else DpSize.Unspecified, + clipToSize = if (appBarHeight > 0) { + DpSize( + avatarBloomSize, + appBarHeight.toDp() + ) + } else { + DpSize.Unspecified + }, bottomSoftEdgeColor = ElementTheme.materialColors.background, bottomSoftEdgeAlpha = 1f - collapsedFraction, alpha = if (areSearchResultsDisplayed) 0f else 1f, @@ -225,7 +229,7 @@ private fun DefaultRoomListTopBar( contentDescription = stringResource(CommonStrings.action_search), ) } - if (RoomListConfig.hasDropdownMenu) { + if (RoomListConfig.HAS_DROP_DOWN_MENU) { var showMenu by remember { mutableStateOf(false) } IconButton( onClick = { showMenu = !showMenu } @@ -239,7 +243,7 @@ private fun DefaultRoomListTopBar( expanded = showMenu, onDismissRequest = { showMenu = false } ) { - if (RoomListConfig.showInviteMenuItem) { + if (RoomListConfig.SHOW_INVITE_MENU_ITEM) { DropdownMenuItem( onClick = { showMenu = false @@ -255,7 +259,7 @@ private fun DefaultRoomListTopBar( } ) } - if (RoomListConfig.showReportProblemMenuItem) { + if (RoomListConfig.SHOW_REPORT_PROBLEM_MENU_ITEM) { DropdownMenuItem( onClick = { showMenu = false diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryPlaceholderRow.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryPlaceholderRow.kt index ae9aed918c..7181afea7a 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryPlaceholderRow.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryPlaceholderRow.kt @@ -31,12 +31,12 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.placeholderBackground -import io.element.android.compound.theme.ElementTheme /** * https://www.figma.com/file/0MMNu7cTOzLOlWb7ctTkv3/Element-X?node-id=6547%3A147623 diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSource.kt index dc89d163c3..cce14f316a 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSource.kt @@ -40,7 +40,6 @@ class DefaultInviteStateDataSource @Inject constructor( private val seenInvitesStore: SeenInvitesStore, private val coroutineDispatchers: CoroutineDispatchers, ) : InviteStateDataSource { - @Composable override fun inviteState(): InvitesState { val invites by client diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/InviteStateDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/InviteStateDataSource.kt index f44ec1ea83..866bed1efd 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/InviteStateDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/InviteStateDataSource.kt @@ -20,8 +20,6 @@ import androidx.compose.runtime.Composable import io.element.android.features.roomlist.impl.InvitesState interface InviteStateDataSource { - @Composable fun inviteState(): InvitesState - } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt index e337fe5ad3..8db08dac39 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt @@ -162,6 +162,7 @@ class RoomListDataSource @Inject constructor( avatarData = avatarData, notificationMode = roomSummary.details.notificationMode, hasOngoingCall = roomSummary.details.hasOngoingCall, + isDm = roomSummary.details.isDm, ) } null -> null diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt index cfaf9b8321..ecb3aa6a96 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt @@ -34,4 +34,5 @@ data class RoomListRoomSummary( val isPlaceholder: Boolean = false, val notificationMode: RoomNotificationMode? = null, val hasOngoingCall: Boolean = false, + val isDm: Boolean = false, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryPlaceholders.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryPlaceholders.kt index 111217d7a2..f5976d7d1c 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryPlaceholders.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryPlaceholders.kt @@ -21,7 +21,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.core.RoomId object RoomListRoomSummaryPlaceholders { - fun create(id: String): RoomListRoomSummary { return RoomListRoomSummary( id = id, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchResultView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchResultView.kt index 668c3b96bd..e05f0a3ac5 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchResultView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchResultView.kt @@ -20,7 +20,6 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -98,7 +97,7 @@ internal fun RoomListSearchResultView( } } -@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun RoomListSearchResultContent( state: RoomListState, diff --git a/features/roomlist/impl/src/main/res/values-es/translations.xml b/features/roomlist/impl/src/main/res/values-es/translations.xml index 899b1e2cac..c3f8b23398 100644 --- a/features/roomlist/impl/src/main/res/values-es/translations.xml +++ b/features/roomlist/impl/src/main/res/values-es/translations.xml @@ -1,7 +1,11 @@ + "La copia de seguridad del chat no está sincronizada en este momento. Debes confirmar tu clave de recuperación para mantener el acceso a la copia de seguridad del chat." + "Confirma tu clave de recuperación" "Crear una nueva conversación o sala" + "Empieza enviando un mensaje a alguien." + "Aún no hay chats." "Todos los chats" "Parece que estás usando un nuevo dispositivo. Verifica que eres tú para acceder a tus mensajes cifrados." - "Accede a tu historial de mensajes" + "Verifica que eres tú" diff --git a/features/roomlist/impl/src/main/res/values-it/translations.xml b/features/roomlist/impl/src/main/res/values-it/translations.xml index cbe93e52d9..2d186453a9 100644 --- a/features/roomlist/impl/src/main/res/values-it/translations.xml +++ b/features/roomlist/impl/src/main/res/values-it/translations.xml @@ -1,7 +1,11 @@ + "Il backup della chat non è attualmente sincronizzato. Devi confermare la chiave di recupero per mantenere l\'accesso al backup della chat." + "Conferma la chiave di recupero" "Crea una nuova conversazione o stanza" + "Inizia inviando un messaggio a qualcuno." + "Ancora nessuna chat." "Tutte le conversazioni" - "Sembra che tu stia utilizzando un nuovo dispositivo. Verifica di essere tu per accedere ai tuoi messaggi crittografati." - "Accedi alla cronologia dei messaggi" + "Sembra che tu stia usando un nuovo dispositivo. Verificati con un altro dispositivo per accedere ai tuoi messaggi cifrati." + "Verifica che sei tu" diff --git a/features/roomlist/impl/src/main/res/values-ru/translations.xml b/features/roomlist/impl/src/main/res/values-ru/translations.xml index caba3f6dfe..261feda31d 100644 --- a/features/roomlist/impl/src/main/res/values-ru/translations.xml +++ b/features/roomlist/impl/src/main/res/values-ru/translations.xml @@ -6,6 +6,6 @@ "Начните переписку с отправки сообщения." "Пока нет доступных чатов." "Все чаты" - "Похоже, вы используете новое устройство. Чтобы получить доступ к зашифрованным сообщениям в дальнейшем, проверьте их на другом устройстве." + "Похоже, вы используете новое устройство. Чтобы получить доступ к зашифрованным сообщениям пройдите верификацию с другим устройством." "Подтвердите, что это вы" diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index 4573456762..f56379f186 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -71,7 +71,6 @@ import org.junit.Rule import org.junit.Test class RoomListPresenterTests { - @get:Rule val warmUpRule = WarmUpRule() @@ -322,7 +321,7 @@ class RoomListPresenterTests { val shownState = awaitItem() assertThat(shownState.contextMenu) - .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name)) + .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name, false)) scope.cancel() } } @@ -342,7 +341,7 @@ class RoomListPresenterTests { val shownState = awaitItem() assertThat(shownState.contextMenu) - .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name)) + .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name, false)) shownState.eventSink(RoomListEvents.HideContextMenu) val hiddenState = awaitItem() diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSourceTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSourceTest.kt index fc41e48b53..0ff0f02d32 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSourceTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSourceTest.kt @@ -32,7 +32,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test internal class DefaultInviteStateDataSourceTest { - @Test fun `emits NoInvites state if invites list is empty`() = runTest { val roomListService = FakeRoomListService() diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/FakeInviteDataSource.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/FakeInviteDataSource.kt index 7ea2d7821d..49f4a65f7f 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/FakeInviteDataSource.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/FakeInviteDataSource.kt @@ -25,7 +25,6 @@ import kotlinx.coroutines.flow.flowOf class FakeInviteDataSource( private val flow: Flow = flowOf() ) : InviteStateDataSource { - @Composable override fun inviteState(): InvitesState { val state = flow.collectAsState(initial = InvitesState.NoInvites) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/LoggerTag.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/LoggerTag.kt index b8b56f5ace..49497f1a9a 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/LoggerTag.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/LoggerTag.kt @@ -22,4 +22,3 @@ private val loggerTag = LoggerTag("SecureBackup") val loggerTagRoot = LoggerTag("Root", loggerTag) val loggerTagSetup = LoggerTag("Setup", loggerTag) val loggerTagDisable = LoggerTag("Disable", loggerTag) - diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt index bc635e5e98..62eecd09c2 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt @@ -50,7 +50,6 @@ class SecureBackupFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins ) { - sealed interface NavTarget : Parcelable { @Parcelize data object Root : NavTarget diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableEvents.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableEvents.kt index 4cda13f7af..2406265af6 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableEvents.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableEvents.kt @@ -17,6 +17,6 @@ package io.element.android.features.securebackup.impl.disable sealed interface SecureBackupDisableEvents { - data class DisableBackup(val force: Boolean) : SecureBackupDisableEvents + data object DisableBackup : SecureBackupDisableEvents data object DismissDialogs : SecureBackupDisableEvents } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableNode.kt index 6e3d69fe7f..3fd6559858 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableNode.kt @@ -32,7 +32,6 @@ class SecureBackupDisableNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: SecureBackupDisablePresenter, ) : Node(buildContext, plugins = plugins) { - @Composable override fun View(modifier: Modifier) { val state = presenter.present() diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt index 60b4691661..502fc45084 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt @@ -23,9 +23,8 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import io.element.android.features.securebackup.impl.loggerTagDisable -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.meta.BuildMeta @@ -39,25 +38,21 @@ class SecureBackupDisablePresenter @Inject constructor( private val encryptionService: EncryptionService, private val buildMeta: BuildMeta, ) : Presenter { - @Composable override fun present(): SecureBackupDisableState { val backupState by encryptionService.backupStateStateFlow.collectAsState() Timber.tag(loggerTagDisable.value).d("backupState: $backupState") - val disableAction = remember { mutableStateOf>(Async.Uninitialized) } + val disableAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val coroutineScope = rememberCoroutineScope() - var showDialog by remember { mutableStateOf(false) } fun handleEvents(event: SecureBackupDisableEvents) { when (event) { - is SecureBackupDisableEvents.DisableBackup -> if (event.force) { - showDialog = false + is SecureBackupDisableEvents.DisableBackup -> if (disableAction.value.isConfirming()) { coroutineScope.disableBackup(disableAction) } else { - showDialog = true + disableAction.value = AsyncAction.Confirming } SecureBackupDisableEvents.DismissDialogs -> { - showDialog = false - disableAction.value = Async.Uninitialized + disableAction.value = AsyncAction.Uninitialized } } } @@ -65,13 +60,12 @@ class SecureBackupDisablePresenter @Inject constructor( return SecureBackupDisableState( backupState = backupState, disableAction = disableAction.value, - showConfirmationDialog = showDialog, appName = buildMeta.applicationName, eventSink = ::handleEvents ) } - private fun CoroutineScope.disableBackup(disableAction: MutableState>) = launch { + private fun CoroutineScope.disableBackup(disableAction: MutableState>) = launch { suspend { Timber.tag(loggerTagDisable.value).d("Calling encryptionService.disableRecovery()") encryptionService.disableRecovery().getOrThrow() diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableState.kt index 41e39c398e..4875ef8807 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableState.kt @@ -16,13 +16,12 @@ package io.element.android.features.securebackup.impl.disable -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.encryption.BackupState data class SecureBackupDisableState( val backupState: BackupState, - val disableAction: Async, - val showConfirmationDialog: Boolean, + val disableAction: AsyncAction, val appName: String, val eventSink: (SecureBackupDisableEvents) -> Unit ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableStateProvider.kt index 8c22e36968..036ed12102 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableStateProvider.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableStateProvider.kt @@ -17,28 +17,26 @@ package io.element.android.features.securebackup.impl.disable import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.encryption.BackupState open class SecureBackupDisableStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aSecureBackupDisableState(), - aSecureBackupDisableState(showConfirmationDialog = true), - aSecureBackupDisableState(disableAction = Async.Loading()), - aSecureBackupDisableState(disableAction = Async.Failure(Exception("Failed to disable"))), + aSecureBackupDisableState(disableAction = AsyncAction.Confirming), + aSecureBackupDisableState(disableAction = AsyncAction.Loading), + aSecureBackupDisableState(disableAction = AsyncAction.Failure(Exception("Failed to disable"))), // Add other states here ) } fun aSecureBackupDisableState( backupState: BackupState = BackupState.UNKNOWN, - disableAction: Async = Async.Uninitialized, - showConfirmationDialog: Boolean = false, + disableAction: AsyncAction = AsyncAction.Uninitialized, ) = SecureBackupDisableState( backupState = backupState, disableAction = disableAction, - showConfirmationDialog = showConfirmationDialog, appName = "Element", eventSink = {} ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt index 4f515fd841..21da455794 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt @@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource @@ -34,10 +33,9 @@ import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.securebackup.impl.R -import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog -import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button @@ -52,11 +50,6 @@ fun SecureBackupDisableView( onBackClicked: () -> Unit, modifier: Modifier = Modifier, ) { - LaunchedEffect(state.disableAction) { - if (state.disableAction is Async.Success) { - onDone() - } - } FlowStepPage( modifier = modifier, onBackClicked = onBackClicked, @@ -67,17 +60,19 @@ fun SecureBackupDisableView( buttons = { Buttons(state = state) }, ) - if (state.showConfirmationDialog) { - SecureBackupDisableConfirmationDialog( - onConfirm = { state.eventSink.invoke(SecureBackupDisableEvents.DisableBackup(force = true)) }, - onDismiss = { state.eventSink.invoke(SecureBackupDisableEvents.DismissDialogs) }, - ) - } else if (state.disableAction is Async.Failure) { - ErrorDialog( - content = state.disableAction.error.let { it.message ?: it.toString() }, - onDismiss = { state.eventSink.invoke(SecureBackupDisableEvents.DismissDialogs) }, - ) - } + AsyncActionView( + async = state.disableAction, + confirmationDialog = { + SecureBackupDisableConfirmationDialog( + onConfirm = { state.eventSink.invoke(SecureBackupDisableEvents.DisableBackup) }, + onDismiss = { state.eventSink.invoke(SecureBackupDisableEvents.DismissDialogs) }, + ) + }, + progressDialog = {}, + errorMessage = { it.message ?: it.toString() }, + onErrorDismiss = { state.eventSink.invoke(SecureBackupDisableEvents.DismissDialogs) }, + onSuccess = { onDone() }, + ) } @Composable @@ -101,7 +96,7 @@ private fun ColumnScope.Buttons( showProgress = state.disableAction.isLoading(), destructive = true, modifier = Modifier.fillMaxWidth(), - onClick = { state.eventSink.invoke(SecureBackupDisableEvents.DisableBackup(force = false)) } + onClick = { state.eventSink.invoke(SecureBackupDisableEvents.DisableBackup) } ) } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableNode.kt index 9966dd0df0..804af27493 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableNode.kt @@ -32,7 +32,6 @@ class SecureBackupEnableNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: SecureBackupEnablePresenter, ) : Node(buildContext, plugins = plugins) { - @Composable override fun View(modifier: Modifier) { val state = presenter.present() diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenter.kt index dfceb16dea..c12ea2d24e 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenter.kt @@ -22,7 +22,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import io.element.android.features.securebackup.impl.loggerTagDisable -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.matrix.api.encryption.EncryptionService @@ -34,17 +34,16 @@ import javax.inject.Inject class SecureBackupEnablePresenter @Inject constructor( private val encryptionService: EncryptionService, ) : Presenter { - @Composable override fun present(): SecureBackupEnableState { - val enableAction = remember { mutableStateOf>(Async.Uninitialized) } + val enableAction = remember { mutableStateOf>(AsyncData.Uninitialized) } val coroutineScope = rememberCoroutineScope() fun handleEvents(event: SecureBackupEnableEvents) { when (event) { is SecureBackupEnableEvents.EnableBackup -> coroutineScope.enableBackup(enableAction) SecureBackupEnableEvents.DismissDialog -> { - enableAction.value = Async.Uninitialized + enableAction.value = AsyncData.Uninitialized } } } @@ -55,7 +54,7 @@ class SecureBackupEnablePresenter @Inject constructor( ) } - private fun CoroutineScope.enableBackup(action: MutableState>) = launch { + private fun CoroutineScope.enableBackup(action: MutableState>) = launch { suspend { Timber.tag(loggerTagDisable.value).d("Calling encryptionService.enableBackups()") encryptionService.enableBackups().getOrThrow() diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableState.kt index 7c3aaf839b..bd81004f9c 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableState.kt @@ -16,9 +16,9 @@ package io.element.android.features.securebackup.impl.enable -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData data class SecureBackupEnableState( - val enableAction: Async, + val enableAction: AsyncData, val eventSink: (SecureBackupEnableEvents) -> Unit ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableStateProvider.kt index 494c45b5b4..1029278c05 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableStateProvider.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableStateProvider.kt @@ -17,20 +17,20 @@ package io.element.android.features.securebackup.impl.enable import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData open class SecureBackupEnableStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aSecureBackupEnableState(), - aSecureBackupEnableState(enableAction = Async.Loading()), - aSecureBackupEnableState(enableAction = Async.Failure(Exception("Failed to enable"))), + aSecureBackupEnableState(enableAction = AsyncData.Loading()), + aSecureBackupEnableState(enableAction = AsyncData.Failure(Exception("Failed to enable"))), // Add other states here ) } fun aSecureBackupEnableState( - enableAction: Async = Async.Uninitialized, + enableAction: AsyncData = AsyncData.Uninitialized, ) = SecureBackupEnableState( enableAction = enableAction, eventSink = {} diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableView.kt index d97fc735d8..37a46a1408 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableView.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.features.securebackup.impl.R -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.preview.ElementPreview @@ -42,7 +42,7 @@ fun SecureBackupEnableView( modifier: Modifier = Modifier, ) { LaunchedEffect(state.enableAction) { - if (state.enableAction is Async.Success) { + if (state.enableAction is AsyncData.Success) { onDone() } } @@ -53,7 +53,7 @@ fun SecureBackupEnableView( iconVector = ImageVector.vectorResource(CommonDrawables.ic_key), buttons = { Buttons(state = state) } ) - if (state.enableAction is Async.Failure) { + if (state.enableAction is AsyncData.Failure) { ErrorDialog( content = state.enableAction.error.let { it.message ?: it.toString() }, onDismiss = { state.eventSink.invoke(SecureBackupEnableEvents.DismissDialog) }, diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyNode.kt index 79576da8a1..8c6ae282fa 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyNode.kt @@ -39,7 +39,6 @@ class SecureBackupEnterRecoveryKeyNode @AssistedInject constructor( private val presenter: SecureBackupEnterRecoveryKeyPresenter, private val snackbarDispatcher: SnackbarDispatcher, ) : Node(buildContext, plugins = plugins) { - @Composable override fun View(modifier: Modifier) { val coroutineScope = rememberCoroutineScope() diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt index 9207dbef79..9706b47d7b 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt @@ -27,7 +27,7 @@ import androidx.compose.runtime.setValue import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyUserStory import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState import io.element.android.features.securebackup.impl.tools.RecoveryKeyTools -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.matrix.api.encryption.EncryptionService @@ -39,21 +39,20 @@ class SecureBackupEnterRecoveryKeyPresenter @Inject constructor( private val encryptionService: EncryptionService, private val recoveryKeyTools: RecoveryKeyTools, ) : Presenter { - @Composable override fun present(): SecureBackupEnterRecoveryKeyState { val coroutineScope = rememberCoroutineScope() var recoveryKey by rememberSaveable { mutableStateOf("") } - val submitAction = remember { - mutableStateOf>(Async.Uninitialized) + val submitAction: MutableState> = remember { + mutableStateOf(AsyncAction.Uninitialized) } fun handleEvents(event: SecureBackupEnterRecoveryKeyEvents) { when (event) { SecureBackupEnterRecoveryKeyEvents.ClearDialog -> { - submitAction.value = Async.Uninitialized + submitAction.value = AsyncAction.Uninitialized } is SecureBackupEnterRecoveryKeyEvents.OnRecoveryKeyChange -> { val previousRecoveryKey = recoveryKey @@ -86,7 +85,7 @@ class SecureBackupEnterRecoveryKeyPresenter @Inject constructor( private fun CoroutineScope.submitRecoveryKey( recoveryKey: String, - action: MutableState> + action: MutableState> ) = launch { suspend { encryptionService.recover(recoveryKey).getOrThrow() diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyState.kt index c9a631c11c..c064a3fc13 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyState.kt @@ -17,12 +17,12 @@ package io.element.android.features.securebackup.impl.enter import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction // Do not use default value, so no member get forgotten in the presenters. data class SecureBackupEnterRecoveryKeyState( val recoveryKeyViewState: RecoveryKeyViewState, val isSubmitEnabled: Boolean, - val submitAction: Async, + val submitAction: AsyncAction, val eventSink: (SecureBackupEnterRecoveryKeyEvents) -> Unit ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyStateProvider.kt index 375eafef73..2869b9a281 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyStateProvider.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyStateProvider.kt @@ -20,22 +20,22 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyUserStory import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState import io.element.android.features.securebackup.impl.setup.views.aFormattedRecoveryKey -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction open class SecureBackupEnterRecoveryKeyStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aSecureBackupEnterRecoveryKeyState(recoveryKey = ""), aSecureBackupEnterRecoveryKeyState(), - aSecureBackupEnterRecoveryKeyState(submitAction = Async.Loading()), - aSecureBackupEnterRecoveryKeyState(submitAction = Async.Failure(Exception("A Failure"))), + aSecureBackupEnterRecoveryKeyState(submitAction = AsyncAction.Loading), + aSecureBackupEnterRecoveryKeyState(submitAction = AsyncAction.Failure(Exception("A Failure"))), ) } fun aSecureBackupEnterRecoveryKeyState( recoveryKey: String = aFormattedRecoveryKey(), isSubmitEnabled: Boolean = recoveryKey.isNotEmpty(), - submitAction: Async = Async.Uninitialized, + submitAction: AsyncAction = AsyncAction.Uninitialized, ) = SecureBackupEnterRecoveryKeyState( recoveryKeyViewState = RecoveryKeyViewState( recoveryKeyUserStory = RecoveryKeyUserStory.Enter, diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt index d5eda2c15a..b590219a64 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt @@ -29,7 +29,7 @@ import androidx.compose.ui.unit.dp import io.element.android.features.securebackup.impl.R import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyView import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage -import io.element.android.libraries.designsystem.components.async.AsyncView +import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button @@ -43,10 +43,10 @@ fun SecureBackupEnterRecoveryKeyView( onBackClicked: () -> Unit, modifier: Modifier = Modifier, ) { - AsyncView( + AsyncActionView( async = state.submitAction, onSuccess = { onDone() }, - showProgressDialog = false, + progressDialog = { }, errorTitle = { stringResource(id = R.string.screen_recovery_key_confirm_error_title) }, errorMessage = { stringResource(id = R.string.screen_recovery_key_confirm_error_content) }, onErrorDismiss = { state.eventSink(SecureBackupEnterRecoveryKeyEvents.ClearDialog) }, diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootNode.kt index 0723568881..2fa037860e 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootNode.kt @@ -39,7 +39,6 @@ class SecureBackupRootNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins ) { - interface Callback : Plugin { fun onSetupClicked() fun onChangeClicked() @@ -69,7 +68,7 @@ class SecureBackupRootNode @AssistedInject constructor( } private fun onLearnMoreClicked(uriHandler: UriHandler) { - uriHandler.openUri(SecureBackupConfig.LearnMoreUrl) + uriHandler.openUri(SecureBackupConfig.LEARN_MORE_URL) } @Composable diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt index 9036200ac9..789506b74f 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt @@ -25,7 +25,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import io.element.android.features.securebackup.impl.loggerTagRoot -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.meta.BuildMeta @@ -43,7 +43,6 @@ class SecureBackupRootPresenter @Inject constructor( private val buildMeta: BuildMeta, private val snackbarDispatcher: SnackbarDispatcher, ) : Presenter { - @Composable override fun present(): SecureBackupRootState { val localCoroutineScope = rememberCoroutineScope() @@ -55,7 +54,7 @@ class SecureBackupRootPresenter @Inject constructor( Timber.tag(loggerTagRoot.value).d("backupState: $backupState") Timber.tag(loggerTagRoot.value).d("recoveryState: $recoveryState") - val doesBackupExistOnServerAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + val doesBackupExistOnServerAction: MutableState> = remember { mutableStateOf(AsyncData.Uninitialized) } LaunchedEffect(backupState) { if (backupState == BackupState.UNKNOWN) { @@ -79,7 +78,7 @@ class SecureBackupRootPresenter @Inject constructor( ) } - private fun CoroutineScope.getKeyBackupStatus(action: MutableState>) = launch { + private fun CoroutineScope.getKeyBackupStatus(action: MutableState>) = launch { suspend { encryptionService.doesBackupExistOnServer().getOrThrow() }.runCatchingUpdatingState(action) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt index e2c6c1154c..0d57ebbf46 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt @@ -16,14 +16,14 @@ package io.element.android.features.securebackup.impl.root -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.RecoveryState data class SecureBackupRootState( val backupState: BackupState, - val doesBackupExistOnServer: Async, + val doesBackupExistOnServer: AsyncData, val recoveryState: RecoveryState, val appName: String, val snackbarMessage: SnackbarMessage?, diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt index ae8b2aa63b..beb54101ff 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt @@ -17,7 +17,7 @@ package io.element.android.features.securebackup.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.RecoveryState @@ -25,10 +25,10 @@ import io.element.android.libraries.matrix.api.encryption.RecoveryState open class SecureBackupRootStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = Async.Uninitialized), - aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = Async.Success(true)), - aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = Async.Success(false)), - aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = Async.Failure(Exception("An error"))), + aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = AsyncData.Uninitialized), + aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = AsyncData.Success(true)), + aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = AsyncData.Success(false)), + aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = AsyncData.Failure(Exception("An error"))), aSecureBackupRootState(backupState = BackupState.ENABLED), aSecureBackupRootState(recoveryState = RecoveryState.UNKNOWN), aSecureBackupRootState(recoveryState = RecoveryState.ENABLED), @@ -40,7 +40,7 @@ open class SecureBackupRootStateProvider : PreviewParameterProvider = Async.Uninitialized, + doesBackupExistOnServer: AsyncData = AsyncData.Uninitialized, recoveryState: RecoveryState = RecoveryState.UNKNOWN, snackbarMessage: SnackbarMessage? = null, ) = SecureBackupRootState( diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt index d4033f1297..754beb6d5f 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt @@ -23,8 +23,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.compound.theme.ElementTheme import io.element.android.features.securebackup.impl.R -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.async.AsyncLoading import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.PreferenceDivider @@ -41,7 +42,6 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.RecoveryState -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -82,7 +82,7 @@ fun SecureBackupRootView( BackupState.WAITING_FOR_SYNC -> Unit BackupState.UNKNOWN -> { when (state.doesBackupExistOnServer) { - is Async.Success -> when (state.doesBackupExistOnServer.data) { + is AsyncData.Success -> when (state.doesBackupExistOnServer.data) { true -> { PreferenceText( title = stringResource(id = R.string.screen_chat_backup_key_backup_action_disable), @@ -97,8 +97,8 @@ fun SecureBackupRootView( ) } } - is Async.Loading, - Async.Uninitialized -> { + is AsyncData.Loading, + AsyncData.Uninitialized -> { ListItem(headlineContent = { Row( modifier = Modifier.fillMaxWidth(), @@ -108,7 +108,7 @@ fun SecureBackupRootView( } }) } - is Async.Failure -> { + is AsyncData.Failure -> { ListItem( headlineContent = { Text( diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupNode.kt index 84cee69110..495b4a38c1 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupNode.kt @@ -41,7 +41,6 @@ class SecureBackupSetupNode @AssistedInject constructor( presenterFactory: SecureBackupSetupPresenter.Factory, private val snackbarDispatcher: SnackbarDispatcher, ) : Node(buildContext, plugins = plugins) { - data class Inputs( val isChangeRecoveryKeyUserStory: Boolean, ) : NodeInputs @@ -68,10 +67,11 @@ class SecureBackupSetupNode @AssistedInject constructor( private fun CoroutineScope.postSuccessSnackbar() = launch { snackbarDispatcher.post( SnackbarMessage( - messageResId = if (inputs.isChangeRecoveryKeyUserStory) + messageResId = if (inputs.isChangeRecoveryKeyUserStory) { R.string.screen_recovery_key_change_success - else + } else { R.string.screen_recovery_key_setup_success + } ) ) } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenter.kt index eab2e0450d..d929a92f52 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenter.kt @@ -46,7 +46,6 @@ class SecureBackupSetupPresenter @AssistedInject constructor( private val stateMachine: SecureBackupSetupStateMachine, private val encryptionService: EncryptionService, ) : Presenter { - @AssistedFactory interface Factory { fun create(isChangeRecoveryKeyUserStory: Boolean): SecureBackupSetupPresenter diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupStateMachine.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupStateMachine.kt index 7e2c633a61..441076264d 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupStateMachine.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupStateMachine.kt @@ -24,11 +24,9 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import javax.inject.Inject import com.freeletics.flowredux.dsl.State as MachineState -class SecureBackupSetupStateMachine @Inject constructor( -) : FlowReduxStateMachine( +class SecureBackupSetupStateMachine @Inject constructor() : FlowReduxStateMachine( initialState = State.Initial ) { - init { spec { inState { diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt index abdf8e9563..c62072b4a5 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt @@ -80,10 +80,11 @@ private fun SecureBackupSetupState.canGoBack(): Boolean { private fun title(state: SecureBackupSetupState): String { return when (state.setupState) { SetupState.Init, - SetupState.Creating -> if (state.isChangeRecoveryKeyUserStory) + SetupState.Creating -> if (state.isChangeRecoveryKeyUserStory) { stringResource(id = R.string.screen_recovery_key_change_title) - else + } else { stringResource(id = R.string.screen_recovery_key_setup_title) + } is SetupState.Created, is SetupState.CreatedAndSaved -> stringResource(id = R.string.screen_recovery_key_save_title) @@ -94,10 +95,11 @@ private fun title(state: SecureBackupSetupState): String { private fun subtitle(state: SecureBackupSetupState): String { return when (state.setupState) { SetupState.Init, - SetupState.Creating -> if (state.isChangeRecoveryKeyUserStory) + SetupState.Creating -> if (state.isChangeRecoveryKeyUserStory) { stringResource(id = R.string.screen_recovery_key_change_description) - else + } else { stringResource(id = R.string.screen_recovery_key_setup_description) + } is SetupState.Created, is SetupState.CreatedAndSaved -> stringResource(id = R.string.screen_recovery_key_save_description) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt index 34b731b1ab..5cb8f0739b 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt @@ -41,6 +41,8 @@ import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.securebackup.impl.R import io.element.android.features.securebackup.impl.tools.RecoveryKeyVisualTransformation import io.element.android.libraries.designsystem.modifiers.clickableIfNotNull @@ -52,8 +54,6 @@ import io.element.android.libraries.designsystem.theme.components.OutlinedTextFi import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.autofill import io.element.android.libraries.designsystem.utils.CommonDrawables -import io.element.android.compound.theme.ElementTheme -import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -209,10 +209,11 @@ private fun RecoveryKeyFooter(state: RecoveryKeyViewState) { ) Text( text = stringResource( - id = if (state.recoveryKeyUserStory == RecoveryKeyUserStory.Change) + id = if (state.recoveryKeyUserStory == RecoveryKeyUserStory.Change) { R.string.screen_recovery_key_change_generate_key_description - else + } else { R.string.screen_recovery_key_setup_generate_key_description + } ), color = ElementTheme.colors.textSecondary, modifier = Modifier.padding(start = 8.dp), diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformation.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformation.kt index f15acaa458..d35dbf5ee9 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformation.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformation.kt @@ -37,10 +37,11 @@ class RecoveryKeyVisualTransformation : VisualTransformation { override fun originalToTransformed(offset: Int): Int { if (offset == 0) return 0 val numberOfChunks = offset / 4 - return if (offset == text.length && offset % 4 == 0) + return if (offset == text.length && offset % 4 == 0) { offset + numberOfChunks - 1 - else + } else { offset + numberOfChunks + } } override fun transformedToOriginal(offset: Int): Int { diff --git a/features/securebackup/impl/src/main/res/values-de/translations.xml b/features/securebackup/impl/src/main/res/values-de/translations.xml index 77ec0fa82a..5ec8f99e8c 100644 --- a/features/securebackup/impl/src/main/res/values-de/translations.xml +++ b/features/securebackup/impl/src/main/res/values-de/translations.xml @@ -17,6 +17,7 @@ "Du verlierst den Zugriff auf deine verschlüsselten Nachrichten, wenn du dich überall von %1$s abmeldest" "Bist du sicher, dass du das Backup ausschalten willst?" "Besorge dir einen neuen Wiederherstellungsschlüssel, wenn du deinen alten verloren hast. Nachdem du deinen Wiederherstellungsschlüssel geändert hast, funktioniert dein alter Schlüssel nicht mehr." + "Erstelle einen neuen Wiederherstellungsschlüssel" "Stelle sicher, dass du deinen Wiederherstellungsschlüssel an einem sicheren Ort aufbewahren kannst" "Wiederherstellungsschlüssel geändert" "Wiederherstellungsschlüssel ändern?" diff --git a/features/securebackup/impl/src/main/res/values-es/translations.xml b/features/securebackup/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..9dfc061a43 --- /dev/null +++ b/features/securebackup/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,44 @@ + + + "Desactivar copia de seguridad" + "Activar copia de seguridad" + "La copia de seguridad garantiza que no pierdas tu historial de mensajes. %1$s." + "Copia de seguridad" + "Cambiar la clave de recuperación" + "Confirmar clave de recuperación" + "La copia de seguridad de tus chats no está sincronizada ahora mismo." + "Configurar la clave de recuperación" + "Accede a tus mensajes cifrados si pierdes todos tus dispositivos o cierras sesión de %1$s en cualquier lugar." + "Desactivar" + "Perderás tus mensajes cifrados si cierras sesión en todos los dispositivos." + "¿Estás seguro de que quieres desactivar la copia de seguridad?" + "Desactivar la copia de seguridad eliminará la copia de seguridad de tu clave de encriptación actual y desactivará otras funciones de seguridad. En este caso:" + "No tendrás un historial de mensajes cifrados en nuevos dispositivos" + "Perderás el acceso a tus mensajes cifrados si cierras sesión %1$s en todas partes" + "¿Estás seguro de que quieres desactivar la copia de seguridad?" + "Obtén una nueva clave de recuperación si has perdido la que tenías. Después de cambiar la clave de recuperación, la anterior dejará de funcionar." + "Generar una nueva clave de recuperación" + "Asegúrate de poder guardar su clave de recuperación en un lugar seguro" + "Clave de recuperación cambiada" + "¿Cambiar la clave de recuperación?" + "Introduce tu clave de recuperación para confirmar el acceso a la copia de seguridad de tú chat." + "Por favor, inténtalo de nuevo para confirmar el acceso a tu copia de seguridad del chat." + "Clave de recuperación incorrecta" + "Introduce el código de 48 caracteres." + "Ingresar…" + "Clave de recuperación confirmada" + "Confirma tu clave de recuperación" + "Clave de recuperación copiada" + "Generando…" + "Guardar clave de recuperación" + "Anota tu clave de recuperación en un lugar seguro o guárdala en un administrador de contraseñas." + "Pulsa para copiar la clave de recuperación" + "Guardar tu clave de recuperación" + "No podrás acceder a tu nueva clave de recuperación después de este paso." + "¿Has guardado tu clave de recuperación?" + "La copia de seguridad del chat está protegida por una clave de recuperación. Si necesitas una nueva clave de recuperación después de la configuración, puedes volver a crearla seleccionando «Cambiar clave de recuperación»." + "Generar tu clave de recuperación" + "Asegúrate de que puedes guardar tu clave de recuperación en algún lugar seguro" + "Configuración de recuperación terminada" + "Configurar la recuperación" + diff --git a/features/securebackup/impl/src/main/res/values-it/translations.xml b/features/securebackup/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..2670c5b052 --- /dev/null +++ b/features/securebackup/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,44 @@ + + + "Disattiva il backup" + "Attiva il backup" + "Il backup ti garantisce di non perdere la cronologia dei messaggi. %1$s." + "Backup" + "Cambia la chiave di recupero" + "Conferma la chiave di recupero" + "Il backup della chat attualmente non è sincronizzato." + "Configura il recupero" + "Ottieni l\'accesso ai tuoi messaggi cifrati se perdi tutti i tuoi dispositivi o se sei disconnesso da %1$s ovunque." + "Disattiva" + "Perderai i tuoi messaggi cifrati se sei disconnesso da tutti i dispositivi." + "Vuoi davvero disattivare il backup?" + "La disattivazione del backup rimuoverà il backup dell\'attuale chiave crittografica e disattiverà altre funzioni di sicurezza. In questo caso:" + "Non avrai la cronologia dei messaggi cifrati su nuovi dispositivi" + "Perderai l\'accesso ai tuoi messaggi cifrati se ti sei disconnesso da %1$s ovunque" + "Vuoi davvero disattivare il backup?" + "Ottieni una nuova chiave di recupero se hai perso quella esistente. Dopo averla cambiata, quella vecchia non funzionerà più." + "Genera una nuova chiave di recupero" + "Assicurati di conservare la chiave di recupero in un posto sicuro" + "Chiave di recupero cambiata" + "Cambiare la chiave di recupero?" + "Inserisci la tua chiave di recupero per confermare l\'accesso al backup della chat." + "Riprova per confermare l\'accesso al backup della chat." + "Chiave di recupero errata" + "Inserisci il codice di 48 caratteri." + "Inserisci…" + "Chiave di recupero confermata" + "Conferma la chiave di recupero" + "Chiave di recupero copiata" + "Generazione…" + "Salva la chiave di recupero" + "Annota la chiave di recupero in un posto sicuro o salvala in un gestore di password." + "Tocca per copiare la chiave di recupero" + "Salva la tua chiave di recupero" + "Dopo questo passaggio non potrai accedere alla nuova chiave di recupero." + "Hai salvato la chiave di recupero?" + "Il backup della chat è protetto da una chiave di recupero. Se hai bisogno di una nuova chiave di recupero dopo la configurazione, puoi ricrearla selezionando \"Cambia chiave di recupero\"." + "Genera la tua chiave di recupero" + "Assicurati di conservare la chiave di recupero in un posto sicuro" + "Configurazione del recupero completata" + "Configura il recupero" + diff --git a/features/securebackup/impl/src/main/res/values-zh-rTW/translations.xml b/features/securebackup/impl/src/main/res/values-zh-rTW/translations.xml new file mode 100644 index 0000000000..27b8f2e0a1 --- /dev/null +++ b/features/securebackup/impl/src/main/res/values-zh-rTW/translations.xml @@ -0,0 +1,9 @@ + + + "關閉備份功能" + "開啟備份功能" + "備份可確保您不會遺失歷史訊息。%1$s。" + "備份" + "關閉" + "點擊以複製復原金鑰" + diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt index 8e42ee7efe..18fd9b800a 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt @@ -20,7 +20,7 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.test.core.aBuildMeta @@ -42,8 +42,7 @@ class SecureBackupDisablePresenterTest { }.test { val initialState = awaitItem() assertThat(initialState.backupState).isEqualTo(BackupState.UNKNOWN) - assertThat(initialState.disableAction).isEqualTo(Async.Uninitialized) - assertThat(initialState.showConfirmationDialog).isFalse() + assertThat(initialState.disableAction).isEqualTo(AsyncAction.Uninitialized) assertThat(initialState.appName).isEqualTo("Element") } } @@ -55,13 +54,12 @@ class SecureBackupDisablePresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.showConfirmationDialog).isFalse() - initialState.eventSink(SecureBackupDisableEvents.DisableBackup(force = false)) + initialState.eventSink(SecureBackupDisableEvents.DisableBackup) val state = awaitItem() - assertThat(state.showConfirmationDialog).isTrue() + assertThat(state.disableAction).isEqualTo(AsyncAction.Confirming) initialState.eventSink(SecureBackupDisableEvents.DismissDialogs) val finalState = awaitItem() - assertThat(finalState.showConfirmationDialog).isFalse() + assertThat(finalState.disableAction).isEqualTo(AsyncAction.Uninitialized) } } @@ -72,17 +70,15 @@ class SecureBackupDisablePresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.showConfirmationDialog).isFalse() - initialState.eventSink(SecureBackupDisableEvents.DisableBackup(force = false)) + assertThat(initialState.disableAction).isEqualTo(AsyncAction.Uninitialized) + initialState.eventSink(SecureBackupDisableEvents.DisableBackup) val state = awaitItem() - assertThat(state.showConfirmationDialog).isTrue() - initialState.eventSink(SecureBackupDisableEvents.DisableBackup(force = true)) - skipItems(1) + assertThat(state.disableAction).isEqualTo(AsyncAction.Confirming) + initialState.eventSink(SecureBackupDisableEvents.DisableBackup) val loadingState = awaitItem() - assertThat(loadingState.showConfirmationDialog).isFalse() - assertThat(loadingState.disableAction).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState.disableAction).isInstanceOf(AsyncAction.Loading::class.java) val finalState = awaitItem() - assertThat(finalState.disableAction).isEqualTo(Async.Success(Unit)) + assertThat(finalState.disableAction).isEqualTo(AsyncAction.Success(Unit)) } } @@ -98,20 +94,18 @@ class SecureBackupDisablePresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.showConfirmationDialog).isFalse() - initialState.eventSink(SecureBackupDisableEvents.DisableBackup(force = false)) + assertThat(initialState.disableAction).isEqualTo(AsyncAction.Uninitialized) + initialState.eventSink(SecureBackupDisableEvents.DisableBackup) val state = awaitItem() - assertThat(state.showConfirmationDialog).isTrue() - initialState.eventSink(SecureBackupDisableEvents.DisableBackup(force = true)) - skipItems(1) + assertThat(state.disableAction).isEqualTo(AsyncAction.Confirming) + initialState.eventSink(SecureBackupDisableEvents.DisableBackup) val loadingState = awaitItem() - assertThat(loadingState.showConfirmationDialog).isFalse() - assertThat(loadingState.disableAction).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState.disableAction).isInstanceOf(AsyncAction.Loading::class.java) val errorState = awaitItem() - assertThat(errorState.disableAction).isInstanceOf(Async.Failure::class.java) + assertThat(errorState.disableAction).isInstanceOf(AsyncAction.Failure::class.java) errorState.eventSink(SecureBackupDisableEvents.DismissDialogs) val finalState = awaitItem() - assertThat(finalState.disableAction).isEqualTo(Async.Uninitialized) + assertThat(finalState.disableAction).isEqualTo(AsyncAction.Uninitialized) } } diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenterTest.kt index 5aa223cf93..39d8ebf81f 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenterTest.kt @@ -20,8 +20,9 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.encryption.EncryptionService +import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest @@ -39,7 +40,7 @@ class SecureBackupEnablePresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.enableAction).isEqualTo(Async.Uninitialized) + assertThat(initialState.enableAction).isEqualTo(AsyncData.Uninitialized) } } @@ -52,9 +53,29 @@ class SecureBackupEnablePresenterTest { val initialState = awaitItem() initialState.eventSink(SecureBackupEnableEvents.EnableBackup) val loadingState = awaitItem() - assertThat(loadingState.enableAction).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState.enableAction).isInstanceOf(AsyncData.Loading::class.java) val finalState = awaitItem() - assertThat(finalState.enableAction).isEqualTo(Async.Success(Unit)) + assertThat(finalState.enableAction).isEqualTo(AsyncData.Success(Unit)) + } + } + + @Test + fun `present - user enable backup with error`() = runTest { + val encryptionService = FakeEncryptionService() + encryptionService.givenEnableBackupsFailure(AN_EXCEPTION) + val presenter = createPresenter(encryptionService = encryptionService) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(SecureBackupEnableEvents.EnableBackup) + val loadingState = awaitItem() + assertThat(loadingState.enableAction).isInstanceOf(AsyncData.Loading::class.java) + val errorState = awaitItem() + assertThat(errorState.enableAction).isEqualTo(AsyncData.Failure(AN_EXCEPTION)) + errorState.eventSink(SecureBackupEnableEvents.DismissDialog) + val finalState = awaitItem() + assertThat(finalState.enableAction).isEqualTo(AsyncData.Uninitialized) } } diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt index 862cbbde72..3991fe306c 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt @@ -23,7 +23,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyUserStory import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState import io.element.android.features.securebackup.impl.tools.RecoveryKeyTools -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService @@ -44,7 +44,7 @@ class SecureBackupEnterRecoveryKeyPresenterTest { }.test { val initialState = awaitItem() assertThat(initialState.isSubmitEnabled).isFalse() - assertThat(initialState.submitAction).isEqualTo(Async.Uninitialized) + assertThat(initialState.submitAction).isEqualTo(AsyncAction.Uninitialized) assertThat(initialState.recoveryKeyViewState).isEqualTo( RecoveryKeyViewState( recoveryKeyUserStory = RecoveryKeyUserStory.Enter, @@ -76,22 +76,22 @@ class SecureBackupEnterRecoveryKeyPresenterTest { encryptionService.givenRecoverFailure(AN_EXCEPTION) withRecoveryKeyState.eventSink(SecureBackupEnterRecoveryKeyEvents.Submit) val loadingState = awaitItem() - assertThat(loadingState.submitAction).isEqualTo(Async.Loading()) + assertThat(loadingState.submitAction).isEqualTo(AsyncAction.Loading) assertThat(loadingState.isSubmitEnabled).isFalse() val errorState = awaitItem() - assertThat(errorState.submitAction).isEqualTo(Async.Failure(AN_EXCEPTION)) + assertThat(errorState.submitAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) assertThat(errorState.isSubmitEnabled).isFalse() errorState.eventSink(SecureBackupEnterRecoveryKeyEvents.ClearDialog) val clearedState = awaitItem() - assertThat(clearedState.submitAction).isEqualTo(Async.Uninitialized) + assertThat(clearedState.submitAction).isEqualTo(AsyncAction.Uninitialized) assertThat(clearedState.isSubmitEnabled).isTrue() encryptionService.givenRecoverFailure(null) clearedState.eventSink(SecureBackupEnterRecoveryKeyEvents.Submit) val loadingState2 = awaitItem() - assertThat(loadingState2.submitAction).isEqualTo(Async.Loading()) + assertThat(loadingState2.submitAction).isEqualTo(AsyncAction.Loading) assertThat(loadingState2.isSubmitEnabled).isFalse() val finalState = awaitItem() - assertThat(finalState.submitAction).isEqualTo(Async.Success(Unit)) + assertThat(finalState.submitAction).isEqualTo(AsyncAction.Success(Unit)) assertThat(finalState.isSubmitEnabled).isFalse() } } diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt index 6b88e20748..3baecd4381 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt @@ -20,7 +20,7 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.EncryptionService @@ -65,15 +65,15 @@ class SecureBackupRootPresenterTest { val initialState = awaitItem() encryptionService.givenDoesBackupExistOnServerResult(Result.failure(AN_EXCEPTION)) assertThat(initialState.backupState).isEqualTo(BackupState.UNKNOWN) - assertThat(initialState.doesBackupExistOnServer).isEqualTo(Async.Uninitialized) + assertThat(initialState.doesBackupExistOnServer).isEqualTo(AsyncData.Uninitialized) val loadingState1 = awaitItem() - assertThat(loadingState1.doesBackupExistOnServer).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState1.doesBackupExistOnServer).isInstanceOf(AsyncData.Loading::class.java) val errorState = awaitItem() - assertThat(errorState.doesBackupExistOnServer).isEqualTo(Async.Failure(AN_EXCEPTION)) + assertThat(errorState.doesBackupExistOnServer).isEqualTo(AsyncData.Failure(AN_EXCEPTION)) encryptionService.givenDoesBackupExistOnServerResult(Result.success(false)) errorState.eventSink.invoke(SecureBackupRootEvents.RetryKeyBackupState) val loadingState2 = awaitItem() - assertThat(loadingState2.doesBackupExistOnServer).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState2.doesBackupExistOnServer).isInstanceOf(AsyncData.Loading::class.java) val finalState = awaitItem() assertThat(finalState.doesBackupExistOnServer.dataOrNull()).isFalse() } diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenterTest.kt index f4071c6645..e8fd89841a 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenterTest.kt @@ -140,11 +140,11 @@ class SecureBackupSetupPresenterTest { ) ) val createdState = awaitItem() - assertThat(createdState.setupState).isEqualTo(SetupState.Created(FakeEncryptionService.fakeRecoveryKey)) + assertThat(createdState.setupState).isEqualTo(SetupState.Created(FakeEncryptionService.FAKE_RECOVERY_KEY)) assertThat(createdState.recoveryKeyViewState).isEqualTo( RecoveryKeyViewState( recoveryKeyUserStory = RecoveryKeyUserStory.Change, - formattedRecoveryKey = FakeEncryptionService.fakeRecoveryKey, + formattedRecoveryKey = FakeEncryptionService.FAKE_RECOVERY_KEY, inProgress = false, ) ) diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyToolsTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyToolsTest.kt index 4629211fc9..4f6e27993f 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyToolsTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyToolsTest.kt @@ -20,7 +20,6 @@ import com.google.common.truth.Truth.assertThat import org.junit.Test class RecoveryKeyToolsTest { - @Test fun `isRecoveryKeyFormatValid return false for invalid key`() { val sut = RecoveryKeyTools() diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformationTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformationTest.kt index cfe2ba32a6..7d2397b1ee 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformationTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformationTest.kt @@ -58,7 +58,8 @@ class RecoveryKeyVisualTransformationTest { @Test fun `RecoveryKeyOffsetMapping computes correct transformedToOriginal values`() { - val sut = RecoveryKeyVisualTransformation.RecoveryKeyOffsetMapping("" /* Not used by transformedToOriginal */) + // text parameter is not used by transformedToOriginal + val sut = RecoveryKeyVisualTransformation.RecoveryKeyOffsetMapping("") assertThat(sut.transformedToOriginal(0)).isEqualTo(0) assertThat(sut.transformedToOriginal(1)).isEqualTo(1) assertThat(sut.transformedToOriginal(2)).isEqualTo(2) diff --git a/features/signedout/api/src/main/kotlin/io/element/android/features/signedout/api/SignedOutEntryPoint.kt b/features/signedout/api/src/main/kotlin/io/element/android/features/signedout/api/SignedOutEntryPoint.kt index 7a156998d1..13d12ce1d0 100644 --- a/features/signedout/api/src/main/kotlin/io/element/android/features/signedout/api/SignedOutEntryPoint.kt +++ b/features/signedout/api/src/main/kotlin/io/element/android/features/signedout/api/SignedOutEntryPoint.kt @@ -22,7 +22,6 @@ import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.matrix.api.core.SessionId interface SignedOutEntryPoint : FeatureEntryPoint { - data class Params( val sessionId: SessionId, ) @@ -34,4 +33,3 @@ interface SignedOutEntryPoint : FeatureEntryPoint { fun build(): Node } } - diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/DefaultSignedOutEntryPoint.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/DefaultSignedOutEntryPoint.kt index 59b19f041e..72afaeb610 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/DefaultSignedOutEntryPoint.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/DefaultSignedOutEntryPoint.kt @@ -27,12 +27,10 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultSignedOutEntryPoint @Inject constructor() : SignedOutEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): SignedOutEntryPoint.NodeBuilder { val plugins = ArrayList() return object : SignedOutEntryPoint.NodeBuilder { - override fun params(params: SignedOutEntryPoint.Params): SignedOutEntryPoint.NodeBuilder { plugins += SignedOutNode.Inputs(params.sessionId) return this diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutNode.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutNode.kt index 381daa7278..8fc68a7227 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutNode.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutNode.kt @@ -35,7 +35,6 @@ class SignedOutNode @AssistedInject constructor( @Assisted plugins: List, presenterFactory: SignedOutPresenter.Factory, ) : Node(buildContext, plugins = plugins) { - data class Inputs( val sessionId: SessionId, ) : NodeInputs diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt index a93d22253d..0b3bbba17b 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt @@ -31,11 +31,11 @@ import io.element.android.libraries.sessionstorage.api.SessionStore import kotlinx.coroutines.launch class SignedOutPresenter @AssistedInject constructor( - @Assisted private val sessionId: String, /* Cannot inject SessionId */ + // Cannot inject SessionId + @Assisted private val sessionId: String, private val sessionStore: SessionStore, private val buildMeta: BuildMeta, ) : Presenter { - @AssistedFactory interface Factory { fun create(sessionId: String): SignedOutPresenter diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt index 66d552423e..9f62546c1a 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem @@ -41,7 +42,6 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.temporaryColorBgSpecial import io.element.android.libraries.designsystem.utils.CommonDrawables -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.persistentListOf diff --git a/features/signedout/impl/src/main/res/values-es/translations.xml b/features/signedout/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..b4ec370c95 --- /dev/null +++ b/features/signedout/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,8 @@ + + + "Has cambiado tu contraseña en otra sesión" + "Has eliminado la sesión desde otra sesión" + "El administrador de tu servidor ha invalidado su acceso" + "Es posible que haya cerrado sesión por uno de los motivos que se enumeran a continuación. Por favor inicia sesión nuevamente para continuar usando %s ." + "Has cerrado sesión" + diff --git a/features/signedout/impl/src/main/res/values-it/translations.xml b/features/signedout/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..c9da145562 --- /dev/null +++ b/features/signedout/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,8 @@ + + + "Hai cambiato la password in un\'altra sessione" + "Hai eliminato la sessione da un\'altra sessione" + "L\'amministratore del tuo server ha invalidato il tuo accesso" + "Potresti essere stato disconnesso per uno dei motivi elencati di seguito. Accedi di nuovo per continuare a usare %s." + "Sei disconnesso" + diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionNode.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionNode.kt index 2eb9063e3d..7612f9292c 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionNode.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionNode.kt @@ -32,7 +32,6 @@ class VerifySelfSessionNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: VerifySelfSessionPresenter, ) : Node(buildContext, plugins = plugins) { - @Composable override fun View(modifier: Modifier) { val state = presenter.present() diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt index 89751ca604..4d6889f716 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt @@ -24,7 +24,7 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import com.freeletics.flowredux.compose.rememberStateAndDispatch -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.VerificationFlowState @@ -40,7 +40,6 @@ class VerifySelfSessionPresenter @Inject constructor( private val sessionVerificationService: SessionVerificationService, private val stateMachine: VerifySelfSessionStateMachine, ) : Presenter { - @Composable override fun present(): VerifySelfSessionState { LaunchedEffect(Unit) { @@ -94,8 +93,8 @@ class VerifySelfSessionPresenter @Inject constructor( is StateMachineState.Verifying -> { val async = when (machineState) { - is StateMachineState.Verifying.Replying -> Async.Loading() - else -> Async.Uninitialized + is StateMachineState.Verifying.Replying -> AsyncData.Loading() + else -> AsyncData.Uninitialized } VerifySelfSessionState.VerificationStep.Verifying(machineState.data, async) } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt index f3afd3b7b9..1273367a71 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt @@ -18,7 +18,7 @@ package io.element.android.features.verifysession.impl import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.verification.SessionVerificationData @Immutable @@ -26,14 +26,13 @@ data class VerifySelfSessionState( val verificationFlowStep: VerificationStep, val eventSink: (VerifySelfSessionViewEvents) -> Unit, ) { - @Stable sealed interface VerificationStep { data object Initial : VerificationStep data object Canceled : VerificationStep data object AwaitingOtherDeviceResponse : VerificationStep data object Ready : VerificationStep - data class Verifying(val data: SessionVerificationData, val state: Async) : VerificationStep + data class Verifying(val data: SessionVerificationData, val state: AsyncData) : VerificationStep data object Completed : VerificationStep } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateMachine.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateMachine.kt index 52bd38bfbd..2a1c63370e 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateMachine.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateMachine.kt @@ -31,7 +31,6 @@ class VerifySelfSessionStateMachine @Inject constructor( ) : FlowReduxStateMachine( initialState = State.Initial ) { - init { spec { inState { diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateProvider.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateProvider.kt index 9115b8b444..81f25866bd 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateProvider.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateProvider.kt @@ -17,7 +17,7 @@ package io.element.android.features.verifysession.impl import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.verification.SessionVerificationData import io.element.android.libraries.matrix.api.verification.VerificationEmoji @@ -29,10 +29,10 @@ open class VerifySelfSessionStateProvider : PreviewParameterProvider Unit) val verificationViewState = screenState.verificationFlowStep val eventSink = screenState.eventSink - val isVerifying = (verificationViewState as? FlowStep.Verifying)?.state is Async.Loading + val isVerifying = (verificationViewState as? FlowStep.Verifying)?.state is AsyncData.Loading val positiveButtonTitle = when (verificationViewState) { FlowStep.Initial -> R.string.screen_session_verification_positive_button_initial FlowStep.Canceled -> R.string.screen_session_verification_positive_button_canceled diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt index 10e95bc09a..8f9c69085c 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt @@ -17,10 +17,10 @@ package io.element.android.features.verifysession.impl sealed interface VerifySelfSessionViewEvents { - data object RequestVerification: VerifySelfSessionViewEvents - data object StartSasVerification: VerifySelfSessionViewEvents - data object Restart: VerifySelfSessionViewEvents - data object ConfirmVerification: VerifySelfSessionViewEvents - data object DeclineVerification: VerifySelfSessionViewEvents - data object CancelAndClose: VerifySelfSessionViewEvents + data object RequestVerification : VerifySelfSessionViewEvents + data object StartSasVerification : VerifySelfSessionViewEvents + data object Restart : VerifySelfSessionViewEvents + data object ConfirmVerification : VerifySelfSessionViewEvents + data object DeclineVerification : VerifySelfSessionViewEvents + data object CancelAndClose : VerifySelfSessionViewEvents } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/emoji/EmojiResource.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/emoji/EmojiResource.kt index e57239d5f2..f35fe9a51c 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/emoji/EmojiResource.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/emoji/EmojiResource.kt @@ -89,6 +89,7 @@ internal fun Int.toEmojiResource(): EmojiResource { 60 -> EmojiResource(R.drawable.ic_verification_60, R.string.verification_emoji_60) 61 -> EmojiResource(R.drawable.ic_verification_61, R.string.verification_emoji_61) 62 -> EmojiResource(R.drawable.ic_verification_62, R.string.verification_emoji_62) - /* 63 */ else -> EmojiResource(R.drawable.ic_verification_63, R.string.verification_emoji_63) + 63 -> EmojiResource(R.drawable.ic_verification_63, R.string.verification_emoji_63) + else -> error("Cannot happen ($this)!") } } diff --git a/features/verifysession/impl/src/main/res/values-es/translations.xml b/features/verifysession/impl/src/main/res/values-es/translations.xml index e80fcc8682..812ba53a32 100644 --- a/features/verifysession/impl/src/main/res/values-es/translations.xml +++ b/features/verifysession/impl/src/main/res/values-es/translations.xml @@ -3,12 +3,15 @@ "Algo no fue bien. Se agotó el tiempo de espera de la solicitud o se rechazó." "Confirma que los emojis que aparecen a continuación coinciden con los que aparecen en tu otra sesión." "Comparar emojis" + "Confirma que los números que aparecen a continuación coinciden con los mostrados en tu otra sesión." + "Comparar números" "Tu nueva sesión ya está verificada. Tienes acceso a tus mensajes cifrados y otros usuarios lo considerarán de confianza." "Demuestra que eres tú para acceder a tu historial de mensajes cifrados." "Abrir una sesión existente" "Reintentar la verificación" "Estoy listo" "Esperando a que coincida" + "Compara un conjunto único de emojis." "Compara los emoji, asegurándote de que aparecen en el mismo orden." "No coinciden" "Coinciden" diff --git a/features/verifysession/impl/src/main/res/values-in/translations.xml b/features/verifysession/impl/src/main/res/values-in/translations.xml index 0c0a7dad4d..25bcdb2508 100644 --- a/features/verifysession/impl/src/main/res/values-in/translations.xml +++ b/features/verifysession/impl/src/main/res/values-in/translations.xml @@ -3,6 +3,8 @@ "Sepertinya ada yang tidak beres. Entah permintaan sudah habis masa berlakunya atau permintaan ditolak." "Konfirmasikan bahwa emoji di bawah ini sesuai dengan yang ditampilkan pada sesi Anda yang lain." "Bandingkan emoji" + "Konfirmasikan bahwa angka-angka di bawah ini sesuai dengan yang ditampilkan pada sesi Anda yang lain." + "Bandingkan angka" "Sesi baru Anda sekarang diverifikasi. Ini memiliki akses ke pesan terenkripsi Anda, dan pengguna lain akan melihatnya sebagai tepercaya." "Buktikan bahwa ini memang Anda untuk mengakses riwayat pesan terenkripsi Anda." "Buka sesi yang sudah ada" diff --git a/features/verifysession/impl/src/main/res/values-it/translations.xml b/features/verifysession/impl/src/main/res/values-it/translations.xml index 852e94b463..0f4f86adf4 100644 --- a/features/verifysession/impl/src/main/res/values-it/translations.xml +++ b/features/verifysession/impl/src/main/res/values-it/translations.xml @@ -3,12 +3,15 @@ "C\'è qualcosa che non va. La richiesta è scaduta o è stata rifiutata." "Verifica che gli emoji sottostanti corrispondano a quelli mostrati nell\'altra sessione." "Confronta le emoji" + "Conferma che i numeri seguenti corrispondano a quelli mostrati nell\'altra sessione." + "Confronta i numeri" "La tua nuova sessione è ora verificata. Ha accesso ai tuoi messaggi crittografati e gli altri utenti la vedranno come attendibile." "Dimostra la tua identità per accedere alla cronologia dei messaggi crittografati." "Apri una sessione esistente" "Riprova la verifica" "Sono pronto" "In attesa di un riscontro" + "Confronta un set unico di emoji." "Confronta le emoji uniche, assicurandoti che appaiano nello stesso ordine." "Non corrispondono" "Corrispondono" diff --git a/features/verifysession/impl/src/main/res/values-ru/translations.xml b/features/verifysession/impl/src/main/res/values-ru/translations.xml index 8351059b5d..78bc282cf4 100644 --- a/features/verifysession/impl/src/main/res/values-ru/translations.xml +++ b/features/verifysession/impl/src/main/res/values-ru/translations.xml @@ -1,7 +1,7 @@ - "Кажется, что-то не так. Время ожидания запроса истекло, либо запрос был отклонен." - "Убедитесь, что приведенные ниже смайлики совпадают со смайликами, показанными во время другого сеанса." + "Похоже, что-то не так. Время ожидания запроса либо истекло, либо запрос был отклонен." + "Убедитесь, что приведенные ниже емоджи совпадают с емоджи показанными во время другого сеанса." "Сравните емодзи" "Убедитесь, что приведенные ниже числа совпадают с цифрами, показанными в другом сеансе." "Сравните числа" diff --git a/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml b/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml index 39a74d1bb6..ed85823769 100644 --- a/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml @@ -9,6 +9,7 @@ "重新嘗試驗證" "我準備好了" "等待比對" + "比對一組唯一的表情符號。" "表情符號是唯一的,請相互比對,確認它們的排列順序是否相同。" "不一樣" "一樣" diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt index c96866cb5a..240a4f0606 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt @@ -22,7 +22,7 @@ import app.cash.turbine.ReceiveTurbine import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.verifysession.impl.VerifySelfSessionState.VerificationStep -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.verification.SessionVerificationData import io.element.android.libraries.matrix.api.verification.VerificationEmoji import io.element.android.libraries.matrix.api.verification.VerificationFlowState @@ -35,7 +35,6 @@ import org.junit.Test @ExperimentalCoroutinesApi class VerifySelfSessionPresenterTests { - @get:Rule val warmUpRule = WarmUpRule() @@ -173,7 +172,7 @@ class VerifySelfSessionPresenterTests { assertThat(awaitItem().verificationFlowStep).isEqualTo( VerificationStep.Verifying( SessionVerificationData.Emojis(emojis), - Async.Loading(), + AsyncData.Loading(), ) ) assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Completed) @@ -192,7 +191,7 @@ class VerifySelfSessionPresenterTests { assertThat(awaitItem().verificationFlowStep).isEqualTo( VerificationStep.Verifying( SessionVerificationData.Emojis(emptyList()), - Async.Loading(), + AsyncData.Loading(), ) ) assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Canceled) diff --git a/gradle.properties b/gradle.properties index 0ece8f4e8c..959d1bc05c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -56,3 +56,6 @@ android.experimental.enableTestFixtures=true # Create BuildConfig files as bytecode to avoid Java compilation phase android.enableBuildConfigAsBytecode=true + +# By default, the plugin applies itself to all subprojects, but we don't want that as it would cause issues with builds using local AARs +dependency.analysis.autoapply=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1650196db7..d65eb31e8a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,11 +3,9 @@ [versions] # Project -# Warning: version 8.2.0 is leading https://github.com/element-hq/element-x-android/issues/1971 -# which will only occurs in release mode, and so will not be detected by Maestro -android_gradle_plugin = "8.1.4" -kotlin = "1.9.20" -ksp = "1.9.20-1.0.14" +android_gradle_plugin = "8.2.1" +kotlin = "1.9.21" +ksp = "1.9.21-1.0.16" firebaseAppDistribution = "4.0.1" # AndroidX @@ -15,13 +13,13 @@ core = "1.12.0" datastore = "1.0.0" constraintlayout = "2.1.4" constraintlayout_compose = "1.0.1" -lifecycle = "2.7.0-rc02" +lifecycle = "2.7.0" activity = "1.8.2" -media3 = "1.2.0" +media3 = "1.2.1" # Compose compose_bom = "2023.10.01" -composecompiler = "1.5.4" +composecompiler = "1.5.7" # Coroutines coroutines = "1.7.3" @@ -34,16 +32,17 @@ test_core = "1.5.0" #other coil = "2.5.0" -datetime = "0.4.1" -serialization_json = "1.6.1" +datetime = "0.5.0" +dependencyAnalysis = "1.28.0" +serialization_json = "1.6.2" showkase = "1.0.2" appyx = "1.4.0" sqldelight = "2.0.1" -wysiwyg = "2.22.0" +wysiwyg = "2.25.0" # DI -dagger = "2.49" -anvil = "2.4.8-1-8" +dagger = "2.50" +anvil = "2.4.9" # Auto service autoservice = "1.1.1" @@ -52,6 +51,7 @@ autoservice = "1.1.1" junit = "4.13.2" androidx-test-ext-junit = "1.1.5" espresso-core = "3.5.1" +kover = "0.7.5" [libraries] # Project @@ -59,10 +59,12 @@ android_gradle_plugin = { module = "com.android.tools.build:gradle", version.ref # https://developer.android.com/studio/write/java8-support#library-desugaring-versions android_desugar = "com.android.tools:desugar_jdk_libs:2.0.4" kotlin_gradle_plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } +kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" } gms_google_services = "com.google.gms:google-services:4.4.0" # https://firebase.google.com/docs/android/setup#available-libraries google_firebase_bom = "com.google.firebase:firebase-bom:32.7.0" firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" } +autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" } # AndroidX androidx_core = { module = "androidx.core:core", version.ref = "core" } @@ -91,7 +93,7 @@ androidx_preference = "androidx.preference:preference:1.2.1" androidx_webkit = "androidx.webkit:webkit:1.9.0" androidx_compose_bom = { module = "androidx.compose:compose-bom", version.ref = "compose_bom" } -androidx_compose_material3 = "androidx.compose.material3:material3:1.2.0-beta01" +androidx_compose_material3 = "androidx.compose.material3:material3:1.2.0-alpha11" # Keep using alpha11 for now as it breaks timeline scrolling a bit. androidx_compose_ui = { module = "androidx.compose.ui:ui" } androidx_compose_ui_tooling = { module = "androidx.compose.ui:ui-tooling" } androidx_compose_ui_tooling_preview = { module = "androidx.compose.ui:ui-tooling-preview" } @@ -127,7 +129,7 @@ test_arch_core = "androidx.arch.core:core-testing:2.2.0" test_junit = "junit:junit:4.13.2" test_runner = "androidx.test:runner:1.5.2" test_junitext = "androidx.test.ext:junit:1.1.5" -test_mockk = "io.mockk:mockk:1.13.8" +test_mockk = "io.mockk:mockk:1.13.9" test_konsist = "com.lemonappdev:konsist:0.13.0" test_turbine = "app.cash.turbine:turbine:1.0.0" test_truth = "com.google.truth:truth:1.2.0" @@ -140,17 +142,17 @@ coil = { module = "io.coil-kt:coil", version.ref = "coil" } coil_compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } coil_gif = { module = "io.coil-kt:coil-gif", version.ref = "coil" } coil_test = { module = "io.coil-kt:coil-test", version.ref = "coil" } -compound = { module = "io.element.android:compound-android", version = "0.0.2" } +compound = { module = "io.element.android:compound-android", version = "0.0.3" } datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" } serialization_json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization_json" } -kotlinx_collections_immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.6" +kotlinx_collections_immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7" showkase = { module = "com.airbnb.android:showkase", version.ref = "showkase" } showkase_processor = { module = "com.airbnb.android:showkase-processor", version.ref = "showkase" } -jsoup = "org.jsoup:jsoup:1.17.1" +jsoup = "org.jsoup:jsoup:1.17.2" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } -molecule-runtime = "app.cash.molecule:molecule-runtime:1.3.1" +molecule-runtime = "app.cash.molecule:molecule-runtime:1.3.2" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.82" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.83" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } @@ -162,7 +164,7 @@ unifiedpush = "com.github.UnifiedPush:android-connector:2.1.1" otaliastudios_transcoder = "com.otaliastudios:transcoder:0.10.5" vanniktech_blurhash = "com.vanniktech:blurhash:0.2.0" telephoto_zoomableimage = "me.saket.telephoto:zoomable-image-coil:0.7.1" -statemachine = "com.freeletics.flowredux:compose:1.2.0" +statemachine = "com.freeletics.flowredux:compose:1.2.1" maplibre = "org.maplibre.gl:android-sdk:10.2.0" maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:2.0.2" maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:2.0.2" @@ -170,8 +172,8 @@ opusencoder = "io.element.android:opusencoder:1.1.0" kotlinpoet = "com.squareup:kotlinpoet:1.15.3" # Analytics -posthog = "com.posthog:posthog-android:3.0.0" -sentry = "io.sentry:sentry-android:7.1.0" +posthog = "com.posthog:posthog-android:3.1.2" +sentry = "io.sentry:sentry-android:7.2.0" matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:aa14cbcdf81af2746d20a71779ec751f971e1d7f" # Emojibase @@ -210,15 +212,15 @@ kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } anvil = { id = "com.squareup.anvil", version.ref = "anvil" } detekt = "io.gitlab.arturbosch.detekt:1.23.4" -ktlint = "org.jlleitschuh.gradle.ktlint:12.0.3" +ktlint = "org.jlleitschuh.gradle.ktlint:12.1.0" dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12" -dependencycheck = "org.owasp.dependencycheck:9.0.7" -dependencyanalysis = "com.autonomousapps.dependency-analysis:1.27.0" +dependencycheck = "org.owasp.dependencycheck:9.0.8" +dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" } paparazzi = "app.cash.paparazzi:1.3.1" -kover = "org.jetbrains.kotlinx.kover:0.6.1" +kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } firebaseAppDistribution = { id = "com.google.firebase.appdistribution", version.ref = "firebaseAppDistribution" } -knit = { id = "org.jetbrains.kotlinx.knit", version = "0.4.0" } +knit = { id = "org.jetbrains.kotlinx.knit", version = "0.5.0" } # Version '4.3.1.3277' introduced some regressions in CI time (more than 2x slower), so make sure # this is no longer the case before upgrading. diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/accessibility/ContextExt.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/accessibility/ContextExt.kt index bb223e43fb..803c140c58 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/accessibility/ContextExt.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/accessibility/ContextExt.kt @@ -36,4 +36,3 @@ fun Context.isScreenReaderEnabled(): Boolean { it.isEnabled && it.isTouchExplorationEnabled } } - diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/AndroidClipboardHelper.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/AndroidClipboardHelper.kt index cecf47eb1b..d7f7d5f4a6 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/AndroidClipboardHelper.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/AndroidClipboardHelper.kt @@ -31,7 +31,6 @@ import javax.inject.Inject class AndroidClipboardHelper @Inject constructor( @ApplicationContext private val context: Context, ) : ClipboardHelper { - private val clipboardManager = requireNotNull(context.getSystemService()) override fun copyPlainText(text: String) { diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/FakeClipboardHelper.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/FakeClipboardHelper.kt index 03cd70c768..1d93a207c6 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/FakeClipboardHelper.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/FakeClipboardHelper.kt @@ -17,7 +17,6 @@ package io.element.android.libraries.androidutils.clipboard class FakeClipboardHelper : ClipboardHelper { - var clipboardContents: Any? = null override fun copyPlainText(text: String) { diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt index ca00f72fb7..3c897b3d88 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt @@ -26,6 +26,6 @@ fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int): Ap packageName, PackageManager.ApplicationInfoFlags.of(flags.toLong()) ) - else -> @Suppress("DEPRECATION") getApplicationInfo(packageName, flags) + else -> getApplicationInfo(packageName, flags) } } diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DefaultDiffCallback.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DefaultDiffCallback.kt index 219441d5e6..9c58e2baa5 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DefaultDiffCallback.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DefaultDiffCallback.kt @@ -26,7 +26,6 @@ internal class DefaultDiffCallback( private val newList: List, private val areItemsTheSame: (oldItem: T?, newItem: T?) -> Boolean, ) : DiffUtil.Callback() { - override fun getOldListSize(): Int { return oldList.size } diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCache.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCache.kt index 3d1161e2e0..b0f93227cc 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCache.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCache.kt @@ -40,7 +40,6 @@ interface MutableDiffCache : DiffCache { * */ class MutableListDiffCache(private val mutableList: MutableList = ArrayList()) : MutableDiffCache { - override fun removeAt(index: Int): E? { return mutableList.removeAt(index) } diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheInvalidator.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheInvalidator.kt index 4ebdc3224f..d99aad185c 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheInvalidator.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheInvalidator.kt @@ -36,7 +36,6 @@ interface DiffCacheInvalidator { * It invalidates the cache by setting values to null. */ class DefaultDiffCacheInvalidator : DiffCacheInvalidator { - override fun onChanged(position: Int, count: Int, cache: MutableDiffCache) { for (i in position until position + count) { // Invalidate cache diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheUpdater.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheUpdater.kt index 500edcb135..d7d5125b58 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheUpdater.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheUpdater.kt @@ -36,7 +36,6 @@ class DiffCacheUpdater( private val cacheInvalidator: DiffCacheInvalidator = DefaultDiffCacheInvalidator(), private val areItemsTheSame: (oldItem: ListItem?, newItem: ListItem?) -> Boolean, ) { - private val lock = Object() private var prevOriginalList: List = emptyList() diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/compressFile.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/FileCompression.kt similarity index 100% rename from libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/compressFile.kt rename to libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/FileCompression.kt diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hardware/vibrator.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hardware/VibratorTools.kt similarity index 100% rename from libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hardware/vibrator.kt rename to libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hardware/VibratorTools.kt diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt index 6d4cc321a0..c9c0ce956a 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt @@ -26,8 +26,8 @@ fun String.hash() = try { val digest = MessageDigest.getInstance("SHA-512") digest.update(toByteArray()) digest.digest() - .joinToString("") { String.format(Locale.ROOT, "%02X", it) } - .lowercase(Locale.ROOT) + .joinToString("") { String.format(Locale.ROOT, "%02X", it) } + .lowercase(Locale.ROOT) } catch (exc: Exception) { // Should not happen, but just in case hashCode().toString() diff --git a/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatterTest.kt b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatterTest.kt index 4d83a2367a..32239ca36b 100644 --- a/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatterTest.kt +++ b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatterTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.androidutils.filesize +package io.element.android.libraries.androidutils.filesize import android.os.Build import com.google.common.truth.Truth.assertThat diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncAction.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncAction.kt new file mode 100644 index 0000000000..38de169db3 --- /dev/null +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncAction.kt @@ -0,0 +1,161 @@ +/* + * 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.architecture + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.Stable +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Sealed type that allows to model an asynchronous operation triggered by the user. + */ +@Stable +sealed interface AsyncAction { + /** + * Represents an uninitialized operation (i.e. yet to be run by the user). + */ + data object Uninitialized : AsyncAction + + /** + * Represents an operation that is currently waiting for user confirmation. + */ + data object Confirming : AsyncAction + + /** + * Represents an operation that is currently ongoing. + */ + data object Loading : AsyncAction + + /** + * Represents a failed operation. + * + * @property error the error that caused the operation to fail. + */ + data class Failure( + val error: Throwable, + ) : AsyncAction + + /** + * Represents a successful operation. + * + * @param T the type of data returned by the operation. + * @property data the data returned by the operation. + */ + data class Success( + val data: T, + ) : AsyncAction + + /** + * Returns the data returned by the operation, or null otherwise. + */ + fun dataOrNull(): T? = when (this) { + is Success -> data + else -> null + } + + /** + * Returns the error that caused the operation to fail, or null otherwise. + */ + fun errorOrNull(): Throwable? = when (this) { + is Failure -> error + else -> null + } + + fun isUninitialized(): Boolean = this == Uninitialized + + fun isConfirming(): Boolean = this is Confirming + + fun isLoading(): Boolean = this is Loading + + fun isFailure(): Boolean = this is Failure + + fun isSuccess(): Boolean = this is Success +} + +suspend inline fun MutableState>.runCatchingUpdatingState( + errorTransform: (Throwable) -> Throwable = { it }, + block: () -> T, +): Result = runUpdatingState( + state = this, + errorTransform = errorTransform, + resultBlock = { + runCatching { + block() + } + }, +) + +suspend inline fun (suspend () -> T).runCatchingUpdatingState( + state: MutableState>, + errorTransform: (Throwable) -> Throwable = { it }, +): Result = runUpdatingState( + state = state, + errorTransform = errorTransform, + resultBlock = { + runCatching { + this() + } + }, +) + +suspend inline fun MutableState>.runUpdatingState( + errorTransform: (Throwable) -> Throwable = { it }, + resultBlock: () -> Result, +): Result = runUpdatingState( + state = this, + errorTransform = errorTransform, + resultBlock = resultBlock, +) + +/** + * Calls the specified [Result]-returning function [resultBlock] + * encapsulating its progress and return value into an [AsyncAction] while + * posting its updates to the MutableState [state]. + * + * @param T the type of data returned by the operation. + * @param state the [MutableState] to post updates to. + * @param errorTransform a function to transform the error before posting it. + * @param resultBlock a suspending function that returns a [Result]. + * @return the [Result] returned by [resultBlock]. + */ +@OptIn(ExperimentalContracts::class) +@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") +suspend inline fun runUpdatingState( + state: MutableState>, + errorTransform: (Throwable) -> Throwable = { it }, + resultBlock: suspend () -> Result, +): Result { + contract { + callsInPlace(resultBlock, InvocationKind.EXACTLY_ONCE) + } + state.value = AsyncAction.Loading + return resultBlock().fold( + onSuccess = { + state.value = AsyncAction.Success(it) + Result.success(it) + }, + onFailure = { + val error = errorTransform(it) + state.value = AsyncAction.Failure( + error = error, + ) + Result.failure(error) + } + ) +} diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/Async.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncData.kt similarity index 88% rename from libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/Async.kt rename to libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncData.kt index fb7bc2836b..75bb503390 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/Async.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncData.kt @@ -26,8 +26,7 @@ import kotlin.contracts.contract * Sealed type that allows to model an asynchronous operation. */ @Stable -sealed interface Async { - +sealed interface AsyncData { /** * Represents a failed operation. * @@ -38,7 +37,7 @@ sealed interface Async { data class Failure( val error: Throwable, val prevData: T? = null, - ) : Async + ) : AsyncData /** * Represents an operation that is currently ongoing. @@ -48,7 +47,7 @@ sealed interface Async { */ data class Loading( val prevData: T? = null, - ) : Async + ) : AsyncData /** * Represents a successful operation. @@ -58,12 +57,12 @@ sealed interface Async { */ data class Success( val data: T, - ) : Async + ) : AsyncData /** * Represents an uninitialized operation (i.e. yet to be run). */ - data object Uninitialized : Async + data object Uninitialized : AsyncData /** * Returns the data returned by the operation, or null otherwise. @@ -94,7 +93,7 @@ sealed interface Async { fun isUninitialized(): Boolean = this == Uninitialized } -suspend inline fun MutableState>.runCatchingUpdatingState( +suspend inline fun MutableState>.runCatchingUpdatingState( errorTransform: (Throwable) -> Throwable = { it }, block: () -> T, ): Result = runUpdatingState( @@ -108,7 +107,7 @@ suspend inline fun MutableState>.runCatchingUpdatingState( ) suspend inline fun (suspend () -> T).runCatchingUpdatingState( - state: MutableState>, + state: MutableState>, errorTransform: (Throwable) -> Throwable = { it }, ): Result = runUpdatingState( state = state, @@ -120,7 +119,7 @@ suspend inline fun (suspend () -> T).runCatchingUpdatingState( }, ) -suspend inline fun MutableState>.runUpdatingState( +suspend inline fun MutableState>.runUpdatingState( errorTransform: (Throwable) -> Throwable = { it }, resultBlock: () -> Result, ): Result = runUpdatingState( @@ -131,7 +130,7 @@ suspend inline fun MutableState>.runUpdatingState( /** * Calls the specified [Result]-returning function [resultBlock] - * encapsulating its progress and return value into an [Async] while + * encapsulating its progress and return value into an [AsyncData] while * posting its updates to the MutableState [state]. * * @param T the type of data returned by the operation. @@ -143,7 +142,7 @@ suspend inline fun MutableState>.runUpdatingState( @OptIn(ExperimentalContracts::class) @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") suspend inline fun runUpdatingState( - state: MutableState>, + state: MutableState>, errorTransform: (Throwable) -> Throwable = { it }, resultBlock: suspend () -> Result, ): Result { @@ -151,15 +150,15 @@ suspend inline fun runUpdatingState( callsInPlace(resultBlock, InvocationKind.EXACTLY_ONCE) } val prevData = state.value.dataOrNull() - state.value = Async.Loading(prevData = prevData) + state.value = AsyncData.Loading(prevData = prevData) return resultBlock().fold( onSuccess = { - state.value = Async.Success(it) + state.value = AsyncData.Success(it) Result.success(it) }, onFailure = { val error = errorTransform(it) - state.value = Async.Failure( + state.value = AsyncData.Failure( error = error, prevData = prevData, ) diff --git a/libraries/textcomposer/test/build.gradle.kts b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/coverage/ExcludeFromCoverage.kt similarity index 71% rename from libraries/textcomposer/test/build.gradle.kts rename to libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/coverage/ExcludeFromCoverage.kt index 04e36e337d..559c3f99c8 100644 --- a/libraries/textcomposer/test/build.gradle.kts +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/coverage/ExcludeFromCoverage.kt @@ -14,15 +14,8 @@ * limitations under the License. */ -plugins { - id("io.element.android-compose-library") -} +package io.element.android.libraries.architecture.coverage -android { - namespace = "io.element.android.libraries.textcomposer.test" -} - -dependencies { - api(projects.libraries.textcomposer.impl) - implementation(projects.tests.testutils) -} +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) +annotation class ExcludeFromCoverage diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/HideOverlayBackPressHandler.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/HideOverlayBackPressHandler.kt index 38149f2e92..126f95bdb7 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/HideOverlayBackPressHandler.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/HideOverlayBackPressHandler.kt @@ -23,9 +23,8 @@ import io.element.android.libraries.architecture.overlay.operation.Hide import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -class HideOverlayBackPressHandler - : BaseBackPressHandlerStrategy() { - +class HideOverlayBackPressHandler : + BaseBackPressHandlerStrategy() { override val canHandleBackPressFlow: Flow by lazy { navModel.elements.map(::areThereElements) } diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/Overlay.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/Overlay.kt index e8f3e3ebc5..ebae1a3381 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/Overlay.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/Overlay.kt @@ -40,7 +40,6 @@ class Overlay( savedStateMap = savedStateMap, key = key, ) { - override val initialElements: NavElements get() = emptyList() } diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Hide.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Hide.kt index e782d4537f..db3b71c2ef 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Hide.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Hide.kt @@ -24,7 +24,6 @@ import kotlinx.parcelize.Parcelize @Parcelize class Hide : OverlayOperation { - override fun isApplicable(elements: BackStackElements): Boolean = elements.any { it.targetState == BackStack.State.ACTIVE } diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Show.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Show.kt index 90561127ef..1ed00e65b2 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Show.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Show.kt @@ -29,7 +29,6 @@ import kotlinx.parcelize.RawValue data class Show( private val element: @RawValue T ) : OverlayOperation { - override fun isApplicable(elements: BackStackElements): Boolean = element != elements.activeElement diff --git a/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncKtTest.kt b/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncDataKtTest.kt similarity index 73% rename from libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncKtTest.kt rename to libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncDataKtTest.kt index 4b6c75a108..f661e6f61c 100644 --- a/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncKtTest.kt +++ b/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncDataKtTest.kt @@ -22,10 +22,10 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest import org.junit.Test -class AsyncKtTest { +class AsyncDataKtTest { @Test fun `updates state when block returns success`() = runTest { - val state = TestableMutableState>(Async.Uninitialized) + val state = TestableMutableState>(AsyncData.Uninitialized) val result = runUpdatingState(state) { delay(1) @@ -35,15 +35,15 @@ class AsyncKtTest { assertThat(result.isSuccess).isTrue() assertThat(result.getOrNull()).isEqualTo(1) - assertThat(state.popFirst()).isEqualTo(Async.Uninitialized) - assertThat(state.popFirst()).isEqualTo(Async.Loading(null)) - assertThat(state.popFirst()).isEqualTo(Async.Success(1)) + assertThat(state.popFirst()).isEqualTo(AsyncData.Uninitialized) + assertThat(state.popFirst()).isEqualTo(AsyncData.Loading(null)) + assertThat(state.popFirst()).isEqualTo(AsyncData.Success(1)) state.assertNoMoreValues() } @Test fun `updates state when block returns failure`() = runTest { - val state = TestableMutableState>(Async.Uninitialized) + val state = TestableMutableState>(AsyncData.Uninitialized) val result = runUpdatingState(state) { delay(1) @@ -53,15 +53,15 @@ class AsyncKtTest { assertThat(result.isFailure).isTrue() assertThat(result.exceptionOrNull()).isEqualTo(MyThrowable("hello")) - assertThat(state.popFirst()).isEqualTo(Async.Uninitialized) - assertThat(state.popFirst()).isEqualTo(Async.Loading(null)) - assertThat(state.popFirst()).isEqualTo(Async.Failure(MyThrowable("hello"))) + assertThat(state.popFirst()).isEqualTo(AsyncData.Uninitialized) + assertThat(state.popFirst()).isEqualTo(AsyncData.Loading(null)) + assertThat(state.popFirst()).isEqualTo(AsyncData.Failure(MyThrowable("hello"))) state.assertNoMoreValues() } @Test fun `updates state when block returns failure transforming the error`() = runTest { - val state = TestableMutableState>(Async.Uninitialized) + val state = TestableMutableState>(AsyncData.Uninitialized) val result = runUpdatingState(state, { MyThrowable(it.message + " world") }) { delay(1) @@ -71,9 +71,9 @@ class AsyncKtTest { assertThat(result.isFailure).isTrue() assertThat(result.exceptionOrNull()).isEqualTo(MyThrowable("hello world")) - assertThat(state.popFirst()).isEqualTo(Async.Uninitialized) - assertThat(state.popFirst()).isEqualTo(Async.Loading(null)) - assertThat(state.popFirst()).isEqualTo(Async.Failure(MyThrowable("hello world"))) + assertThat(state.popFirst()).isEqualTo(AsyncData.Uninitialized) + assertThat(state.popFirst()).isEqualTo(AsyncData.Loading(null)) + assertThat(state.popFirst()).isEqualTo(AsyncData.Failure(MyThrowable("hello world"))) state.assertNoMoreValues() } } @@ -84,7 +84,7 @@ class AsyncKtTest { private class TestableMutableState( value: T ) : MutableState { - + @Suppress("ktlint:standard:property-naming") private val _deque = ArrayDeque(listOf(value)) override var value: T diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/cache/CircularCache.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/cache/CircularCache.kt index f5305f006b..48cc6e2e82 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/cache/CircularCache.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/cache/CircularCache.kt @@ -21,7 +21,6 @@ package io.element.android.libraries.core.cache * This class is not thread safe. */ class CircularCache(cacheSize: Int, factory: (Int) -> Array) { - companion object { inline fun create(cacheSize: Int) = CircularCache(cacheSize) { Array(cacheSize) { null } } } diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt index 343f5ce351..d839be9dee 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt @@ -26,20 +26,6 @@ inline fun T.ooi(block: (T) -> Unit): T = also(block) */ fun CharSequence?.orEmpty() = this ?: "" -/** - * Check if a CharSequence is a phone number. - */ -/* -fun CharSequence.isMsisdn(): Boolean { - return try { - PhoneNumberUtil.getInstance().parse(ensurePrefix("+"), null) - true - } catch (e: NumberParseException) { - false - } -} - */ - /** * Useful to append a String at the end of a filename but before the extension if any * Ex: diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/log/logger/LoggerTag.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/log/logger/LoggerTag.kt index 1bec5524cd..eaafdf6f61 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/log/logger/LoggerTag.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/log/logger/LoggerTag.kt @@ -23,7 +23,6 @@ package io.element.android.libraries.core.log.logger * Timber.tag(loggerTag.value).v("My log message") */ open class LoggerTag(name: String, parentTag: LoggerTag? = null) { - object PushLoggerTag : LoggerTag("Push") object NotificationLoggerTag : LoggerTag("Notification", PushLoggerTag) diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt index 6a97757b55..0c1efd0070 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.core.mimetype import io.element.android.libraries.core.bool.orFalse // The Android SDK does not provide constant for mime type, add some of them here +@Suppress("ktlint:standard:property-naming") object MimeTypes { const val Any: String = "*/*" const val OctetStream = "application/octet-stream" diff --git a/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/BasicExtensionsTest.kt b/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/BasicExtensionsTest.kt index 5a3baa1297..b9d6a91cb1 100644 --- a/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/BasicExtensionsTest.kt +++ b/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/BasicExtensionsTest.kt @@ -20,7 +20,6 @@ import org.junit.Assert.assertEquals import org.junit.Test class BasicExtensionsTest { - @Test(expected = IllegalArgumentException::class) fun `test ellipsize at 0`() { "1234567890".ellipsize(0) diff --git a/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/ResultTest.kt b/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/ResultTest.kt index 7f83c9de8f..42ac6cd31e 100644 --- a/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/ResultTest.kt +++ b/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/ResultTest.kt @@ -20,7 +20,6 @@ import com.google.common.truth.Truth.assertThat import org.junit.Test class ResultTest { - @Test fun testFlatMap() { val initial = Result.success("initial") diff --git a/libraries/coroutines/.gitignore b/libraries/coroutines/.gitignore deleted file mode 100644 index 42afabfd2a..0000000000 --- a/libraries/coroutines/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionService.kt b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionService.kt index cf1ea93e3a..2baec4560a 100644 --- a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionService.kt +++ b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionService.kt @@ -31,7 +31,6 @@ import javax.inject.Inject */ @ContributesBinding(AppScope::class) class AESEncryptionDecryptionService @Inject constructor() : EncryptionDecryptionService { - override fun createEncryptionCipher(key: SecretKey): Cipher { return Cipher.getInstance(AESEncryptionSpecs.CIPHER_TRANSFORMATION).apply { init(Cipher.ENCRYPT_MODE, key) diff --git a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/CryptographyModule.kt b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/CryptographyModule.kt index 7b55dca15b..bd248489f2 100644 --- a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/CryptographyModule.kt +++ b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/CryptographyModule.kt @@ -27,7 +27,6 @@ internal const val ANDROID_KEYSTORE = "AndroidKeyStore" @ContributesTo(AppScope::class) @Module object CryptographyModule { - @Provides fun providesAndroidKeyStore(): KeyStore { return KeyStore.getInstance(ANDROID_KEYSTORE).apply { diff --git a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreSecretKeyRepository.kt b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreSecretKeyRepository.kt index f1bdb03b5c..c3b14f8bed 100644 --- a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreSecretKeyRepository.kt +++ b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreSecretKeyRepository.kt @@ -38,7 +38,6 @@ import javax.inject.Inject class KeyStoreSecretKeyRepository @Inject constructor( private val keyStore: KeyStore, ) : SecretKeyRepository { - // False positive lint issue @SuppressLint("WrongConstant") override fun getOrCreateKey(alias: String, requiresUserAuthentication: Boolean): SecretKey { diff --git a/libraries/cryptography/impl/src/test/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionServiceTest.kt b/libraries/cryptography/impl/src/test/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionServiceTest.kt index 38e1c924ca..61c8466023 100644 --- a/libraries/cryptography/impl/src/test/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionServiceTest.kt +++ b/libraries/cryptography/impl/src/test/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionServiceTest.kt @@ -24,7 +24,6 @@ import java.security.GeneralSecurityException import javax.crypto.KeyGenerator class AESEncryptionDecryptionServiceTest { - private val encryptionDecryptionService = AESEncryptionDecryptionService() @Test @@ -50,5 +49,4 @@ class AESEncryptionDecryptionServiceTest { encryptionDecryptionService.decrypt(decryptionKey, encryptionResult) } } - } diff --git a/libraries/cryptography/test/src/main/kotlin/io/element/android/libraries/cryptography/test/SimpleSecretKeyRepository.kt b/libraries/cryptography/test/src/main/kotlin/io/element/android/libraries/cryptography/test/SimpleSecretKeyRepository.kt index b6f9155d12..e8e2b7e6f7 100644 --- a/libraries/cryptography/test/src/main/kotlin/io/element/android/libraries/cryptography/test/SimpleSecretKeyRepository.kt +++ b/libraries/cryptography/test/src/main/kotlin/io/element/android/libraries/cryptography/test/SimpleSecretKeyRepository.kt @@ -22,7 +22,6 @@ import javax.crypto.KeyGenerator import javax.crypto.SecretKey class SimpleSecretKeyRepository : SecretKeyRepository { - private var secretKeyForAlias = HashMap() override fun getOrCreateKey(alias: String, requiresUserAuthentication: Boolean): SecretKey { diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt index 31b58085ba..9493079925 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt @@ -36,7 +36,6 @@ class DateFormatters @Inject constructor( private val clock: Clock, private val timeZone: TimeZone, ) { - private val onlyTimeFormatter: DateTimeFormatter by lazy { val pattern = DateFormat.getBestDateTimePattern(locale, "HH:mm") ?: "HH:mm" DateTimeFormatter.ofPattern(pattern, locale) diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDaySeparatorFormatter.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDaySeparatorFormatter.kt index 4c6ebbbde0..90d7f5c205 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDaySeparatorFormatter.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDaySeparatorFormatter.kt @@ -26,7 +26,6 @@ class DefaultDaySeparatorFormatter @Inject constructor( private val localDateTimeProvider: LocalDateTimeProvider, private val dateFormatters: DateFormatters, ) : DaySeparatorFormatter { - override fun format(timestamp: Long): String { val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp) return dateFormatters.formatDateWithYear(dateToFormat) diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultLastMessageTimestampFormatter.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultLastMessageTimestampFormatter.kt index 78294870d2..ace64fb8e6 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultLastMessageTimestampFormatter.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultLastMessageTimestampFormatter.kt @@ -26,7 +26,6 @@ class DefaultLastMessageTimestampFormatter @Inject constructor( private val localDateTimeProvider: LocalDateTimeProvider, private val dateFormatters: DateFormatters, ) : LastMessageTimestampFormatter { - override fun format(timestamp: Long?): String { if (timestamp == null) return "" val currentDate = localDateTimeProvider.providesNow() diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocalDateTimeProvider.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocalDateTimeProvider.kt index 8395cb476c..9d3e5735bb 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocalDateTimeProvider.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocalDateTimeProvider.kt @@ -27,7 +27,6 @@ class LocalDateTimeProvider @Inject constructor( private val clock: Clock, private val timezone: TimeZone, ) { - fun providesNow(): LocalDateTime { val now: Instant = clock.now() return now.toLocalDateTime(timezone) diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt index 352306da65..bd83d3abce 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt @@ -22,7 +22,7 @@ import dagger.Provides import io.element.android.libraries.di.AppScope import kotlinx.datetime.Clock import kotlinx.datetime.TimeZone -import java.util.* +import java.util.Locale @Module @ContributesTo(AppScope::class) diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultLastMessageTimestampFormatterTest.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultLastMessageTimestampFormatterTest.kt index 5aefcdcd7b..dd8f75cae3 100644 --- a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultLastMessageTimestampFormatterTest.kt +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultLastMessageTimestampFormatterTest.kt @@ -25,7 +25,6 @@ import org.junit.Test import java.util.Locale class DefaultLastMessageTimestampFormatterTest { - @Test fun `test null`() { val now = "1980-04-06T18:35:24.00Z" diff --git a/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDaySeparatorFormatter.kt b/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDaySeparatorFormatter.kt index 202e3ee5ae..c314809e66 100644 --- a/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDaySeparatorFormatter.kt +++ b/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDaySeparatorFormatter.kt @@ -19,7 +19,6 @@ package io.element.android.libraries.dateformatter.test import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter class FakeDaySeparatorFormatter : DaySeparatorFormatter { - private var format = "" fun givenFormat(format: String) { diff --git a/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/usecase/InviteFriendsUseCase.kt b/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/usecase/InviteFriendsUseCase.kt index 8ee94c31fe..24296f8798 100644 --- a/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/usecase/InviteFriendsUseCase.kt +++ b/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/usecase/InviteFriendsUseCase.kt @@ -21,11 +21,11 @@ import io.element.android.libraries.androidutils.system.startSharePlainTextInten import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder +import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.toolbox.api.strings.StringProvider import timber.log.Timber import javax.inject.Inject import io.element.android.libraries.androidutils.R as AndroidUtilsR -import io.element.android.libraries.ui.strings.CommonStrings class InviteFriendsUseCase @Inject constructor( private val stringProvider: StringProvider, diff --git a/libraries/deeplink/src/test/kotlin/io/element/android/libraries/deeplink/DeepLinkCreatorTest.kt b/libraries/deeplink/src/test/kotlin/io/element/android/libraries/deeplink/DeepLinkCreatorTest.kt index 70c047f8ed..5c43624655 100644 --- a/libraries/deeplink/src/test/kotlin/io/element/android/libraries/deeplink/DeepLinkCreatorTest.kt +++ b/libraries/deeplink/src/test/kotlin/io/element/android/libraries/deeplink/DeepLinkCreatorTest.kt @@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.test.A_THREAD_ID import org.junit.Test class DeepLinkCreatorTest { - @Test fun room() { val sut = DeepLinkCreator() diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt index e08f156340..338ad05349 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt @@ -38,8 +38,8 @@ import io.element.android.libraries.designsystem.R import io.element.android.libraries.designsystem.modifiers.blurCompat import io.element.android.libraries.designsystem.modifiers.blurredShapeShadow import io.element.android.libraries.designsystem.modifiers.canUseBlurMaskFilter -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight @Composable fun ElementLogoAtom( @@ -49,7 +49,6 @@ fun ElementLogoAtom( darkTheme: Boolean = isSystemInDarkTheme(), ) { val blur = if (darkTheme) 160.dp else 24.dp - //box-shadow: 0px 6.075949668884277px 24.30379867553711px 0px #1B1D2280; val shadowColor = if (darkTheme) size.shadowColorDark else size.shadowColorLight val logoShadowColor = if (darkTheme) size.logoShadowColorDark else size.logoShadowColorLight val backgroundColor = if (darkTheme) Color.White.copy(alpha = 0.2f) else Color.White.copy(alpha = 0.4f) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/PlaceholderAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/PlaceholderAtom.kt index a892667379..96c23c4ae7 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/PlaceholderAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/PlaceholderAtom.kt @@ -26,10 +26,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.theme.placeholderBackground import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.placeholderBackground @Composable fun PlaceholderAtom( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RedIndicatorAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RedIndicatorAtom.kt index 772bc723d7..8985446a68 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RedIndicatorAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RedIndicatorAtom.kt @@ -28,9 +28,9 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.compound.theme.ElementTheme @Composable fun RedIndicatorAtom( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt index 1bb7915fa3..ac0c7ec80a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt @@ -32,11 +32,11 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.temporaryColorBgSpecial -import io.element.android.compound.theme.ElementTheme /** * RoundedIconAtom is an atom which displays an icon inside a rounded container. diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/UnreadIndicatorAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/UnreadIndicatorAtom.kt index 99b0c8363c..a770cdcaec 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/UnreadIndicatorAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/UnreadIndicatorAtom.kt @@ -26,10 +26,10 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.theme.unreadIndicator import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.unreadIndicator @Composable fun UnreadIndicatorAtom( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonColumnMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonColumnMolecule.kt index bdd5fa4cf3..fd5be7c680 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonColumnMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonColumnMolecule.kt @@ -24,8 +24,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.OutlinedButton import io.element.android.libraries.designsystem.theme.components.TextButton diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonRowMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonRowMolecule.kt index 0d5afbe714..9388b54880 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonRowMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonRowMolecule.kt @@ -22,8 +22,8 @@ import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.TextButton @Composable diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/InfoListOrganism.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/InfoListOrganism.kt index 47ae6d46d0..e3611de5db 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/InfoListOrganism.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/InfoListOrganism.kt @@ -29,13 +29,13 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.atomic.molecules.InfoListItemMolecule import io.element.android.libraries.designsystem.atomic.molecules.InfoListItemPosition import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.ElementTheme import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt index 0c94df7bf2..c08eb5149a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt @@ -26,11 +26,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.ElementTheme /** * @param modifier Classical modifier. diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/OnBoardingPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/OnBoardingPage.kt index 7a66ac97a5..1b8aed72c2 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/OnBoardingPage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/OnBoardingPage.kt @@ -29,11 +29,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.R -import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.R +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Text /** * Page for onboarding screens, with content and optional footer. diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/AvatarColors.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/AvatarColors.kt index abac299ca6..ed56bde5ce 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/AvatarColors.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/AvatarColors.kt @@ -22,4 +22,3 @@ data class AvatarColors( val background: Color, val foreground: Color, ) - diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt index 701d2945ba..eb916345d1 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt @@ -171,11 +171,7 @@ fun AnnotatedString.linkify(linkStyle: SpanStyle): AnnotatedString { @Preview(group = PreviewGroup.Text) @Composable -internal fun ClickableLinkTextPreview() = - ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun ClickableLinkTextPreview() = ElementThemedPreview { ClickableLinkText( annotatedString = AnnotatedString("Hello", ParagraphStyle()), linkAnnotationTag = "", @@ -184,4 +180,3 @@ private fun ContentToPreview() { interactionSource = remember { MutableInteractionSource() }, ) } - diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledCheckbox.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledCheckbox.kt index 4076be8653..7a45fc2dda 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledCheckbox.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledCheckbox.kt @@ -54,10 +54,7 @@ fun LabelledCheckbox( @Preview(group = PreviewGroup.Toggles) @Composable -internal fun LabelledCheckboxPreview() = ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun LabelledCheckboxPreview() = ElementThemedPreview { LabelledCheckbox( checked = true, onCheckedChange = {}, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledOutlinedTextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledOutlinedTextField.kt index 9b31970da0..615abcb187 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledOutlinedTextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledOutlinedTextField.kt @@ -25,11 +25,11 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.OutlinedTextField import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.ElementTheme @Composable fun LabelledOutlinedTextField( @@ -83,4 +83,3 @@ internal fun LabelledOutlinedTextFieldPreview() = ElementPreview { ) } } - diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledTextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledTextField.kt index 027aeb8f95..9ea59545ee 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledTextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledTextField.kt @@ -25,11 +25,11 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextField -import io.element.android.compound.theme.ElementTheme @Composable fun LabelledTextField( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/PinIcon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/PinIcon.kt index 11bed90654..7ad7ec17ec 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/PinIcon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/PinIcon.kt @@ -24,11 +24,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.R -import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.R +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Icon @Composable fun PinIcon( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt index e678f7817c..d94a0486a4 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt @@ -35,10 +35,10 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties -import io.element.android.libraries.designsystem.theme.components.DialogPreview import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.DialogPreview import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.ui.strings.CommonStrings @@ -141,10 +141,7 @@ private fun ProgressDialogContent( @Preview(group = PreviewGroup.Dialogs) @Composable -internal fun ProgressDialogPreview() = ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun ProgressDialogPreview() = ElementThemedPreview { DialogPreview { ProgressDialogContent(text = "test dialog content", isCancellable = true) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncProvider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionProvider.kt similarity index 65% rename from libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncProvider.kt rename to libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionProvider.kt index 028de2d57b..d74f0de5ca 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncProvider.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionProvider.kt @@ -17,14 +17,15 @@ package io.element.android.libraries.designsystem.components.async import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction -open class AsyncProvider : PreviewParameterProvider> { - override val values: Sequence> +open class AsyncActionProvider : PreviewParameterProvider> { + override val values: Sequence> get() = sequenceOf( - Async.Uninitialized, - Async.Loading(), - Async.Failure(Exception("An error occurred")), - Async.Success(Unit), + AsyncAction.Uninitialized, + AsyncAction.Confirming, + AsyncAction.Loading, + AsyncAction.Failure(Exception("An error occurred")), + AsyncAction.Success(Unit), ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncView.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionView.kt similarity index 67% rename from libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncView.kt rename to libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionView.kt index d19dc08c6a..732a3473a3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncView.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionView.kt @@ -19,8 +19,9 @@ package io.element.android.libraries.designsystem.components.async import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.tooling.preview.PreviewParameter -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.components.ProgressDialog +import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.components.dialogs.ErrorDialogDefaults import io.element.android.libraries.designsystem.components.dialogs.RetryDialog @@ -28,52 +29,28 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight /** - * Render an Async object. + * Render an AsyncAction object. * - If Success, invoke the callback [onSuccess], only once. * - If Failure, display a dialog with the error, which can be transformed, using [errorMessage]. When * closed, [onErrorDismiss] will be invoked. If [onRetry] is not null, a retry button will be displayed. - * - When loading, display a loading dialog, if [showProgressDialog] is true, with on optional [progressText]. + * - When loading, display a loading dialog using [progressDialog]. Pass empty lambda to disable. */ @Composable -fun AsyncView( - async: Async, +fun AsyncActionView( + async: AsyncAction, onSuccess: (T) -> Unit, onErrorDismiss: () -> Unit, - showProgressDialog: Boolean = true, - progressText: String? = null, - errorTitle: @Composable (Throwable) -> String = { ErrorDialogDefaults.title }, - errorMessage: @Composable (Throwable) -> String = { it.message ?: it.toString() }, - onRetry: (() -> Unit)? = null, -) { - AsyncView( - async = async, - onSuccess = onSuccess, - onErrorDismiss = onErrorDismiss, - progressDialog = { - if (showProgressDialog) { - AsyncViewDefaults.ProgressDialog(progressText) - } - }, - errorTitle = errorTitle, - errorMessage = errorMessage, - onRetry = onRetry, - ) -} - -@Composable -fun AsyncView( - async: Async, - onSuccess: (T) -> Unit, - onErrorDismiss: () -> Unit, - progressDialog: @Composable () -> Unit = { AsyncViewDefaults.ProgressDialog() }, + confirmationDialog: @Composable () -> Unit = { }, + progressDialog: @Composable () -> Unit = { AsyncActionViewDefaults.ProgressDialog() }, errorTitle: @Composable (Throwable) -> String = { ErrorDialogDefaults.title }, errorMessage: @Composable (Throwable) -> String = { it.message ?: it.toString() }, onRetry: (() -> Unit)? = null, ) { when (async) { - Async.Uninitialized -> Unit - is Async.Loading -> progressDialog() - is Async.Failure -> { + AsyncAction.Uninitialized -> Unit + AsyncAction.Confirming -> confirmationDialog() + is AsyncAction.Loading -> progressDialog() + is AsyncAction.Failure -> { if (onRetry == null) { ErrorDialog( title = errorTitle(async.error), @@ -89,7 +66,7 @@ fun AsyncView( ) } } - is Async.Success -> { + is AsyncAction.Success -> { LaunchedEffect(async) { onSuccess(async.data) } @@ -97,7 +74,7 @@ fun AsyncView( } } -object AsyncViewDefaults { +object AsyncActionViewDefaults { @Composable fun ProgressDialog(progressText: String? = null) { ProgressDialog( @@ -108,12 +85,20 @@ object AsyncViewDefaults { @PreviewsDayNight @Composable -internal fun AsyncViewPreview( - @PreviewParameter(AsyncProvider::class) async: Async, +internal fun AsyncActionViewPreview( + @PreviewParameter(AsyncActionProvider::class) async: AsyncAction, ) = ElementPreview { - AsyncView( + AsyncActionView( async = async, onSuccess = {}, onErrorDismiss = {}, + confirmationDialog = { + ConfirmationDialog( + title = "Confirmation", + content = "Are you sure?", + onSubmitClicked = {}, + onDismiss = {}, + ) + }, ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt index e3186f5600..c6b226e055 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt @@ -26,8 +26,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.ui.strings.CommonStrings diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncLoading.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncLoading.kt index 7f6385658d..544f35c038 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncLoading.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncLoading.kt @@ -23,8 +23,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator @Composable diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt index 7bcb8bf1dd..810e0cae10 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt @@ -25,7 +25,6 @@ data class AvatarData( val url: String? = null, val size: AvatarSize, ) { - val initial by lazy { (name?.takeIf { it.isNotBlank() } ?: id) .let { dn -> diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/ButtonVisuals.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/ButtonVisuals.kt index 3da88c49a4..491d0637b0 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/ButtonVisuals.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/ButtonVisuals.kt @@ -29,7 +29,6 @@ import io.element.android.libraries.designsystem.theme.components.TextButton */ @Immutable sealed interface ButtonVisuals { - val action: () -> Unit /** diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt index 055843edae..e790cb1921 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt @@ -29,9 +29,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.airbnb.android.showkase.annotation.ShowkaseComposable import io.element.android.libraries.designsystem.components.list.CheckboxListItem -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.DialogPreview import io.element.android.libraries.designsystem.theme.components.ListSupportingText import io.element.android.libraries.designsystem.theme.components.SimpleAlertDialogContent diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt index 36869bf270..2462fd779f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt @@ -27,9 +27,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.airbnb.android.showkase.annotation.ShowkaseComposable import io.element.android.libraries.designsystem.components.list.RadioButtonListItem -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.DialogPreview import io.element.android.libraries.designsystem.theme.components.ListSupportingText import io.element.android.libraries.designsystem.theme.components.SimpleAlertDialogContent diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/keyboard/Keyboard.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/keyboard/Keyboard.kt index 1ebb67f75a..c96f7f5963 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/keyboard/Keyboard.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/keyboard/Keyboard.kt @@ -29,7 +29,8 @@ import androidx.lifecycle.Lifecycle * Inspired from https://stackoverflow.com/questions/68847559/how-can-i-detect-keyboard-opening-and-closing-in-jetpack-compose */ enum class Keyboard { - Opened, Closed + Opened, + Closed } // Note: it does not work as expected... diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SingleSelectionListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SingleSelectionListItem.kt index 01dff71991..d119025b9b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SingleSelectionListItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SingleSelectionListItem.kt @@ -166,7 +166,7 @@ internal fun SingleSelectionListItemCustomFormattertPreview() { options = listOptionOf("Option 1", "Option 2", "Option 3"), supportingText = "Supporting text", onSelectionChanged = {}, - resultFormatter = { "Selected index: $it"}, + resultFormatter = { "Selected index: $it" }, selected = 1, displayResultInTrailingContent = true, ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt index c26c12c7ea..f0e9458a02 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt @@ -24,11 +24,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.theme.components.OutlinedTextField import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.ElementTheme @Composable fun TextFieldListItem( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/DrawScopeWaveformExtensions.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/DrawScopeWaveformExtensions.kt index 9e09d97471..4723c8ffa9 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/DrawScopeWaveformExtensions.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/DrawScopeWaveformExtensions.kt @@ -23,23 +23,22 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.Fill import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import kotlinx.collections.immutable.ImmutableList import kotlin.math.max fun DrawScope.drawWaveform( waveformData: ImmutableList, - canvasSize: DpSize, + canvasSizePx: Size, brush: Brush, minimumGraphAmplitude: Float = 2F, lineWidth: Dp = 2.dp, linePadding: Dp = 2.dp, ) { - val centerY = canvasSize.height.toPx() / 2 + val centerY = canvasSizePx.height / 2 val cornerRadius = lineWidth / 2 waveformData.forEachIndexed { index, amplitude -> - val drawingAmplitude = max(minimumGraphAmplitude, amplitude * (canvasSize.height.toPx() - 2)) + val drawingAmplitude = max(minimumGraphAmplitude, amplitude * (canvasSizePx.height - 2)) drawRoundRect( brush = brush, topLeft = Offset( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveformPlaybackView.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveformPlaybackView.kt index 6921c7f809..6aa96cf6c5 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveformPlaybackView.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveformPlaybackView.kt @@ -40,12 +40,13 @@ import androidx.compose.ui.graphics.drawscope.Fill import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.RequestDisallowInterceptTouchEvent import androidx.compose.ui.input.pointer.pointerInteropFilter +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.compound.theme.ElementTheme import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList @@ -58,7 +59,7 @@ private const val DEFAULT_GRAPHICS_LAYER_ALPHA: Float = 0.99F * * @param playbackProgress The current playback progress, between 0 and 1. * @param showCursor Whether to show the cursor or not. - * @param waveform The waveform to display. Use [FakeWaveformFactory] to generate a fake waveform. + * @param waveform The waveform to display. Use [createFakeWaveform] to generate a fake waveform. * @param onSeek Callback when the user seeks the waveform. Called with a value between 0 and 1. * @param modifier The modifier to be applied to the view. * @param seekEnabled Whether the user can seek the waveform or not. @@ -103,6 +104,11 @@ fun WaveformPlaybackView( } } + val density = LocalDensity.current + val waveformWidthPx by remember { + derivedStateOf { with(density) { normalizedWaveformData.size * (lineWidth + linePadding).roundToPx().toFloat() } } + } + val requestDisallowInterceptTouchEvent = remember { RequestDisallowInterceptTouchEvent() } Canvas( modifier = Modifier @@ -110,19 +116,20 @@ fun WaveformPlaybackView( .graphicsLayer(alpha = DEFAULT_GRAPHICS_LAYER_ALPHA) .let { if (!seekEnabled) return@let it - it.pointerInteropFilter(requestDisallowInterceptTouchEvent = requestDisallowInterceptTouchEvent) { e -> return@pointerInteropFilter when (e.action) { MotionEvent.ACTION_DOWN -> { - if (e.x in 0F..canvasSizePx.width) { + if (e.x in 0F..waveformWidthPx) { requestDisallowInterceptTouchEvent.invoke(true) - seekProgress.value = e.x / canvasSizePx.width + seekProgress.value = e.x / waveformWidthPx true - } else false + } else { + false + } } MotionEvent.ACTION_MOVE -> { - if (e.x in 0F..canvasSizePx.width) { - seekProgress.value = e.x / canvasSizePx.width + if (e.x in 0F..waveformWidthPx) { + seekProgress.value = e.x / waveformWidthPx } true } @@ -140,11 +147,11 @@ fun WaveformPlaybackView( ) { canvasSize = size.toDpSize() canvasSizePx = size - val centerY = canvasSize.height.toPx() / 2 val cornerRadius = lineWidth / 2 + // Calculate the size of the waveform by summing the width of all the lines and paddings drawWaveform( waveformData = normalizedWaveformData, - canvasSize = canvasSize, + canvasSizePx = canvasSizePx, brush = brush, lineWidth = lineWidth, linePadding = linePadding @@ -152,8 +159,8 @@ fun WaveformPlaybackView( drawRect( brush = progressBrush, size = Size( - width = progressAnimated.value * canvasSize.width.toPx(), - height = canvasSize.height.toPx() + width = progressAnimated.value * waveformWidthPx, + height = canvasSizePx.height ), blendMode = BlendMode.SrcAtop ) @@ -161,12 +168,12 @@ fun WaveformPlaybackView( drawRoundRect( brush = cursorBrush, topLeft = Offset( - x = progressAnimated.value * canvasSize.width.toPx(), - y = centerY - (canvasSize.height.toPx() - 2) / 2 + x = progressAnimated.value * waveformWidthPx, + y = 1f ), size = Size( width = lineWidth.toPx(), - height = canvasSize.height.toPx() - 2 + height = canvasSizePx.height - 2 ), cornerRadius = CornerRadius(cornerRadius.toPx(), cornerRadius.toPx()), style = Fill diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCategory.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCategory.kt index 03631634a6..42dc343782 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCategory.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCategory.kt @@ -24,11 +24,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.ElementTheme -import io.element.android.libraries.designsystem.icons.CompoundDrawables @Composable fun PreferenceCategory( @@ -68,10 +68,7 @@ private fun PreferenceCategoryTitle(title: String, modifier: Modifier = Modifier @Preview(group = PreviewGroup.Preferences) @Composable -internal fun PreferenceCategoryPreview() = ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun PreferenceCategoryPreview() = ElementThemedPreview { PreferenceCategory( title = "Category title", ) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCheckbox.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCheckbox.kt index 68a29c9795..579223cace 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCheckbox.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCheckbox.kt @@ -95,10 +95,7 @@ fun PreferenceCheckbox( @Preview(group = PreviewGroup.Preferences) @Composable -internal fun PreferenceCheckboxPreview() = ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun PreferenceCheckboxPreview() = ElementThemedPreview { Column { PreferenceCheckbox( title = "Checkbox", diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDivider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDivider.kt index 1727c68ca5..27a846aa10 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDivider.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDivider.kt @@ -19,10 +19,10 @@ package io.element.android.libraries.designsystem.components.preferences import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.theme.components.HorizontalDivider -import io.element.android.compound.theme.ElementTheme @Composable fun PreferenceDivider( @@ -36,9 +36,6 @@ fun PreferenceDivider( @Preview(group = PreviewGroup.Preferences) @Composable -internal fun PreferenceDividerPreview() = ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun PreferenceDividerPreview() = ElementThemedPreview { PreferenceDivider() } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferencePage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferencePage.kt index 66c99fda45..063c17aac1 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferencePage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferencePage.kt @@ -95,7 +95,6 @@ private fun PreferenceTopAppBar( overflow = TextOverflow.Ellipsis ) } - ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceRow.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceRow.kt index ef1c6ac09a..80813a2d1a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceRow.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceRow.kt @@ -50,10 +50,7 @@ fun PreferenceRow( @Preview(group = PreviewGroup.Preferences) @Composable -internal fun PreferenceRowPreview() = ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun PreferenceRowPreview() = ElementThemedPreview { PreferenceRow { Text(text = "Content") } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt index 4b125480f7..f0ad10efcb 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt @@ -90,10 +90,7 @@ fun PreferenceSlide( @Preview(group = PreviewGroup.Preferences) @Composable -internal fun PreferenceSlidePreview() = ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun PreferenceSlidePreview() = ElementThemedPreview { PreferenceSlide( iconResourceId = CompoundDrawables.ic_user_profile, title = "Slide", diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt index 3d0ceb87c2..5e2a2c267a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt @@ -102,10 +102,7 @@ fun PreferenceSwitch( @Preview(group = PreviewGroup.Preferences) @Composable -internal fun PreferenceSwitchPreview() = ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun PreferenceSwitchPreview() = ElementThemedPreview { PreferenceSwitch( title = "Switch", subtitle = "Subtitle Switch", diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt index 648ea97434..0c3735547b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt @@ -55,7 +55,7 @@ fun PreferenceTextField( style: ListItemStyle = ListItemStyle.Default, ) { var displayTextFieldDialog by rememberSaveable { mutableStateOf(false) } - val valueToDisplay = if (displayValue(value)) { value } else supportingText + val valueToDisplay = if (displayValue(value)) value else supportingText ListItem( modifier = modifier, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt index cd7e36d611..748f6dcd32 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt @@ -41,7 +41,7 @@ internal val iconsOther = listOf( R.drawable.ic_location_navigator_centered, R.drawable.ic_new_message, R.drawable.ic_numbered_list, - R.drawable.ic_plus, + R.drawable.ic_plus_composer, R.drawable.ic_quote, R.drawable.ic_reply, R.drawable.ic_retry, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt index a131410646..8bd7d2b16e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt @@ -55,7 +55,7 @@ internal class OtherIconListPreviewProvider : PreviewParameterProvider - IconChunk(index = index+1, total = chunks.size, icons = chunk.toPersistentList()) + IconChunk(index = index + 1, total = chunks.size, icons = chunk.toPersistentList()) } .asSequence() } @@ -76,7 +76,8 @@ internal fun IconsCompoundPreview(@PreviewParameter(CompoundIconListPreviewProvi iconNameTransform = { name -> name.removePrefix("ic_compound_") .replace("_", " ") - }) + } + ) } @PreviewsDayNight @@ -88,7 +89,8 @@ internal fun IconsOtherPreview(@PreviewParameter(OtherIconListPreviewProvider::c iconNameTransform = { name -> name.removePrefix("ic_") .replace("_", " ") - }) + } + ) } @Composable diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CircularReveal.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CircularReveal.kt index 9675c54a20..3f9ea0040e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CircularReveal.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CircularReveal.kt @@ -78,10 +78,11 @@ fun Modifier.circularReveal( val path = Path() val center = revealFrom.mapTo(size) val radius = calculateRadius(revealFrom, size) - val scrimColor = if (showScrim) + val scrimColor = if (showScrim) { Color.Gray - else + } else { Color.Transparent + } path.addOval(Rect(center, radius * transitionProgress.value)) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt index a7fba965dc..e489419e8c 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt @@ -18,8 +18,8 @@ package io.element.android.libraries.designsystem.preview import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable -import io.element.android.libraries.designsystem.theme.components.Surface import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.theme.components.Surface @Composable @Suppress("ModifierMissing") diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewGroup.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewGroup.kt index a1f458ee08..494f3a404b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewGroup.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewGroup.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.designsystem.preview +@Suppress("ktlint:standard:property-naming") object PreviewGroup { const val AppBars = "App Bars" const val Avatars = "Avatars" diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/HorizontalRuler.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/HorizontalRuler.kt index f5636aaf10..545abd4291 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/HorizontalRuler.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/HorizontalRuler.kt @@ -26,8 +26,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight /** * Horizontal ruler is a debug composable that displays a horizontal ruler. diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/VerticalRuler.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/VerticalRuler.kt index 057cb543b3..7d1f879e74 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/VerticalRuler.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/VerticalRuler.kt @@ -26,8 +26,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight /** * Vertical ruler is a debug composable that displays a vertical ruler. diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/WithRulers.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/WithRulers.kt index fd8f6ba37a..c24b34271f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/WithRulers.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/WithRulers.kt @@ -21,8 +21,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.ButtonSize import io.element.android.libraries.designsystem.theme.components.OutlinedButton diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/DpScale.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/DpScale.kt index a593e81f7b..9db1255df3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/DpScale.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/DpScale.kt @@ -25,10 +25,10 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.WithFontScale import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.ElementTheme /** * Return the maximum value between the receiver value and the value with fontScale applied. diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt index d81484d512..938cbf03c6 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt @@ -109,7 +109,7 @@ val SemanticColors.pinDigitBg } val SemanticColors.currentUserMentionPillText - get() = if (isLight) { + get() = if (isLight) { // We want LightDesignTokens.colorGreen1100 Color(0xff005c45) } else { @@ -118,7 +118,7 @@ val SemanticColors.currentUserMentionPillText } val SemanticColors.currentUserMentionPillBackground - get() = if (isLight) { + get() = if (isLight) { // We want LightDesignTokens.colorGreenAlpha400 Color(0x3b07b661) } else { @@ -130,7 +130,7 @@ val SemanticColors.mentionPillText get() = textPrimary val SemanticColors.mentionPillBackground - get() = if (isLight) { + get() = if (isLight) { // We want LightDesignTokens.colorGray400 Color(0x1f052e61) } else { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt index 70a906f758..8ea42eef88 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt @@ -232,10 +232,11 @@ internal fun AlertDialogContent( // Align the title to the center when an icon is present. Modifier .then( - if (applyPaddingToContents) + if (applyPaddingToContents) { Modifier - else + } else { Modifier.padding(DialogContentDefaults.externalHorizontalPadding) + } ) .padding(DialogContentDefaults.titlePadding) .align( @@ -364,8 +365,10 @@ private fun AlertDialogFlowRow( val mainAxisPositions = IntArray(childrenMainAxisSizes.size) { 0 } with(arrangement) { arrange( - mainAxisLayoutSize, childrenMainAxisSizes, - layoutDirection, mainAxisPositions + mainAxisLayoutSize, + childrenMainAxisSizes, + layoutDirection, + mainAxisPositions ) } placeables.forEachIndexed { j, placeable -> @@ -444,7 +447,8 @@ internal fun DialogWithTitleIconAndOkButtonPreview() { ) }, title = "Dialog Title", - content = "A dialog is a type of modal window that appears in front of app content to provide critical information, or prompt for a decision to be made. Learn more", + content = "A dialog is a type of modal window that appears in front of app content to provide critical information," + + " or prompt for a decision to be made. Learn more", submitText = "OK", onSubmitClicked = {}, ) @@ -460,7 +464,8 @@ internal fun DialogWithTitleAndOkButtonPreview() { DialogPreview { SimpleAlertDialogContent( title = "Dialog Title", - content = "A dialog is a type of modal window that appears in front of app content to provide critical information, or prompt for a decision to be made. Learn more", + content = "A dialog is a type of modal window that appears in front of app content to provide critical information," + + " or prompt for a decision to be made. Learn more", submitText = "OK", onSubmitClicked = {}, ) @@ -475,7 +480,8 @@ internal fun DialogWithOnlyMessageAndOkButtonPreview() { ElementThemedPreview(showBackground = false) { DialogPreview { SimpleAlertDialogContent( - content = "A dialog is a type of modal window that appears in front of app content to provide critical information, or prompt for a decision to be made. Learn more", + content = "A dialog is a type of modal window that appears in front of app content to provide critical information," + + " or prompt for a decision to be made. Learn more", submitText = "OK", onSubmitClicked = {}, ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/BottomSheetDragHandle.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/BottomSheetDragHandle.kt index da1bf0e4fe..ae331e7ba1 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/BottomSheetDragHandle.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/BottomSheetDragHandle.kt @@ -33,9 +33,9 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.compound.theme.ElementTheme @Composable fun BottomSheetDragHandle( @@ -68,7 +68,6 @@ fun BottomSheetDragHandle( } } - @PreviewsDayNight @Composable internal fun BottomSheetDragHandlePreview() = ElementPreview { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt index fe0d807745..a1f6e8ebcc 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt @@ -147,25 +147,29 @@ private fun ButtonInternal( val contentPadding = when (size) { ButtonSize.Medium -> when (style) { ButtonStyle.Filled, - ButtonStyle.Outlined -> if (hasStartDrawable) + ButtonStyle.Outlined -> if (hasStartDrawable) { PaddingValues(start = 16.dp, top = 10.dp, end = 24.dp, bottom = 10.dp) - else + } else { PaddingValues(start = 24.dp, top = 10.dp, end = 24.dp, bottom = 10.dp) - ButtonStyle.Text -> if (hasStartDrawable) + } + ButtonStyle.Text -> if (hasStartDrawable) { PaddingValues(start = 12.dp, top = 10.dp, end = 16.dp, bottom = 10.dp) - else + } else { PaddingValues(start = 12.dp, top = 10.dp, end = 12.dp, bottom = 10.dp) + } } ButtonSize.Large -> when (style) { ButtonStyle.Filled, - ButtonStyle.Outlined -> if (hasStartDrawable) + ButtonStyle.Outlined -> if (hasStartDrawable) { PaddingValues(start = 24.dp, top = 13.dp, end = 32.dp, bottom = 13.dp) - else + } else { PaddingValues(start = 32.dp, top = 13.dp, end = 32.dp, bottom = 13.dp) - ButtonStyle.Text -> if (hasStartDrawable) + } + ButtonStyle.Text -> if (hasStartDrawable) { PaddingValues(start = 12.dp, top = 13.dp, end = 16.dp, bottom = 13.dp) - else + } else { PaddingValues(start = 16.dp, top = 13.dp, end = 16.dp, bottom = 13.dp) + } } } @@ -179,12 +183,13 @@ private fun ButtonInternal( ButtonStyle.Filled -> null ButtonStyle.Outlined -> BorderStroke( width = 1.dp, - color = if (destructive) + color = if (destructive) { ElementTheme.colors.borderCriticalPrimary.copy( alpha = if (enabled) 1f else 0.5f ) - else + } else { ElementTheme.colors.borderInteractiveSecondary + } ) ButtonStyle.Text -> null } @@ -254,11 +259,14 @@ sealed interface IconSource { } enum class ButtonSize { - Medium, Large + Medium, + Large } internal enum class ButtonStyle { - Filled, Outlined, Text; + Filled, + Outlined, + Text; @Composable fun getColors(destructive: Boolean): ButtonColors = when (this) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Checkbox.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Checkbox.kt index 95cf0b412e..007755ddff 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Checkbox.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Checkbox.kt @@ -31,9 +31,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup -import io.element.android.compound.theme.ElementTheme // Designs in https://www.figma.com/file/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?type=design&mode=design&t=qb99xBP5mwwCtGkN-1 @@ -90,10 +90,7 @@ private fun compoundErrorCheckBoxColors(): CheckboxColors { @Preview(group = PreviewGroup.Toggles) @Composable -internal fun CheckboxesPreview() = ElementThemedPreview(vertical = false) { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun CheckboxesPreview() = ElementThemedPreview(vertical = false) { Column { // Unchecked Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/CircularProgressIndicator.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/CircularProgressIndicator.kt index 5c869db9eb..18ec93ae8f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/CircularProgressIndicator.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/CircularProgressIndicator.kt @@ -69,14 +69,10 @@ fun CircularProgressIndicator( @Preview(group = PreviewGroup.Progress) @Composable -internal fun CircularProgressIndicatorPreview() = ElementThemedPreview(vertical = false) { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun CircularProgressIndicatorPreview() = ElementThemedPreview(vertical = false) { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { // Indeterminate progress - CircularProgressIndicator( - ) + CircularProgressIndicator() // Fixed progress CircularProgressIndicator( progress = { 0.90F } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenuItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenuItem.kt index 3e49018166..e9b65c5041 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenuItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenuItem.kt @@ -78,10 +78,7 @@ internal object DropDownMenuItemDefaults { @Preview(group = PreviewGroup.Menus) @Composable -internal fun DropdownMenuItemPreview() = ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun DropdownMenuItemPreview() = ElementThemedPreview { Column { DropdownMenuItem( text = { Text(text = "Item") }, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FloatingActionButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FloatingActionButton.kt index 9a01cfe69c..f5f8f6b130 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FloatingActionButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FloatingActionButton.kt @@ -38,7 +38,8 @@ import io.element.android.libraries.designsystem.preview.PreviewGroup fun FloatingActionButton( onClick: () -> Unit, modifier: Modifier = Modifier, - shape: Shape = CircleShape, // FloatingActionButtonDefaults.shape, + // FloatingActionButtonDefaults.shape + shape: Shape = CircleShape, containerColor: Color = FloatingActionButtonDefaults.containerColor, contentColor: Color = contentColorFor(containerColor), elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), @@ -59,11 +60,7 @@ fun FloatingActionButton( @Preview(group = PreviewGroup.FABs) @Composable -internal fun FloatingActionButtonPreview() = - ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun FloatingActionButtonPreview() = ElementThemedPreview { Box(modifier = Modifier.padding(8.dp)) { FloatingActionButton(onClick = {}) { Icon(imageVector = CompoundIcons.Close, contentDescription = null) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/HorizontalDivider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/HorizontalDivider.kt index 1a3f91a431..87f51e4665 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/HorizontalDivider.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/HorizontalDivider.kt @@ -50,11 +50,6 @@ object ElementDividerDefaults { @Composable internal fun HorizontalDividerPreview() = ElementThemedPreview { Box(Modifier.padding(vertical = 10.dp), contentAlignment = Alignment.Center) { - ContentToPreview() + HorizontalDivider() } } - -@Composable -private fun ContentToPreview() { - HorizontalDivider() -} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt index fb36f5e3e6..a4a893f3c1 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt @@ -140,10 +140,6 @@ fun Icon( @Preview(group = PreviewGroup.Icons) @Composable -internal fun IconImageVectorPreview() = - ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun IconImageVectorPreview() = ElementThemedPreview { Icon(imageVector = CompoundIcons.Close, contentDescription = null) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconButton.kt index f0a9c12ac0..f2de08b00a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconButton.kt @@ -58,11 +58,7 @@ fun IconButton( @Preview(group = PreviewGroup.Buttons) @Composable -internal fun IconButtonPreview() = - ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun IconButtonPreview() = ElementThemedPreview { Column { CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.iconPrimary) { Row { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt index 92bc5c7605..b6ac1a4cf7 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt @@ -59,10 +59,7 @@ fun IconToggleButton( @Preview(group = PreviewGroup.Toggles) @Composable -internal fun IconToggleButtonPreview() = ElementThemedPreview(vertical = false) { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun IconToggleButtonPreview() = ElementThemedPreview(vertical = false) { var checked by remember { mutableStateOf(false) } Column { Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/LinearProgressIndicator.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/LinearProgressIndicator.kt index 6d081da298..ca676d1d27 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/LinearProgressIndicator.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/LinearProgressIndicator.kt @@ -74,14 +74,10 @@ fun LinearProgressIndicator( @Preview(group = PreviewGroup.Progress) @Composable -internal fun LinearProgressIndicatorPreview() = ElementThemedPreview(vertical = false) { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun LinearProgressIndicatorPreview() = ElementThemedPreview(vertical = false) { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { // Indeterminate progress - LinearProgressIndicator( - ) + LinearProgressIndicator() // Fixed progress LinearProgressIndicator( progress = { 0.90F } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt index ebb8358321..081cc02e02 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt @@ -119,9 +119,13 @@ fun ListItem( androidx.compose.material3.ListItem( headlineContent = decoratedHeadlineContent, - modifier = if (onClick != null) Modifier - .clickable(enabled = enabled, onClick = onClick) - .then(modifier) else modifier, + modifier = if (onClick != null) { + Modifier + .clickable(enabled = enabled, onClick = onClick) + .then(modifier) + } else { + modifier + }, overlineContent = null, supportingContent = decoratedSupportingContent, leadingContent = decoratedLeadingContent, @@ -374,7 +378,6 @@ internal fun ListItemDisabledWithIconPreview() = PreviewItems.OneLineListItemPre @Suppress("ModifierMissing") private object PreviewItems { - @Composable fun ThreeLinesListItemPreview( modifier: Modifier = Modifier, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSectionHeader.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSectionHeader.kt index 4ad6a3c48d..b50d0c985e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSectionHeader.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSectionHeader.kt @@ -27,9 +27,9 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup -import io.element.android.compound.theme.ElementTheme // Designs: https://www.figma.com/file/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?type=design&node-id=425%3A24208&mode=design&t=G5hCfkLB6GgXDuWe-1 diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSupportingText.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSupportingText.kt index e6305d4dc2..aca326279d 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSupportingText.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSupportingText.kt @@ -81,7 +81,6 @@ fun ListSupportingText( } object ListSupportingTextDefaults { - /** Specifies the padding to use for the supporting text. */ @Immutable sealed interface Padding { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/MediumTopAppBar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/MediumTopAppBar.kt index 754c5dde9c..131b2bb8ca 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/MediumTopAppBar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/MediumTopAppBar.kt @@ -59,14 +59,10 @@ fun MediumTopAppBar( ) } +@OptIn(ExperimentalMaterial3Api::class) @Preview(group = PreviewGroup.AppBars) @Composable -internal fun MediumTopAppBarPreview() = - ElementThemedPreview { ContentToPreview() } - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun ContentToPreview() { +internal fun MediumTopAppBarPreview() = ElementThemedPreview { MediumTopAppBar( title = { Text(text = "Title") }, navigationIcon = { BackButton(onClick = {}) }, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheet.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheet.kt index 234a0e0b60..6230592edd 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheet.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheet.kt @@ -34,11 +34,12 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.preview.sheetStateForPreview -import io.element.android.compound.theme.ElementTheme import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -93,6 +94,7 @@ internal fun ModalBottomSheetDarkPreview() = ElementPreviewDark { ContentToPreview() } @OptIn(ExperimentalMaterial3Api::class) +@ExcludeFromCoverage @Composable private fun ContentToPreview() { Box( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheetLayout.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheetLayout.kt index 30e5882f26..8dd63d1cc6 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheetLayout.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheetLayout.kt @@ -47,6 +47,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.modifiers.applyIf import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight @@ -110,6 +111,7 @@ internal fun ModalBottomSheetLayoutDarkPreview() = ElementPreviewDark { ContentToPreview() } @OptIn(ExperimentalMaterialApi::class) +@ExcludeFromCoverage @Composable private fun ContentToPreview() { ModalBottomSheetLayout( @@ -117,9 +119,12 @@ private fun ContentToPreview() { displayHandle = true, sheetState = ModalBottomSheetState(ModalBottomSheetValue.Expanded, density = LocalDensity.current), sheetContent = { - Text(text = "Sheet Content", modifier = Modifier - .padding(start = 16.dp, end = 16.dp, bottom = 20.dp) - .background(color = Color.Green)) + Text( + text = "Sheet Content", + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 20.dp) + .background(color = Color.Green) + ) } ) { Text(text = "Content", modifier = Modifier.background(color = Color.Red)) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/OutlinedTextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/OutlinedTextField.kt index 2c7318c447..f05c72be20 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/OutlinedTextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/OutlinedTextField.kt @@ -43,6 +43,7 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.PreviewGroup @@ -166,6 +167,7 @@ internal fun OutlinedTextFieldsPreview() = ElementPreviewLight { ContentToPrevie internal fun OutlinedTextFieldsDarkPreview() = ElementPreviewDark { ContentToPreview() } @Composable +@ExcludeFromCoverage private fun ContentToPreview() { Column(modifier = Modifier.padding(4.dp)) { allBooleans.forEach { isError -> diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt index 038e74d982..3b438b5f2f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt @@ -30,9 +30,9 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup -import io.element.android.compound.theme.ElementTheme // Designs in https://www.figma.com/file/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?type=design&node-id=425%3A24202&mode=design&t=qb99xBP5mwwCtGkN-1 @@ -66,10 +66,7 @@ internal fun compoundRadioButtonColors(): RadioButtonColors { @Preview(group = PreviewGroup.Toggles) @Composable -internal fun RadioButtonPreview() = ElementThemedPreview(vertical = false) { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun RadioButtonPreview() = ElementThemedPreview(vertical = false) { var checked by remember { mutableStateOf(false) } Column { Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt index 7e208b88d9..470f0d7b4e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt @@ -59,7 +59,7 @@ fun SearchBar( modifier: Modifier = Modifier, enabled: Boolean = true, showBackButton: Boolean = true, - resultState: SearchBarResultState = SearchBarResultState.NotSearching(), + resultState: SearchBarResultState = SearchBarResultState.Initial(), shape: Shape = SearchBarDefaults.inputFieldShape, tonalElevation: Dp = SearchBarDefaults.TonalElevation, windowInsets: WindowInsets = SearchBarDefaults.windowInsets, @@ -129,7 +129,7 @@ fun SearchBar( resultHandler(resultState.results) } - is SearchBarResultState.NoResults -> { + is SearchBarResultState.NoResultsFound -> { // No results found, show a message Spacer(Modifier.size(80.dp)) @@ -151,7 +151,6 @@ fun SearchBar( } object ElementSearchBarDefaults { - @OptIn(ExperimentalMaterial3Api::class) @Composable fun inactiveColors() = SearchBarDefaults.colors( @@ -184,10 +183,10 @@ object ElementSearchBarDefaults { @Immutable sealed interface SearchBarResultState { /** No search results are available yet (e.g. because the user hasn't entered a search term). */ - class NotSearching : SearchBarResultState + class Initial : SearchBarResultState /** The search has completed, but no results were found. */ - class NoResults : SearchBarResultState + class NoResultsFound : SearchBarResultState /** The search has completed, and some matching users were found. */ data class Results(val results: T) : SearchBarResultState @@ -199,7 +198,7 @@ internal fun SearchBarInactivePreview() = ElementThemedPreview { ContentToPrevie @Preview(group = PreviewGroup.Search) @Composable -internal fun SearchBarActiveEmptyQueryPreview() = ElementThemedPreview { +internal fun SearchBarActiveNoneQueryPreview() = ElementThemedPreview { ContentToPreview( query = "", active = true, @@ -231,7 +230,7 @@ internal fun SearchBarActiveWithNoResultsPreview() = ElementThemedPreview { ContentToPreview( query = "search term", active = true, - resultState = SearchBarResultState.NoResults(), + resultState = SearchBarResultState.NoResultsFound(), ) } @@ -257,16 +256,15 @@ internal fun SearchBarActiveWithContentPreview() = ElementThemedPreview { .background(color = Color.Blue) .fillMaxWidth() ) - }, - resultHandler = { - Text( - text = "Results go here", - modifier = Modifier - .background(color = Color.Green) - .fillMaxWidth() - ) } - ) + ) { + Text( + text = "Results go here", + modifier = Modifier + .background(color = Color.Green) + .fillMaxWidth() + ) + } } @OptIn(ExperimentalMaterial3Api::class) @@ -275,7 +273,7 @@ private fun ContentToPreview( query: String = "", active: Boolean = false, showBackButton: Boolean = true, - resultState: SearchBarResultState = SearchBarResultState.NotSearching(), + resultState: SearchBarResultState = SearchBarResultState.Initial(), contentPrefix: @Composable ColumnScope.() -> Unit = {}, contentSuffix: @Composable ColumnScope.() -> Unit = {}, resultHandler: @Composable ColumnScope.(String) -> Unit = {}, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt index 2041d7998d..65f43bb250 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt @@ -37,7 +37,7 @@ fun Slider( modifier: Modifier = Modifier, enabled: Boolean = true, valueRange: ClosedFloatingPointRange = 0f..1f, - /*@IntRange(from = 0)*/ + // @IntRange(from = 0) steps: Int = 0, onValueChangeFinished: (() -> Unit)? = null, colors: SliderColors = SliderDefaults.colors(), @@ -58,10 +58,7 @@ fun Slider( @Preview(group = PreviewGroup.Sliders) @Composable -internal fun SlidersPreview() = ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun SlidersPreview() = ElementThemedPreview { var value by remember { mutableFloatStateOf(0.33f) } Column { Slider(onValueChange = { value = it }, value = value, enabled = true) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Surface.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Surface.kt index db7ab7fc08..cda49bdd61 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Surface.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Surface.kt @@ -56,11 +56,7 @@ fun Surface( @Preview @Composable -internal fun SurfacePreview() = - ElementThemedPreview { ContentToPreview() } - -@Composable -private fun ContentToPreview() { +internal fun SurfacePreview() = ElementThemedPreview { Surface { Spacer(modifier = Modifier.size(64.dp)) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Switch.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Switch.kt index b11cf433e0..7efb9d52eb 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Switch.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Switch.kt @@ -32,9 +32,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup -import io.element.android.compound.theme.ElementTheme import androidx.compose.material3.Switch as Material3Switch // Designs in https://www.figma.com/file/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?type=design&node-id=425%3A24203&mode=design&t=qb99xBP5mwwCtGkN-1 diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Text.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Text.kt index 21330d0726..08b8860e08 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Text.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Text.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.element.android.compound.utils.toHrf +import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.PreviewGroup @@ -120,6 +121,7 @@ internal fun TextLightPreview() = ElementPreviewLight { ContentToPreview() } @Composable internal fun TextDarkPreview() = ElementPreviewDark { ContentToPreview() } +@ExcludeFromCoverage @Composable private fun ContentToPreview() { val colors = mapOf( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt index 2ad41887aa..e5f0434c50 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt @@ -44,6 +44,7 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.PreviewGroup @@ -154,6 +155,7 @@ internal fun TextFieldLightPreview() = internal fun TextFieldDarkPreview() = ElementPreviewDark { ContentToPreview() } +@ExcludeFromCoverage @Composable private fun ContentToPreview() { Column(modifier = Modifier.padding(4.dp)) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt index d8bf1d7016..74dcad4fea 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt @@ -59,14 +59,10 @@ fun TopAppBar( ) } +@OptIn(ExperimentalMaterial3Api::class) @Preview(group = PreviewGroup.AppBars) @Composable -internal fun TopAppBarPreview() = - ElementThemedPreview { ContentToPreview() } - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun ContentToPreview() { +internal fun TopAppBarPreview() = ElementThemedPreview { TopAppBar( title = { Text(text = "Title") }, navigationIcon = { BackButton(onClick = {}) }, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/bottomsheet/CustomBottomSheetScaffold.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/bottomsheet/CustomBottomSheetScaffold.kt index 22b55c19af..1cccaf0744 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/bottomsheet/CustomBottomSheetScaffold.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/bottomsheet/CustomBottomSheetScaffold.kt @@ -282,11 +282,13 @@ private fun CustomStandardBottomSheet( if (anchoredDraggableState.anchors.size > 1 && sheetSwipeEnabled) { if (currentValue == SheetValue.PartiallyExpanded) { expand(expandActionLabel) { - scope.launch { expand() }; true + scope.launch { expand() } + true } } else { collapse(partialExpandActionLabel) { - scope.launch { partialExpand() }; true + scope.launch { partialExpand() } + true } } if (!state.skipHiddenState) { @@ -314,7 +316,6 @@ private fun CustomStandardBottomSheet( */ @ExperimentalFoundationApi class DraggableAnchorsConfig { - internal val anchors = mutableMapOf() /** @@ -344,7 +345,6 @@ internal fun DraggableAnchors( ): DraggableAnchors = MapDraggableAnchors(DraggableAnchorsConfig().apply(builder).anchors) private class MapDraggableAnchors(private val anchors: Map) : DraggableAnchors { - override fun positionOf(value: T): Float = anchors[value] ?: Float.NaN override fun hasAnchorFor(value: T) = anchors.containsKey(value) @@ -498,10 +498,10 @@ internal fun rememberSheetState( initialValue: SheetValue = SheetValue.Hidden, skipHiddenState: Boolean = false, ): CustomSheetState { - val density = LocalDensity.current return rememberSaveable( - skipPartiallyExpanded, confirmValueChange, + skipPartiallyExpanded, + confirmValueChange, saver = CustomSheetState.Saver( skipPartiallyExpanded = skipPartiallyExpanded, confirmValueChange = confirmValueChange, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/bottomsheet/CustomSheetState.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/bottomsheet/CustomSheetState.kt index 9a2ff713db..d861a38a6a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/bottomsheet/CustomSheetState.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/bottomsheet/CustomSheetState.kt @@ -35,21 +35,22 @@ import kotlinx.coroutines.CancellationException @OptIn(ExperimentalFoundationApi::class) @Stable @ExperimentalMaterial3Api -class CustomSheetState @Deprecated( +class CustomSheetState +@Deprecated( message = "This constructor is deprecated. " + - "Please use the constructor that provides a [Density]", + "Please use the constructor that provides a [Density]", replaceWith = ReplaceWith( "SheetState(" + - "skipPartiallyExpanded, LocalDensity.current, initialValue, " + - "confirmValueChange, skipHiddenState)" + "skipPartiallyExpanded, LocalDensity.current, initialValue, " + + "confirmValueChange, skipHiddenState)" ) -) constructor( +) +constructor( internal val skipPartiallyExpanded: Boolean, initialValue: SheetValue = Hidden, confirmValueChange: (SheetValue) -> Boolean = { true }, internal val skipHiddenState: Boolean = false, ) { - /** * State of a sheet composable, such as [ModalBottomSheet] * @@ -77,11 +78,12 @@ class CustomSheetState @Deprecated( ) : this(skipPartiallyExpanded, initialValue, confirmValueChange, skipHiddenState) { this.density = density } + init { if (skipPartiallyExpanded) { require(initialValue != PartiallyExpanded) { "The initial value must not be set to PartiallyExpanded if skipPartiallyExpanded " + - "is set to true." + "is set to true." } } if (skipHiddenState) { @@ -168,7 +170,7 @@ class CustomSheetState @Deprecated( suspend fun partialExpand() { check(!skipPartiallyExpanded) { "Attempted to animate to partial expanded when skipPartiallyExpanded was enabled. Set" + - " skipPartiallyExpanded to false to use this function." + " skipPartiallyExpanded to false to use this function." } animateTo(PartiallyExpanded) } @@ -194,7 +196,7 @@ class CustomSheetState @Deprecated( suspend fun hide() { check(!skipHiddenState) { "Attempted to animate to hidden when skipHiddenState was enabled. Set skipHiddenState" + - " to false to use this function." + " to false to use this function." } animateTo(Hidden) } @@ -254,7 +256,7 @@ class CustomSheetState @Deprecated( internal var density: Density? = null private fun requireDensity() = requireNotNull(density) { "SheetState did not have a density attached. Are you using SheetState with " + - "BottomSheetScaffold or ModalBottomSheet component?" + "BottomSheetScaffold or ModalBottomSheet component?" } companion object { @@ -278,7 +280,7 @@ class CustomSheetState @Deprecated( */ @Deprecated( message = "This function is deprecated. Please use the overload where Density is" + - " provided.", + " provided.", replaceWith = ReplaceWith( "Saver(skipPartiallyExpanded, confirmValueChange, LocalDensity.current)" ) @@ -290,7 +292,7 @@ class CustomSheetState @Deprecated( ) = Saver( save = { it.currentValue }, restore = { savedValue -> - CustomSheetState(skipPartiallyExpanded, savedValue, confirmValueChange) + CustomSheetState(skipPartiallyExpanded, savedValue, confirmValueChange) } ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt index 1b2bfd2188..fc25d05750 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt @@ -23,10 +23,11 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.rememberDatePickerState import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import io.element.android.libraries.designsystem.theme.components.AlertDialogContent +import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.theme.components.AlertDialogContent @Preview(group = PreviewGroup.DateTimePickers) @Composable @@ -41,6 +42,7 @@ internal fun DatePickerDarkPreview() { } @OptIn(ExperimentalMaterial3Api::class) +@ExcludeFromCoverage @Composable private fun ContentToPreview() { val state = rememberDatePickerState( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt index 9e56a38137..94e596c360 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt @@ -24,11 +24,11 @@ import androidx.compose.material3.TimePickerLayoutType import androidx.compose.material3.rememberTimePickerState import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import io.element.android.libraries.designsystem.theme.components.AlertDialogContent import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.theme.components.AlertDialogContent @OptIn(ExperimentalMaterial3Api::class) @Preview(widthDp = 600, group = PreviewGroup.DateTimePickers) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/CommonResources.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/CommonDrawables.kt similarity index 100% rename from libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/CommonResources.kt rename to libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/CommonDrawables.kt diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarHost.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarHost.kt index a327d2f651..11074f759b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarHost.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarHost.kt @@ -30,7 +30,8 @@ import io.element.android.libraries.designsystem.theme.components.Snackbar fun SnackbarHost(hostState: SnackbarHostState, modifier: Modifier = Modifier) { androidx.compose.material3.SnackbarHost(hostState, modifier) { data -> Snackbar( - modifier = Modifier.padding(12.dp), // Add default padding + // Add default padding + modifier = Modifier.padding(12.dp), message = data.visuals.message, action = data.visuals.actionLabel?.let { ButtonVisuals.Text(it, data::performAction) }, dismissAction = if (data.visuals.withDismissAction) { @@ -38,7 +39,9 @@ fun SnackbarHost(hostState: SnackbarHostState, modifier: Modifier = Modifier) { IconSource.Vector(CompoundIcons.Close), data::dismiss ) - } else null, + } else { + null + }, ) } } diff --git a/libraries/designsystem/src/main/res/drawable/ic_plus.xml b/libraries/designsystem/src/main/res/drawable/ic_plus_composer.xml similarity index 100% rename from libraries/designsystem/src/main/res/drawable/ic_plus.xml rename to libraries/designsystem/src/main/res/drawable/ic_plus_composer.xml diff --git a/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/colors/AvatarColorsTest.kt b/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/colors/AvatarColorsTest.kt index 2db042cdd0..29a1478b4d 100644 --- a/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/colors/AvatarColorsTest.kt +++ b/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/colors/AvatarColorsTest.kt @@ -22,7 +22,6 @@ import io.element.android.compound.theme.avatarColorsLight import org.junit.Test class AvatarColorsTest { - @Test fun `ensure the size of the avatar color are equal for light and dark theme`() { assertThat(avatarColorsDark.size).isEqualTo(avatarColorsLight.size) diff --git a/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarDispatcherTests.kt b/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarDispatcherTests.kt index fbccaa4e75..68cded1601 100644 --- a/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarDispatcherTests.kt +++ b/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarDispatcherTests.kt @@ -22,7 +22,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class SnackbarDispatcherTests { - @Test fun `given an empty queue the flow emits a null item`() = runTest { val snackbarDispatcher = SnackbarDispatcher() @@ -87,5 +86,4 @@ class SnackbarDispatcherTests { assertThat(awaitItem()).isNull() } } - } diff --git a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/RandomSecretPassphraseProvider.kt b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/RandomSecretPassphraseProvider.kt index dd09188bc4..ce8a365bc4 100644 --- a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/RandomSecretPassphraseProvider.kt +++ b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/RandomSecretPassphraseProvider.kt @@ -33,7 +33,6 @@ class RandomSecretPassphraseProvider( private val file: File, private val secretSize: Int = 256, ) : PassphraseProvider { - override fun getPassphrase(): ByteArray { val encryptedFile = EncryptedFileFactory(context).create(file) return if (!file.exists()) { diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt index 9475275e5d..131250fac7 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt @@ -44,6 +44,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.RedactedConte import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent +import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent @@ -61,6 +62,10 @@ class DefaultRoomLastMessageFormatter @Inject constructor( private val profileChangeContentFormatter: ProfileChangeContentFormatter, private val stateContentFormatter: StateContentFormatter, ) : RoomLastMessageFormatter { + companion object { + // Max characters to display in the last message. This works around https://github.com/element-hq/element-x-android/issues/2105 + private const val MAX_SAFE_LENGTH = 500 + } override fun format(event: EventTimelineItem, isDmRoom: Boolean): CharSequence? { val isOutgoing = event.isOwn @@ -102,7 +107,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor( is FailedToParseMessageLikeContent, is FailedToParseStateContent, is UnknownContent -> { prefixIfNeeded(sp.getString(CommonStrings.common_unsupported_event), senderDisplayName, isDmRoom) } - } + }?.take(MAX_SAFE_LENGTH) } private fun processMessageContents(messageContent: MessageContent, senderDisplayName: String, isDmRoom: Boolean): CharSequence? { @@ -120,6 +125,9 @@ class DefaultRoomLastMessageFormatter @Inject constructor( is ImageMessageType -> { sp.getString(CommonStrings.common_image) } + is StickerMessageType -> { + sp.getString(CommonStrings.common_sticker) + } is LocationMessageType -> { sp.getString(CommonStrings.common_shared_location) } diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt index ce4e4aa860..63c8f939a0 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt @@ -46,7 +46,6 @@ class DefaultTimelineEventFormatter @Inject constructor( private val profileChangeContentFormatter: ProfileChangeContentFormatter, private val stateContentFormatter: StateContentFormatter, ) : TimelineEventFormatter { - override fun format(event: EventTimelineItem): CharSequence? { val isOutgoing = event.isOwn val senderDisplayName = (event.senderProfile as? ProfileTimelineDetails.Ready)?.displayName ?: event.sender.value diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt index 6a65a9bd1e..926648458f 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt @@ -104,7 +104,20 @@ class RoomMembershipContentFormatter @Inject constructor( } else { sp.getString(R.string.state_event_room_knock_denied, senderDisplayName, userId.value) } - else -> { + MembershipChange.NONE -> if (senderIsYou) { + sp.getString(R.string.state_event_room_none_by_you) + } else { + sp.getString(R.string.state_event_room_none, senderDisplayName) + } + MembershipChange.ERROR -> { + Timber.v("Filtering timeline item for room membership: $membershipContent") + null + } + MembershipChange.NOT_IMPLEMENTED -> { + Timber.v("Filtering timeline item for room membership: $membershipContent") + null + } + null -> { Timber.v("Filtering timeline item for room membership: $membershipContent") null } diff --git a/libraries/eventformatter/impl/src/main/res/values-cs/translations.xml b/libraries/eventformatter/impl/src/main/res/values-cs/translations.xml index 69179b1276..ecbe8031b9 100644 --- a/libraries/eventformatter/impl/src/main/res/values-cs/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-cs/translations.xml @@ -39,6 +39,8 @@ "Změnili jste název místnosti na: %1$s" "%1$s odstranil(a) název místnosti" "Odstranili jste název místnosti" + "%1$s neprovedl(a) žádné změny" + "Neprovedli jste žádné změny" "%1$s pozvánku odmítl(a)" "Odmítli jste pozvání" "%1$s odebral(a) %2$s" diff --git a/libraries/eventformatter/impl/src/main/res/values-fr/translations.xml b/libraries/eventformatter/impl/src/main/res/values-fr/translations.xml index f7c12a2028..be8f466aa3 100644 --- a/libraries/eventformatter/impl/src/main/res/values-fr/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-fr/translations.xml @@ -39,6 +39,8 @@ "Vous avez changé le nom du salon en : %1$s" "%1$s a supprimé le nom du salon" "Vous avez supprimé le nom du salon" + "%1$s n‘a fait aucun changement visible" + "Vous n‘avez fait aucun changement visible" "%1$s a rejeté l’invitation" "Vous avez refusé l’invitation" "%1$s a supprimé %2$s" diff --git a/libraries/eventformatter/impl/src/main/res/values-it/translations.xml b/libraries/eventformatter/impl/src/main/res/values-it/translations.xml index 2e2e914dfe..85a55d51a4 100644 --- a/libraries/eventformatter/impl/src/main/res/values-it/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-it/translations.xml @@ -39,6 +39,8 @@ "Hai cambiato il nome della stanza in: %1$s" "%1$s ha rimosso il nome della stanza" "Hai rimosso il nome della stanza" + "%1$s non ha apportato modifiche" + "Non hai apportato modifiche" "%1$s ha rifiutato l\'invito" "Hai rifiutato l\'invito" "%1$s ha rimosso %2$s" diff --git a/libraries/eventformatter/impl/src/main/res/values-ru/translations.xml b/libraries/eventformatter/impl/src/main/res/values-ru/translations.xml index 4c1defbb68..d2cfadc57f 100644 --- a/libraries/eventformatter/impl/src/main/res/values-ru/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-ru/translations.xml @@ -39,6 +39,8 @@ "Вы изменили название комнаты на: %1$s" "%1$s удалил название комнаты" "Вы удалили название комнаты" + "%1$s ничего не изменилось" + "Вы не внесли никаких изменений" "%1$s отклонил приглашение" "Вы отклонили приглашение" "%1$s удалил %2$s" @@ -53,5 +55,5 @@ "Вы удалили тему комнаты" "%1$s разблокирован %2$s" "Вы разблокировали %1$s" - "%1$s внес неизвестное изменение в составе" + "%1$s внес неизвестное изменение для своих участников" diff --git a/libraries/eventformatter/impl/src/main/res/values-sk/translations.xml b/libraries/eventformatter/impl/src/main/res/values-sk/translations.xml index 13722a3318..1987a2d333 100644 --- a/libraries/eventformatter/impl/src/main/res/values-sk/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-sk/translations.xml @@ -39,6 +39,8 @@ "Zmenili ste názov miestnosti na: %1$s" "%1$s odstránil/a názov miestnosti" "Odstránili ste názov miestnosti" + "%1$s nevykonal/a žiadne zmeny" + "Nevykonali ste žiadne zmeny" "%1$s odmietol/a pozvánku" "Odmietli ste pozvánku" "%1$s odstránil/a %2$s" diff --git a/libraries/eventformatter/impl/src/main/res/values-zh-rTW/translations.xml b/libraries/eventformatter/impl/src/main/res/values-zh-rTW/translations.xml index 0cee629b87..376d6dcc3e 100644 --- a/libraries/eventformatter/impl/src/main/res/values-zh-rTW/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-zh-rTW/translations.xml @@ -31,6 +31,7 @@ "%1$s 邀請 %2$s 加入聊天室" "您邀請 %1$s 加入聊天室" "%1$s 撤銷了 %2$s 加入房間的邀請" + "您撤銷了 %1$s 加入房間的邀請" "%1$s 將主題變更為 %2$s" "您將主題變更為 %1$s" "聊天室主題已被 %1$s 移除" diff --git a/libraries/eventformatter/impl/src/main/res/values/localazy.xml b/libraries/eventformatter/impl/src/main/res/values/localazy.xml index 03a13bd29b..525a9435fe 100644 --- a/libraries/eventformatter/impl/src/main/res/values/localazy.xml +++ b/libraries/eventformatter/impl/src/main/res/values/localazy.xml @@ -39,6 +39,8 @@ "You changed the room name to: %1$s" "%1$s removed the room name" "You removed the room name" + "%1$s made no changes" + "You made no changes" "%1$s rejected the invitation" "You rejected the invitation" "%1$s removed %2$s" diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt index a6d0f2e776..c75fb9ed4b 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt @@ -43,6 +43,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.RedactedConte import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent +import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent @@ -64,7 +65,6 @@ import org.robolectric.annotation.Config @Suppress("LargeClass") @RunWith(RobolectricTestRunner::class) class DefaultRoomLastMessageFormatterTest { - private lateinit var context: Context private lateinit var fakeMatrixClient: FakeMatrixClient private lateinit var formatter: DefaultRoomLastMessageFormatter @@ -165,6 +165,7 @@ class DefaultRoomLastMessageFormatterTest { AudioMessageType(body, MediaSource("url"), null), VoiceMessageType(body, MediaSource("url"), null, null), ImageMessageType(body, MediaSource("url"), null), + StickerMessageType(body, MediaSource("url"), null), FileMessageType(body, MediaSource("url"), null), LocationMessageType(body, "geo:1,2", null), NoticeMessageType(body, null), @@ -196,6 +197,7 @@ class DefaultRoomLastMessageFormatterTest { is AudioMessageType -> "Audio" is VoiceMessageType -> "Voice message" is ImageMessageType -> "Image" + is StickerMessageType -> "Sticker" is FileMessageType -> "File" is LocationMessageType -> "Shared location" is EmoteMessageType -> "* $senderName ${type.body}" @@ -214,6 +216,7 @@ class DefaultRoomLastMessageFormatterTest { is AudioMessageType -> "$senderName: Audio" is VoiceMessageType -> "$senderName: Voice message" is ImageMessageType -> "$senderName: Image" + is StickerMessageType -> "$senderName: Sticker" is FileMessageType -> "$senderName: File" is LocationMessageType -> "$senderName: Shared location" is TextMessageType, @@ -226,6 +229,7 @@ class DefaultRoomLastMessageFormatterTest { is AudioMessageType -> true is VoiceMessageType -> true is ImageMessageType -> true + is StickerMessageType -> true is FileMessageType -> true is LocationMessageType -> false is EmoteMessageType -> false @@ -469,10 +473,26 @@ class DefaultRoomLastMessageFormatterTest { assertThat(someoneDeniedYourKnock).isEqualTo("$otherName rejected your request to join") } + @Test + @Config(qualifiers = "en") + fun `Membership change - None`() { + val otherName = "Someone" + val youContent = RoomMembershipContent(A_USER_ID, MembershipChange.NONE) + val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.NONE) + + val youNoneRoomEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) + val youNoneRoom = formatter.format(youNoneRoomEvent, false) + assertThat(youNoneRoom).isEqualTo("You made no changes") + + val someoneNoneRoomEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneNoneRoom = formatter.format(someoneNoneRoomEvent, false) + assertThat(someoneNoneRoom).isEqualTo("$otherName made no changes") + } + @Test @Config(qualifiers = "en") fun `Membership change - others`() { - val otherChanges = arrayOf(MembershipChange.NONE, MembershipChange.ERROR, MembershipChange.NOT_IMPLEMENTED) + val otherChanges = arrayOf(MembershipChange.ERROR, MembershipChange.NOT_IMPLEMENTED, null) val results = otherChanges.map { change -> val content = RoomMembershipContent(A_USER_ID, change) diff --git a/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLastMessageFormatter.kt b/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLastMessageFormatter.kt index cd723a27af..e24758c197 100644 --- a/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLastMessageFormatter.kt +++ b/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLastMessageFormatter.kt @@ -20,7 +20,6 @@ import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem class FakeRoomLastMessageFormatter : RoomLastMessageFormatter { - private var result: CharSequence? = null override fun format(event: EventTimelineItem, isDmRoom: Boolean): CharSequence? { diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index 59ec586004..61c155caac 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -75,11 +75,4 @@ enum class FeatureFlags( defaultValue = true, isFinished = false, ), - ReadReceipts( - key = "feature.readreceipts", - title = "Show read receipts", - description = null, - defaultValue = true, - isFinished = false, - ), } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt index fd6f1b1f47..ac5e0d56cf 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt @@ -30,7 +30,6 @@ import javax.inject.Inject class DefaultFeatureFlagService @Inject constructor( private val providers: Set<@JvmSuppressWildcards FeatureFlagProvider> ) : FeatureFlagService { - override fun isFeatureEnabledFlow(feature: Feature): Flow { return providers.filter { it.hasFeature(feature) } .sortedByDescending(FeatureFlagProvider::priority) diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/FeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/FeatureFlagProvider.kt index e40d59ec5b..5fe716caa3 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/FeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/FeatureFlagProvider.kt @@ -28,4 +28,3 @@ interface FeatureFlagProvider { const val LOW_PRIORITY = 0 const val MEDIUM_PRIORITY = 1 const val HIGH_PRIORITY = 2 - diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt index 7866ac1f1f..75b16d4e84 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt @@ -28,7 +28,6 @@ import javax.inject.Inject */ class StaticFeatureFlagProvider @Inject constructor() : FeatureFlagProvider { - override val priority = LOW_PRIORITY override fun isFeatureEnabledFlow(feature: Feature): Flow { @@ -41,7 +40,6 @@ class StaticFeatureFlagProvider @Inject constructor() : FeatureFlags.PinUnlock -> true FeatureFlags.Mentions -> true FeatureFlags.SecureStorage -> true - FeatureFlags.ReadReceipts -> true } } else { false diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt index b2f0a4106d..19d4b1336d 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt @@ -22,14 +22,13 @@ import dagger.Provides import dagger.multibindings.ElementsIntoSet import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.di.AppScope -import io.element.android.libraries.featureflag.impl.StaticFeatureFlagProvider import io.element.android.libraries.featureflag.impl.FeatureFlagProvider import io.element.android.libraries.featureflag.impl.PreferencesFeatureFlagProvider +import io.element.android.libraries.featureflag.impl.StaticFeatureFlagProvider @Module @ContributesTo(AppScope::class) object FeatureFlagModule { - @JvmStatic @Provides @ElementsIntoSet diff --git a/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagServiceTest.kt b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagServiceTest.kt index fc2b605d47..0fd503d6d4 100644 --- a/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagServiceTest.kt +++ b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagServiceTest.kt @@ -23,7 +23,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class DefaultFeatureFlagServiceTest { - @Test fun `given service without provider when feature is checked then it returns the default value`() = runTest { val featureFlagService = DefaultFeatureFlagService(emptySet()) diff --git a/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeMutableFeatureFlagProvider.kt b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeMutableFeatureFlagProvider.kt index 20242cca69..315624d474 100644 --- a/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeMutableFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeMutableFeatureFlagProvider.kt @@ -21,7 +21,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow class FakeMutableFeatureFlagProvider(override val priority: Int) : MutableFeatureFlagProvider { - private val enabledFeatures = mutableMapOf>() override suspend fun setFeatureEnabled(feature: Feature, enabled: Boolean) { diff --git a/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt b/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt index 18c9920d74..1cdde03286 100644 --- a/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt +++ b/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt @@ -24,7 +24,6 @@ import kotlinx.coroutines.flow.MutableStateFlow class FakeFeatureFlagService( initialState: Map = emptyMap() ) : FeatureFlagService { - private val enabledFeatures = initialState .map { it.key to MutableStateFlow(it.value) diff --git a/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/FeatureListView.kt b/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/FeatureListView.kt index 6bf4467de8..a0e298db26 100644 --- a/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/FeatureListView.kt +++ b/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/FeatureListView.kt @@ -20,8 +20,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import io.element.android.libraries.designsystem.components.preferences.PreferenceCheckbox -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.featureflag.ui.model.FeatureUiModel import io.element.android.libraries.featureflag.ui.model.aFeatureUiModelList import kotlinx.collections.immutable.ImmutableList diff --git a/libraries/indicator/impl/src/main/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorService.kt b/libraries/indicator/impl/src/main/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorService.kt index a47da27a38..80af72d6a9 100644 --- a/libraries/indicator/impl/src/main/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorService.kt +++ b/libraries/indicator/impl/src/main/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorService.kt @@ -39,7 +39,6 @@ class DefaultIndicatorService @Inject constructor( private val encryptionService: EncryptionService, private val featureFlagService: FeatureFlagService, ) : IndicatorService { - @Composable override fun showRoomListTopBarIndicator(): State { val canVerifySession by sessionVerificationService.canVerifySessionFlow.collectAsState(initial = false) diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapApplier.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapApplier.kt index b6cfff034a..650e9d27ef 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapApplier.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapApplier.kt @@ -36,7 +36,6 @@ internal class MapApplier( val style: Style, val symbolManager: SymbolManager, ) : AbstractApplier(MapNodeRoot) { - private val decorations = mutableListOf() override fun onClear() { diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapUpdater.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapUpdater.kt index e4e3565f22..759061ac3d 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapUpdater.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapUpdater.kt @@ -16,6 +16,7 @@ * limitations under the License. */ @file:Suppress("MatchingDeclarationName") + package io.element.android.libraries.maplibre.compose import android.annotation.SuppressLint @@ -41,7 +42,6 @@ internal class MapPropertiesNode( cameraPositionState: CameraPositionState, locationSettings: MapLocationSettings, ) : MapNode { - init { map.locationComponent.activateLocationComponent( LocationComponentActivationOptions.Builder(context, style) diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapboxMap.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapboxMap.kt index 5af79e7524..4fab4b506f 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapboxMap.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapboxMap.kt @@ -24,7 +24,7 @@ import android.content.res.Configuration import android.os.Bundle import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.material.Text +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.Composition import androidx.compose.runtime.CompositionContext @@ -81,7 +81,10 @@ public fun MapboxMap( uiSettings: MapUiSettings = DefaultMapUiSettings, symbolManagerSettings: MapSymbolManagerSettings = DefaultMapSymbolManagerSettings, locationSettings: MapLocationSettings = DefaultMapLocationSettings, - content: (@Composable @MapboxMapComposable () -> Unit)? = null, + content: ( + @Composable @MapboxMapComposable + () -> Unit + )? = null, ) { // When in preview, early return a Box with the received modifier preserving layout if (LocalInspectionMode.current) { @@ -158,7 +161,8 @@ private suspend inline fun CompositionContext.newComposition( val style = map.awaitStyle(context, styleUri, images) val symbolManager = SymbolManager(mapView, map, style) return Composition( - MapApplier(map, style, symbolManager), this + MapApplier(map, style, symbolManager), + this ).apply { setContent(content) } @@ -234,7 +238,7 @@ private fun MapView.lifecycleObserver(previousState: MutableState this.onPause() Lifecycle.Event.ON_STOP -> this.onStop() Lifecycle.Event.ON_DESTROY -> { - //handled in onDispose + // handled in onDispose } Lifecycle.Event.ON_ANY -> error("ON_ANY should never be used") } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixHomeServerDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixHomeServerDetails.kt index f5fc38eb16..f360c12096 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixHomeServerDetails.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixHomeServerDetails.kt @@ -24,4 +24,4 @@ data class MatrixHomeServerDetails( val url: String, val supportsPasswordLogin: Boolean, val supportsOidcLogin: Boolean, -): Parcelable +) : Parcelable diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcConfig.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcConfig.kt index ae473885da..466457664e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcConfig.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcConfig.kt @@ -17,5 +17,5 @@ package io.element.android.libraries.matrix.api.auth object OidcConfig { - const val redirectUri = "io.element:/callback" + const val REDIRECT_URI = "io.element:/callback" } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt index a668448752..d0e0f4c13e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt @@ -24,7 +24,6 @@ import timber.log.Timber * Ref: https://matrix.org/docs/spec/appendices#identifier-grammar */ object MatrixPatterns { - // Note: TLD is not mandatory (localhost, IP address...) private const val DOMAIN_REGEX = ":[A-Z0-9.-]+(:[0-9]{2,5})?" diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomId.kt index d21d3ec368..e74bdd0c6d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomId.kt @@ -21,7 +21,6 @@ import java.io.Serializable @JvmInline value class RoomId(val value: String) : Serializable { - init { if (BuildConfig.DEBUG && !MatrixPatterns.isRoomId(value)) { error("`$value` is not a valid room id.\n Example room id: `!room_id:domain`.") diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SpaceId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SpaceId.kt index 89901b7350..2ccc7dacba 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SpaceId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SpaceId.kt @@ -25,8 +25,8 @@ value class SpaceId(val value: String) : Serializable { if (BuildConfig.DEBUG && !MatrixPatterns.isSpaceId(value)) { error( "`$value` is not a valid space id.\n" + - "Space ids are the same as room ids.\n" + - "Example space id: `!space_id:domain`." + "Space ids are the same as room ids.\n" + + "Example space id: `!space_id:domain`." ) } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ThreadId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ThreadId.kt index b6ec9766f4..83212b8d53 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ThreadId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ThreadId.kt @@ -25,8 +25,8 @@ value class ThreadId(val value: String) : Serializable { if (BuildConfig.DEBUG && !MatrixPatterns.isThreadId(value)) { error( "`$value` is not a valid thread id.\n" + - "Thread ids are the same as event ids.\n" + - "Example thread id: `\$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg`." + "Thread ids are the same as event ids.\n" + + "Example thread id: `\$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg`." ) } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt index e72af8596a..fd9073c981 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt @@ -26,7 +26,6 @@ import java.io.Serializable */ @JvmInline value class UserId(val value: String) : Serializable { - init { if (BuildConfig.DEBUG && !MatrixPatterns.isUserId(value)) { error("`$value` is not a valid user id.\nExample user id: `@name:domain`.") diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupState.kt index 03f650e316..ba023d8a12 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupState.kt @@ -31,5 +31,5 @@ enum class BackupState { RESUMING, ENABLED, DOWNLOADING, - DISABLING; + DISABLING } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notificationsettings/NotificationSettingsService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notificationsettings/NotificationSettingsService.kt index f1fe18d6e6..2a6cb42e33 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notificationsettings/NotificationSettingsService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notificationsettings/NotificationSettingsService.kt @@ -26,7 +26,7 @@ interface NotificationSettingsService { /** * State of the current room notification settings flow ([MatrixRoomNotificationSettingsState.Unknown] if not started). */ - val notificationSettingsChangeFlow : SharedFlow + val notificationSettingsChangeFlow: SharedFlow suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result suspend fun getDefaultRoomNotificationMode(isEncrypted: Boolean, isOneToOne: Boolean): Result suspend fun setDefaultRoomNotificationMode(isEncrypted: Boolean, mode: RoomNotificationMode, isOneToOne: Boolean): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt index 19e71db332..4ae7b7dc94 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt @@ -23,7 +23,6 @@ import io.element.android.appconfig.MatrixConfiguration * Mapping of an input URI to a matrix.to compliant URI. */ object MatrixToConverter { - /** * Try to convert a URL from an element web instance or from a client permalink to a matrix.to url. * To be successfully converted, URL path should contain one of the [SUPPORTED_PATHS]. @@ -34,7 +33,7 @@ object MatrixToConverter { */ fun convert(uri: Uri): Uri? { val uriString = uri.toString() - val baseUrl = MatrixConfiguration.matrixToPermalinkBaseUrl + val baseUrl = MatrixConfiguration.MATRIX_TO_PERMALINK_BASE_URL return when { // URL is already a matrix.to diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt index 2a388ae580..c46e15db3b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt @@ -22,11 +22,10 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId object PermalinkBuilder { - private const val ROOM_PATH = "room/" private const val USER_PATH = "user/" - private val permalinkBaseUrl get() = (MatrixConfiguration.clientPermalinkBaseUrl ?: MatrixConfiguration.matrixToPermalinkBaseUrl).also { + private val permalinkBaseUrl get() = (MatrixConfiguration.clientPermalinkBaseUrl ?: MatrixConfiguration.MATRIX_TO_PERMALINK_BASE_URL).also { var baseUrl = it if (!baseUrl.endsWith("/")) { baseUrl += "/" @@ -80,7 +79,7 @@ object PermalinkBuilder { private fun escapeId(value: String) = value.replace("/", "%2F") - private fun isMatrixTo(): Boolean = permalinkBaseUrl.startsWith(MatrixConfiguration.matrixToPermalinkBaseUrl) + private fun isMatrixTo(): Boolean = permalinkBaseUrl.startsWith(MatrixConfiguration.MATRIX_TO_PERMALINK_BASE_URL) } sealed class PermalinkBuilderError : Throwable() { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt index 1fc350775d..e39fee5a22 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt @@ -27,7 +27,6 @@ import kotlinx.collections.immutable.ImmutableList */ @Immutable sealed interface PermalinkData { - data class RoomLink( val roomIdOrAlias: String, val isRoomAlias: Boolean, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt index dcd5221de8..3b90aee1be 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt @@ -30,7 +30,6 @@ import java.net.URLDecoder * or client permalinks (e.g. user/@chagai95:matrix.org) */ object PermalinkParser { - /** * Turns a uri string to a [PermalinkData]. */ @@ -130,7 +129,9 @@ object PermalinkParser { val splitNameValue = it.split("=") if (splitNameValue.size == 2) { Pair(splitNameValue[0], URLDecoder.decode(splitNameValue[1], "UTF-8")) - } else null + } else { + null + } } private fun String.getViaParameters(): List { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CurrentUserMembership.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CurrentUserMembership.kt index 3aa71a9459..f3e20b5529 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CurrentUserMembership.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CurrentUserMembership.kt @@ -17,5 +17,7 @@ package io.element.android.libraries.matrix.api.room enum class CurrentUserMembership { - INVITED, JOINED, LEFT + INVITED, + JOINED, + LEFT } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/ForwardEventException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/ForwardEventException.kt index 6b2813feb8..56bf0d4b17 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/ForwardEventException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/ForwardEventException.kt @@ -21,6 +21,5 @@ import io.element.android.libraries.matrix.api.core.RoomId class ForwardEventException( val roomIds: List ) : Exception() { - override val message: String? = "Failed to deliver event to $roomIds rooms" } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 00364d4822..03c143f078 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -52,6 +52,9 @@ interface MatrixRoom : Closeable { val activeMemberCount: Long val joinedMemberCount: Long + /** Whether the room is a direct message. */ + val isDm: Boolean get() = isDirect && isOneToOne + val roomInfoFlow: Flow /** diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt index 9a25aaa12e..759b0f46cb 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt @@ -35,5 +35,3 @@ fun MatrixRoomMembersState.roomMembers(): List? { else -> null } } - - diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/Mention.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/Mention.kt index 6a1b1f60ef..47fd900a8f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/Mention.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/Mention.kt @@ -20,7 +20,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId sealed interface Mention { - data class User(val userId: UserId): Mention - data object AtRoom: Mention - data class Room(val roomId: RoomId?, val roomAlias: String?): Mention + data class User(val userId: UserId) : Mention + data object AtRoom : Mention + data class Room(val roomId: RoomId?, val roomAlias: String?) : Mention } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt index 54c00572c5..86cff93688 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt @@ -30,7 +30,11 @@ data class RoomMember( ) enum class RoomMembershipState { - BAN, INVITE, JOIN, KNOCK, LEAVE + BAN, + INVITE, + JOIN, + KNOCK, + LEAVE } fun RoomMember.getBestName(): String { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomNotificationSettings.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomNotificationSettings.kt index 23f2b41797..8efbf20d19 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomNotificationSettings.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomNotificationSettings.kt @@ -22,5 +22,7 @@ data class RoomNotificationSettings( ) enum class RoomNotificationMode { - ALL_MESSAGES, MENTIONS_AND_KEYWORDS_ONLY, MUTE + ALL_MESSAGES, + MENTIONS_AND_KEYWORDS_ONLY, + MUTE } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StateEventType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StateEventType.kt index 3cfacf4a23..487623970e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StateEventType.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StateEventType.kt @@ -38,5 +38,5 @@ enum class StateEventType { ROOM_TOMBSTONE, ROOM_TOPIC, SPACE_CHILD, - SPACE_PARENT; + SPACE_PARENT } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt index e0ba452efe..3a89f61d9d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt @@ -39,4 +39,3 @@ suspend fun MatrixRoom.canSendMessage(type: MessageEventType): Result = * Shortcut for calling [MatrixRoom.canUserRedact] with our own user. */ suspend fun MatrixRoom.canRedact(): Result = canUserRedact(sessionId) - diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/DynamicRoomList.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/DynamicRoomList.kt index 1f46c2780d..02833762e2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/DynamicRoomList.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/DynamicRoomList.kt @@ -28,7 +28,6 @@ import kotlinx.coroutines.flow.onEach * It lets load rooms on demand and filter them. */ interface DynamicRoomList : RoomList { - sealed interface Filter { /** * No filter applied. diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomList.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomList.kt index 8722e2c5bd..5ffc58c332 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomList.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomList.kt @@ -28,7 +28,6 @@ import kotlin.time.Duration * Can be retrieved from [RoomListService] methods. */ interface RoomList { - sealed interface LoadingState { data object NotLoaded : LoadingState data class Loaded(val numberOfRooms: Int) : LoadingState diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt index 5682a43389..c13e6ecad9 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt @@ -25,7 +25,6 @@ import kotlinx.coroutines.flow.StateFlow * It requires the SyncService to be started to receive updates. */ interface RoomListService { - @Immutable sealed interface State { data object Idle : State diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt index 0a6aa7bbaa..a644fa9f85 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt @@ -45,4 +45,5 @@ data class RoomSummaryDetails( val inviter: RoomMember? = null, val notificationMode: RoomNotificationMode? = null, val hasOngoingCall: Boolean = false, + val isDm: Boolean = false, ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt index 2c22b80246..9a423d07bb 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt @@ -21,7 +21,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow interface MatrixTimeline : AutoCloseable { - data class PaginationState( val isBackPaginating: Boolean, val hasMoreToLoadBackwards: Boolean, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt index ae4e07a8f4..b677332e5f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt @@ -30,4 +30,3 @@ sealed interface MatrixTimelineItem { data class Virtual(val uniqueId: String, val virtual: VirtualTimelineItem) : MatrixTimelineItem data object Other : MatrixTimelineItem } - diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/ReceiptType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/ReceiptType.kt index 7f02285b7e..3cf556dc5a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/ReceiptType.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/ReceiptType.kt @@ -19,5 +19,5 @@ package io.element.android.libraries.matrix.api.timeline enum class ReceiptType { READ, READ_PRIVATE, - FULLY_READ; + FULLY_READ } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt index 5dcfd88016..87e4f4ab73 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt @@ -56,7 +56,6 @@ data class PollContent( data class UnableToDecryptContent( val data: Data ) : EventContent { - @Immutable sealed interface Data { data class OlmV1Curve25519AesSha2( @@ -86,7 +85,11 @@ data class ProfileChangeContent( data class StateContent( val stateKey: String, val content: OtherState -) : EventContent +) : EventContent { + fun isVisibleInTimeline(): Boolean { + return content.isVisibleInTimeline() + } +} data class FailedToParseMessageLikeContent( val eventType: String, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventType.kt index 8141528f34..67b57b6a07 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventType.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventType.kt @@ -88,12 +88,12 @@ object EventType { fun isCallEvent(type: String): Boolean { return type == CALL_INVITE || - type == CALL_CANDIDATES || - type == CALL_ANSWER || - type == CALL_HANGUP || - type == CALL_SELECT_ANSWER || - type == CALL_NEGOTIATE || - type == CALL_REJECT || - type == CALL_REPLACES + type == CALL_CANDIDATES || + type == CALL_ANSWER || + type == CALL_HANGUP || + type == CALL_SELECT_ANSWER || + type == CALL_NEGOTIATE || + type == CALL_REJECT || + type == CALL_REPLACES } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MembershipChange.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MembershipChange.kt index 8aa8845f23..6159391928 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MembershipChange.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MembershipChange.kt @@ -33,5 +33,5 @@ enum class MembershipChange { KNOCK_ACCEPTED, KNOCK_RETRACTED, KNOCK_DENIED, - NOT_IMPLEMENTED; + NOT_IMPLEMENTED } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageFormat.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageFormat.kt index 4e88113355..16490ebf5f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageFormat.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageFormat.kt @@ -17,5 +17,6 @@ package io.element.android.libraries.matrix.api.timeline.item.event enum class MessageFormat { - HTML, UNKNOWN + HTML, + UNKNOWN } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt index 0b2d948c4e..3e69b1f8f9 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt @@ -38,6 +38,12 @@ data class ImageMessageType( val info: ImageInfo? ) : MessageType +data class StickerMessageType( + val body: String, + val source: MediaSource, + val info: ImageInfo? +) : MessageType + data class LocationMessageType( val body: String, val geoUri: String, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt index 6960b3565d..e5119c388e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt @@ -41,4 +41,30 @@ sealed interface OtherState { data object SpaceChild : OtherState data object SpaceParent : OtherState data class Custom(val eventType: String) : OtherState + + fun isVisibleInTimeline() = when (this) { + // Visible + is RoomAvatar, + is RoomName, + is RoomTopic, + is RoomThirdPartyInvite, + is RoomCreate, + is RoomEncryption, + is Custom -> true + // Hidden + is RoomAliases, + is RoomCanonicalAlias, + is RoomGuestAccess, + is RoomHistoryVisibility, + is RoomJoinRules, + is RoomPinnedEvents, + is RoomPowerLevels, + is RoomServerAcl, + is RoomTombstone, + is SpaceChild, + is SpaceParent, + is PolicyRuleRoom, + is PolicyRuleServer, + is PolicyRuleUser -> false + } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ReactionSender.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ReactionSender.kt index 60398cffd5..7d26e0257b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ReactionSender.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ReactionSender.kt @@ -28,4 +28,3 @@ data class ReactionSender( val senderId: UserId, val timestamp: Long ) - diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt index 0f906e6719..11415eaf6a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt @@ -17,5 +17,7 @@ package io.element.android.libraries.matrix.api.timeline.item.event enum class TimelineItemEventOrigin { - LOCAL, SYNC, PAGINATION; + LOCAL, + SYNC, + PAGINATION } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt index ae1b24c902..9839d35a27 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt @@ -17,7 +17,6 @@ package io.element.android.libraries.matrix.api.timeline.item.virtual sealed interface VirtualTimelineItem { - data class DayDivider( val timestamp: Long ) : VirtualTimelineItem diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt index db8fa1a939..00fd9dc561 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt @@ -21,7 +21,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow interface SessionVerificationService { - /** * State of the current verification flow ([VerificationFlowState.Initial] if not started). */ diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCodeTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCodeTest.kt index 5f987ad725..855c9a2c9e 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCodeTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCodeTest.kt @@ -20,7 +20,6 @@ import com.google.common.truth.Truth.assertThat import org.junit.Test class AuthErrorCodeTest { - @Test fun `errorCode finds UNKNOWN code`() { val error = AuthenticationException.Generic("M_UNKNOWN") @@ -44,5 +43,4 @@ class AuthErrorCodeTest { val error = AuthenticationException.Generic("Some other error") assertThat(error.errorCode).isEqualTo(AuthErrorCode.UNKNOWN) } - } diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverterTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverterTest.kt index d3d928c189..cbf1bffc88 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverterTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverterTest.kt @@ -24,7 +24,6 @@ import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class MatrixToConverterTest { - @Test fun `converting a matrix-to url does nothing`() { val url = Uri.parse("https://matrix.to/#/#element-android:matrix.org") @@ -54,5 +53,4 @@ class MatrixToConverterTest { val url = Uri.parse("https://element.io/") assertThat(MatrixToConverter.convert(url)).isNull() } - } diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilderTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilderTest.kt index 282f58ab21..69621297c8 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilderTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilderTest.kt @@ -24,7 +24,6 @@ import io.element.android.tests.testutils.isInDebug import org.junit.Test class PermalinkBuilderTest { - fun `building a permalink for an invalid user id throws when verifying the id`() { assertThrowsInDebug { val userId = UserId("some invalid user id") diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkDataTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkDataTest.kt index 08955411d8..03a56651e8 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkDataTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkDataTest.kt @@ -21,14 +21,13 @@ import kotlinx.collections.immutable.persistentListOf import org.junit.Test class PermalinkDataTest { - @Test fun `getRoomId() returns value when isRoomAlias is false`() { val permalinkData = PermalinkData.RoomLink( - roomIdOrAlias = "!abcdef123456:matrix.org", - isRoomAlias = false, - eventId = null, - viaParameters = persistentListOf(), + roomIdOrAlias = "!abcdef123456:matrix.org", + isRoomAlias = false, + eventId = null, + viaParameters = persistentListOf(), ) assertThat(permalinkData.getRoomId()).isNotNull() assertThat(permalinkData.getRoomAlias()).isNull() @@ -45,5 +44,4 @@ class PermalinkDataTest { assertThat(permalinkData.getRoomId()).isNull() assertThat(permalinkData.getRoomAlias()).isNotNull() } - } diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParserTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParserTest.kt index 590be150c4..66cdaf88ee 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParserTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParserTest.kt @@ -24,7 +24,6 @@ import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class PermalinkParserTest { - @Test fun `parsing an invalid url returns a fallback link`() { val url = "https://element.io" @@ -53,9 +52,9 @@ class PermalinkParserTest { fun `parsing a valid user url returns a user link`() { val url = "https://app.element.io/#/user/@test:matrix.org" assertThat(PermalinkParser.parse(url)).isEqualTo( - PermalinkData.UserLink( - userId = "@test:matrix.org" - ) + PermalinkData.UserLink( + userId = "@test:matrix.org" + ) ) } @@ -115,12 +114,12 @@ class PermalinkParserTest { fun `parsing a valid room alias url returns a room link`() { val url = "https://app.element.io/#/room/#element-android:matrix.org" assertThat(PermalinkParser.parse(url)).isEqualTo( - PermalinkData.RoomLink( - roomIdOrAlias = "#element-android:matrix.org", - isRoomAlias = true, - eventId = null, - viaParameters = persistentListOf(), - ) + PermalinkData.RoomLink( + roomIdOrAlias = "#element-android:matrix.org", + isRoomAlias = true, + eventId = null, + viaParameters = persistentListOf(), + ) ) } 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 f7c157d2ba..94e0eb56e4 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 @@ -101,11 +101,10 @@ class RustMatrixClient( baseCacheDirectory: File, private val clock: SystemClock, ) : MatrixClient { - override val sessionId: UserId = UserId(client.userId()) private val innerRoomListService = syncService.roomListService() private val sessionDispatcher = dispatchers.io.limitedParallelism(64) - private val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-${sessionId}") + private val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-$sessionId") private val rustSyncService = RustSyncService(syncService, sessionCoroutineScope) private val verificationService = RustSessionVerificationService(rustSyncService, sessionCoroutineScope) private val pushersService = RustPushersService( @@ -137,7 +136,7 @@ class RustMatrixClient( Timber.w("didReceiveAuthError(isSoftLogout=$isSoftLogout)") if (isLoggingOut.getAndSet(true).not()) { Timber.v("didReceiveAuthError -> do the cleanup") - //TODO handle isSoftLogout parameter. + // TODO handle isSoftLogout parameter. appCoroutineScope.launch { val existingData = sessionStore.getSession(client.userId()) if (existingData != null) { @@ -204,7 +203,7 @@ class RustMatrixClient( // Check if already in memory... var cachedPairOfRoom = pairOfRoom(roomId) if (cachedPairOfRoom == null) { - //... otherwise, lets wait for the SS to load all rooms and check again. + // ... otherwise, lets wait for the SS to load all rooms and check again. roomListService.allRooms.awaitLoaded() cachedPairOfRoom = pairOfRoom(roomId) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index b1cb3ffac4..53aa560b97 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -39,7 +39,6 @@ class RustMatrixClientFactory @Inject constructor( private val userAgentProvider: UserAgentProvider, private val clock: SystemClock, ) { - suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) { val client = ClientBuilder() .basePath(baseDirectory.absolutePath) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfiguration.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfiguration.kt index ad848ef95a..2f9a6e3bb8 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfiguration.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfiguration.kt @@ -21,7 +21,7 @@ import org.matrix.rustcomponents.sdk.OidcConfiguration val oidcConfiguration: OidcConfiguration = OidcConfiguration( clientName = "Element", - redirectUri = OidcConfig.redirectUri, + redirectUri = OidcConfig.REDIRECT_URI, clientUri = "https://element.io", logoUri = "https://element.io/mobile-icon.png", tosUri = "https://element.io/acceptable-use-policy-terms", @@ -29,9 +29,7 @@ val oidcConfiguration: OidcConfiguration = OidcConfiguration( contacts = listOf( "support@element.io", ), - /** - * Some homeservers/auth issuers don't support dynamic client registration, and have to be registered manually - */ + // Some homeservers/auth issuers don't support dynamic client registration, and have to be registered manually staticRegistrations = mapOf( "https://id.thirdroom.io/realms/thirdroom" to "elementx", ), 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 033a5f6073..bfce2b3ec2 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 @@ -52,7 +52,6 @@ class RustMatrixAuthenticationService @Inject constructor( userAgentProvider: UserAgentProvider, private val rustMatrixClientFactory: RustMatrixClientFactory, ) : MatrixAuthenticationService { - private val authService: RustAuthenticationService = RustAuthenticationService( basePath = baseDirectory.absolutePath, passphrase = null, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt index 8732645330..b0c33949d3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt @@ -52,7 +52,6 @@ internal class RustEncryptionService( sessionCoroutineScope: CoroutineScope, private val dispatchers: CoroutineDispatchers, ) : EncryptionService { - private val service: Encryption = client.encryption() private val backupStateMapper = BackupStateMapper() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaUploadHandlerImpl.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaUploadHandlerImpl.kt index 639f9149c4..5241c920ab 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaUploadHandlerImpl.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaUploadHandlerImpl.kt @@ -29,7 +29,7 @@ class MediaUploadHandlerImpl( runCatching { sendAttachmentJoinHandle.join() } - .also { cleanUpFiles() } + .also { cleanUpFiles() } override fun cancel() { sendAttachmentJoinHandle.cancel() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaFile.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaFile.kt index 3274bfc123..cbc0079512 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaFile.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaFile.kt @@ -20,7 +20,6 @@ import io.element.android.libraries.matrix.api.media.MediaFile import org.matrix.rustcomponents.sdk.MediaFileHandle class RustMediaFile(private val inner: MediaFileHandle) : MediaFile { - override fun path(): String { return inner.path() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt index 6ae20e06af..3392dc6c1b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt @@ -34,7 +34,6 @@ class RustMediaLoader( dispatchers: CoroutineDispatchers, private val innerClient: Client, ) : MatrixMediaLoader { - @OptIn(ExperimentalCoroutinesApi::class) private val mediaDispatcher = dispatchers.io.limitedParallelism(32) private val cacheDirectory diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt index 3d444f9a63..cc14fad815 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt @@ -28,7 +28,6 @@ import org.matrix.rustcomponents.sdk.use import javax.inject.Inject class TimelineEventToNotificationContentMapper @Inject constructor() { - fun map(timelineEvent: TimelineEvent): NotificationContent { return timelineEvent.use { timelineEvent.eventType().use { eventType -> diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt index 3ea1895e22..b2a13fbe4a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt @@ -29,7 +29,6 @@ import org.matrix.rustcomponents.sdk.RoomNotificationMode as RustRoomNotificatio class MatrixRoomInfoMapper( private val timelineItemMapper: EventTimelineItemMapper = EventTimelineItemMapper(), ) { - fun map(rustRoomInfo: RustRoomInfo): MatrixRoomInfo = rustRoomInfo.use { return MatrixRoomInfo( id = it.id, @@ -43,7 +42,7 @@ class MatrixRoomInfoMapper( canonicalAlias = it.canonicalAlias, alternativeAliases = it.alternativeAliases.toImmutableList(), currentUserMembership = it.membership.map(), - latestEvent = it.latestEvent?.use (timelineItemMapper::map), + latestEvent = it.latestEvent?.use(timelineItemMapper::map), inviter = it.inviter?.use(RoomMemberMapper::map), activeMembersCount = it.activeMembersCount.toLong(), invitedMembersCount = it.invitedMembersCount.toLong(), @@ -57,13 +56,13 @@ class MatrixRoomInfoMapper( } } -fun RustMembership.map(): CurrentUserMembership = when(this) { +fun RustMembership.map(): CurrentUserMembership = when (this) { RustMembership.INVITED -> CurrentUserMembership.INVITED RustMembership.JOINED -> CurrentUserMembership.JOINED RustMembership.LEFT -> CurrentUserMembership.LEFT } -fun RustRoomNotificationMode.map(): RoomNotificationMode = when(this) { +fun RustRoomNotificationMode.map(): RoomNotificationMode = when (this) { RustRoomNotificationMode.ALL_MESSAGES -> RoomNotificationMode.ALL_MESSAGES RustRoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY RustRoomNotificationMode.MUTE -> RoomNotificationMode.MUTE diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt index f58a5c930d..1d610422f9 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt @@ -35,7 +35,6 @@ import kotlin.time.Duration.Companion.milliseconds class RoomContentForwarder( private val roomListService: RoomListService, ) { - /** * Forwards the event with the given [eventId] from the [fromTimeline] to the given [toRoomIds]. * @param fromTimeline the room to forward the event from diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomMemberMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomMemberMapper.kt index e79a8088aa..3354abbe16 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomMemberMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomMemberMapper.kt @@ -23,7 +23,6 @@ import org.matrix.rustcomponents.sdk.MembershipState as RustMembershipState import org.matrix.rustcomponents.sdk.RoomMember as RustRoomMember object RoomMemberMapper { - fun map(roomMember: RustRoomMember): RoomMember = roomMember.use { RoomMember( UserId(it.userId()), diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt index 4286fbb1ec..1b6b364844 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt @@ -31,7 +31,6 @@ class RoomSyncSubscriber( private val roomListService: RoomListService, private val dispatchers: CoroutineDispatchers, ) { - private val subscriptionCounts = HashMap() private val mutex = Mutex() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index fc7215f583..84ccacb75f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -104,7 +104,6 @@ class RustMatrixRoom( private val roomSyncSubscriber: RoomSyncSubscriber, private val matrixRoomInfoMapper: MatrixRoomInfoMapper, ) : MatrixRoom { - override val roomId = RoomId(innerRoom.id()) override val roomInfoFlow: Flow = mxCallbackFlow { @@ -122,7 +121,7 @@ class RustMatrixRoom( // Create a dispatcher for all room methods... private val roomDispatcher = coroutineDispatchers.io.limitedParallelism(32) - //...except getMember methods as it could quickly fill the roomDispatcher... + // ...except getMember methods as it could quickly fill the roomDispatcher... private val roomMembersDispatcher = coroutineDispatchers.io.limitedParallelism(8) private val roomCoroutineScope = sessionCoroutineScope.childScope(coroutineDispatchers.main, "RoomScope-$roomId") diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactory.kt index 17bff4f5b7..c8d4dfd507 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactory.kt @@ -39,7 +39,6 @@ internal class RoomListFactory( private val dispatcher: CoroutineDispatcher, private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(), ) { - /** * Creates a room list that can be used to load more rooms and filter them dynamically. */ @@ -98,7 +97,6 @@ private class RustDynamicRoomList( private val processor: RoomSummaryListProcessor, override val pageSize: Int, ) : DynamicRoomList { - override suspend fun rebuildSummaries() { processor.rebuildRoomSummaries() } @@ -135,4 +133,3 @@ private fun DynamicRoomList.Filter.toRustFilter(): RoomListEntriesDynamicFilterK DynamicRoomList.Filter.AllNonLeft -> RoomListEntriesDynamicFilterKind.AllNonLeft } } - diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt index 41c8ad40dd..f31d9d1208 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt @@ -25,7 +25,6 @@ import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.use class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory()) { - fun create(roomInfo: RoomInfo): RoomSummaryDetails { val latestRoomMessage = roomInfo.latestEvent?.use { roomMessageFactory.create(it) @@ -42,7 +41,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto inviter = roomInfo.inviter?.let(RoomMemberMapper::map), notificationMode = roomInfo.userDefinedNotificationMode?.let(RoomNotificationSettingsMapper::mapMode), hasOngoingCall = roomInfo.hasRoomCall, + isDm = roomInfo.isDirect && roomInfo.activeMembersCount.toLong() == 2L, ) } } - diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt index f0dd3a4176..5525d802bf 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt @@ -35,7 +35,6 @@ class RoomSummaryListProcessor( private val dispatcher: CoroutineDispatcher, private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(), ) { - private val roomSummariesByIdentifier = HashMap() private val mutex = Mutex() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt index 3dce8a0fc4..77d3a4e1fe 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt @@ -43,7 +43,6 @@ internal class RustRoomListService( private val sessionCoroutineScope: CoroutineScope, roomListFactory: RoomListFactory, ) : RoomListService { - override val allRooms: DynamicRoomList = roomListFactory.createRoomList( pageSize = DEFAULT_PAGE_SIZE, initialFilter = DynamicRoomList.Filter.AllNonLeft, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt index 932da42afb..ce7b536a59 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt @@ -33,7 +33,6 @@ class RustSyncService( private val innerSyncService: SyncServiceInterface, sessionCoroutineScope: CoroutineScope ) : SyncService { - override suspend fun startSync() = runCatching { Timber.i("Start sync") innerSyncService.start() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/AsyncMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/AsyncMatrixTimeline.kt index 9be6d3e3bd..0cfb8dfc26 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/AsyncMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/AsyncMatrixTimeline.kt @@ -43,7 +43,6 @@ class AsyncMatrixTimeline( dispatcher: CoroutineDispatcher, private val timelineProvider: suspend () -> MatrixTimeline ) : MatrixTimeline { - private val _timelineItems: MutableStateFlow> = MutableStateFlow(emptyList()) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt index 38c4ef8fb6..602220c1c7 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt @@ -29,7 +29,6 @@ internal class MatrixTimelineDiffProcessor( private val timelineItems: MutableStateFlow>, private val timelineItemFactory: MatrixTimelineItemMapper, ) { - private val mutex = Mutex() suspend fun postItems(items: List) { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt index beca5f17fb..5bf55105cc 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt @@ -30,7 +30,6 @@ class MatrixTimelineItemMapper( private val virtualTimelineItemMapper: VirtualTimelineItemMapper = VirtualTimelineItemMapper(), private val eventTimelineItemMapper: EventTimelineItemMapper = EventTimelineItemMapper(), ) { - fun map(timelineItem: TimelineItem): MatrixTimelineItem = timelineItem.use { val uniqueId = timelineItem.uniqueId().toString() val asEvent = it.asEvent() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt index 547ccf1377..1841aa4b59 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessage import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper +import io.element.android.libraries.matrix.impl.timeline.postprocessor.FilterHiddenStateEventsProcessor import io.element.android.libraries.matrix.impl.timeline.postprocessor.TimelineEncryptedHistoryPostProcessor import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineDispatcher @@ -65,7 +66,6 @@ class RustMatrixTimeline( lastLoginTimestamp: Date?, private val onNewSyncedEvent: () -> Unit, ) : MatrixTimeline { - private val initLatch = CompletableDeferred() private val isInit = AtomicBoolean(false) @@ -83,6 +83,8 @@ class RustMatrixTimeline( dispatcher = dispatcher, ) + private val filterHiddenStateEventsProcessor = FilterHiddenStateEventsProcessor() + private val timelineItemFactory = MatrixTimelineItemMapper( fetchDetailsForEvent = this::fetchDetailsForEvent, roomCoroutineScope = roomCoroutineScope, @@ -102,9 +104,9 @@ class RustMatrixTimeline( override val paginationState: StateFlow = _paginationState.asStateFlow() @OptIn(ExperimentalCoroutinesApi::class) - override val timelineItems: Flow> = _timelineItems.mapLatest { items -> - encryptedHistoryPostProcessor.process(items) - } + override val timelineItems: Flow> = _timelineItems + .mapLatest { items -> encryptedHistoryPostProcessor.process(items) } + .mapLatest { items -> filterHiddenStateEventsProcessor.process(items) } init { Timber.d("Initialize timeline for room ${matrixRoom.roomId}") @@ -257,7 +259,7 @@ class RustMatrixTimeline( private fun List.hasEncryptionHistoryBanner(): Boolean { val firstItem = firstOrNull() - return firstItem is MatrixTimelineItem.Virtual - && firstItem.virtual is VirtualTimelineItem.EncryptedHistoryBanner + return firstItem is MatrixTimelineItem.Virtual && + firstItem.virtual is VirtualTimelineItem.EncryptedHistoryBanner } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt index 4f19a5c40f..debb35e870 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt @@ -43,7 +43,6 @@ import org.matrix.rustcomponents.sdk.MessageFormat as RustMessageFormat import org.matrix.rustcomponents.sdk.MessageType as RustMessageType class EventMessageMapper { - private val timelineEventContentMapper by lazy { TimelineEventContentMapper() } fun map(message: Message): MessageContent = message.use { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index 4e965fc4ef..1cfbb11e02 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -39,7 +39,6 @@ import org.matrix.rustcomponents.sdk.ProfileDetails as RustProfileDetails import org.matrix.rustcomponents.sdk.Receipt as RustReceipt class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper()) { - fun map(eventTimelineItem: RustEventTimelineItem): EventTimelineItem = eventTimelineItem.use { EventTimelineItem( eventId = it.eventId()?.let(::EventId), @@ -100,11 +99,11 @@ private fun List?.map(): ImmutableList { private fun Map.map(): ImmutableList { return map { - Receipt( - userId = UserId(it.key), - timestamp = it.value.timestamp?.toLong() ?: 0 - ) - } + Receipt( + userId = UserId(it.key), + timestamp = it.value.timestamp?.toLong() ?: 0 + ) + } .sortedByDescending { it.timestamp } .toImmutableList() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index ee28b18d08..67afb3907c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -42,7 +42,6 @@ import org.matrix.rustcomponents.sdk.MembershipChange as RustMembershipChange import org.matrix.rustcomponents.sdk.OtherState as RustOtherState class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMapper = EventMessageMapper()) { - fun map(content: TimelineItemContent): EventContent { return content.use { content.kind().use { kind -> @@ -147,7 +146,7 @@ private fun RustMembershipChange.map(): MembershipChange { } } -//TODO extract state events? +// TODO extract state events? private fun RustOtherState.map(): OtherState { return when (this) { is RustOtherState.Custom -> OtherState.Custom(eventType) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt index c2b6a8c863..912ababe9f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt @@ -20,7 +20,6 @@ import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTime import org.matrix.rustcomponents.sdk.VirtualTimelineItem as RustVirtualTimelineItem class VirtualTimelineItemMapper { - fun map(virtualTimelineItem: RustVirtualTimelineItem): VirtualTimelineItem { return when (virtualTimelineItem) { is RustVirtualTimelineItem.DayDivider -> VirtualTimelineItem.DayDivider(virtualTimelineItem.ts.toLong()) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/FilterHiddenStateEventsProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/FilterHiddenStateEventsProcessor.kt new file mode 100644 index 0000000000..c54221d84e --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/FilterHiddenStateEventsProcessor.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 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.matrix.impl.timeline.postprocessor + +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.libraries.matrix.api.timeline.item.event.StateContent + +/** + * This class is used to filter out 'hidden' state events from the timeline. + */ +class FilterHiddenStateEventsProcessor { + fun process(items: List): List { + return items.filter { item -> + when (item) { + is MatrixTimelineItem.Event -> { + when (val content = item.event.content) { + // If it's a state event, make sure it's visible + is StateContent -> content.isVisibleInTimeline() + // We can display any other event + else -> true + } + } + is MatrixTimelineItem.Virtual -> true + is MatrixTimelineItem.Other -> true + } + } + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt index 9c0fbfd115..e2b0530c53 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt @@ -29,7 +29,6 @@ class TimelineEncryptedHistoryPostProcessor( private val isRoomEncrypted: Boolean, private val isKeyBackupEnabled: Boolean, ) { - suspend fun process(items: List): List = withContext(dispatcher) { Timber.d("Process on Thread=${Thread.currentThread()}") if (!isRoomEncrypted || isKeyBackupEnabled || lastLoginTimestamp == null) return@withContext items diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/LogEventLocation.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/LogEventLocation.kt index 712735649c..e5e6d86388 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/LogEventLocation.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/LogEventLocation.kt @@ -24,7 +24,6 @@ data class LogEventLocation( val file: String, val line: UInt?, ) { - companion object { /** * Create a [LogEventLocation] from a [StackTraceElement]. @@ -37,4 +36,3 @@ data class LogEventLocation( } } } - diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt index c211f48c05..a03813f347 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt @@ -28,7 +28,6 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class RustTracingService @Inject constructor(private val buildMeta: BuildMeta) : TracingService { - override fun setupTracing(tracingConfiguration: TracingConfiguration) { val filter = tracingConfiguration.filterConfiguration val rustTracingConfiguration = org.matrix.rustcomponents.sdk.TracingConfiguration( diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingTree.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingTree.kt index f97b08a018..6daa8134b6 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingTree.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingTree.kt @@ -36,7 +36,6 @@ private val fqcnIgnore = listOf( * A Timber tree that passes logs to the Rust SDK. */ internal class RustTracingTree(private val retrieveFromStackTrace: Boolean) : Timber.Tree() { - override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { val location = if (retrieveFromStackTrace) { getLogEventLocationFromStackTrace() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt index b95cbaeed1..976edd494e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt @@ -21,7 +21,6 @@ import kotlinx.collections.immutable.toImmutableList import org.matrix.rustcomponents.sdk.SearchUsersResults object UserSearchResultMapper { - fun map(result: SearchUsersResults): MatrixSearchUserResults { return MatrixSearchUserResults( results = result.results.map(UserProfileMapper::map).toImmutableList(), diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/TaskHandle.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/TaskHandle.kt index 5842ba1546..ffc3d55d53 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/TaskHandle.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/TaskHandle.kt @@ -25,7 +25,6 @@ fun TaskHandle.cancelAndDestroy() { } class TaskHandleBag(private val taskHandles: MutableSet = CopyOnWriteArraySet()) : Set by taskHandles { - operator fun plusAssign(taskHandle: TaskHandle?) { if (taskHandle == null) return taskHandles += taskHandle diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt index 0d705d6834..3872d27ab5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt @@ -40,7 +40,6 @@ class RustSessionVerificationService( private val syncService: RustSyncService, private val sessionCoroutineScope: CoroutineScope, ) : SessionVerificationService, SessionVerificationControllerDelegate { - var verificationController: SessionVerificationControllerInterface? = null set(value) { field = value diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/RustWidgetDriver.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/RustWidgetDriver.kt index 641be4c618..68affed00d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/RustWidgetDriver.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/RustWidgetDriver.kt @@ -34,8 +34,7 @@ class RustWidgetDriver( widgetSettings: MatrixWidgetSettings, private val room: Room, private val widgetCapabilitiesProvider: WidgetCapabilitiesProvider, -): MatrixWidgetDriver { - +) : MatrixWidgetDriver { // It's important to have extra capacity here to make sure we don't drop any messages override val incomingMessages = MutableSharedFlow(extraBufferCapacity = 10) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationExceptionMappingTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationExceptionMappingTest.kt index b0c02c9dcd..f6778489e4 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationExceptionMappingTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationExceptionMappingTest.kt @@ -23,7 +23,6 @@ import org.junit.Test import org.matrix.rustcomponents.sdk.AuthenticationException as RustAuthenticationException class AuthenticationExceptionMappingTest { - @Test fun `mapping an exception with no message returns 'Unknown error' message`() { val exception = Exception() @@ -41,12 +40,12 @@ class AuthenticationExceptionMappingTest { @Test fun `mapping specific exceptions map to their kotlin counterparts`() { assertThat(RustAuthenticationException.ClientMissing("Client missing").mapAuthenticationException()) - .isException("Client missing") + .isException("Client missing") assertThat(RustAuthenticationException.Generic("Generic").mapAuthenticationException()).isException("Generic") assertThat(RustAuthenticationException.InvalidServerName("Invalid server name").mapAuthenticationException()) - .isException("Invalid server name") + .isException("Invalid server name") assertThat(RustAuthenticationException.SessionMissing("Session missing").mapAuthenticationException()) .isException("Session missing") diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTests.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTests.kt index e4cd1c3abd..a9b0fea454 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTests.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTests.kt @@ -40,7 +40,6 @@ import org.matrix.rustcomponents.sdk.TaskHandle // NOTE: this class is using a fake implementation of a Rust SDK interface which returns actual Rust objects with pointers. // Since we don't access the data in those objects, this is fine for our tests, but that's as far as we can test this class. class RoomSummaryListProcessorTests { - private val summaries = MutableStateFlow>(emptyList()) @Test diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/FilterHiddenStateEventsProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/FilterHiddenStateEventsProcessorTest.kt new file mode 100644 index 0000000000..0532d1b762 --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/FilterHiddenStateEventsProcessorTest.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 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.matrix.impl.timeline.postprocessor + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.libraries.matrix.api.timeline.item.event.OtherState +import io.element.android.libraries.matrix.api.timeline.item.event.StateContent +import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem +import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem +import org.junit.Test + +class FilterHiddenStateEventsProcessorTest { + @Test + fun test() { + val items = listOf( + // These are visible because they're not state events + MatrixTimelineItem.Other, + MatrixTimelineItem.Virtual("virtual", VirtualTimelineItem.ReadMarker), + MatrixTimelineItem.Event("event", anEventTimelineItem()), + // These are visible state events + MatrixTimelineItem.Event("m.room.avatar", anEventTimelineItem(content = StateContent("", OtherState.RoomAvatar("")))), + MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(content = StateContent("", OtherState.RoomCreate))), + MatrixTimelineItem.Event("m.room.encrypted", anEventTimelineItem(content = StateContent("", OtherState.RoomEncryption))), + MatrixTimelineItem.Event("m.room.name", anEventTimelineItem(content = StateContent("", OtherState.RoomName("")))), + MatrixTimelineItem.Event("m.room.third_party_invite", anEventTimelineItem(content = StateContent("", OtherState.RoomThirdPartyInvite("")))), + MatrixTimelineItem.Event("m.room.topic", anEventTimelineItem(content = StateContent("", OtherState.RoomTopic("")))), + MatrixTimelineItem.Event("m.room.custom", anEventTimelineItem(content = StateContent("", OtherState.Custom("")))), + // These ones are hidden + MatrixTimelineItem.Event("m.room.aliases", anEventTimelineItem(content = StateContent("", OtherState.RoomAliases))), + MatrixTimelineItem.Event("m.room.canonical_alias", anEventTimelineItem(content = StateContent("", OtherState.RoomCanonicalAlias))), + MatrixTimelineItem.Event("m.room.guest_access", anEventTimelineItem(content = StateContent("", OtherState.RoomGuestAccess))), + MatrixTimelineItem.Event("m.room.history_visibility", anEventTimelineItem(content = StateContent("", OtherState.RoomHistoryVisibility))), + MatrixTimelineItem.Event("m.room.join_rules", anEventTimelineItem(content = StateContent("", OtherState.RoomJoinRules))), + MatrixTimelineItem.Event("m.room.pinned_events", anEventTimelineItem(content = StateContent("", OtherState.RoomPinnedEvents))), + MatrixTimelineItem.Event("m.room.power_levels", anEventTimelineItem(content = StateContent("", OtherState.RoomPowerLevels))), + MatrixTimelineItem.Event("m.room.server_acl", anEventTimelineItem(content = StateContent("", OtherState.RoomServerAcl))), + MatrixTimelineItem.Event("m.room.tombstone", anEventTimelineItem(content = StateContent("", OtherState.RoomTombstone))), + MatrixTimelineItem.Event("m.space.child", anEventTimelineItem(content = StateContent("", OtherState.SpaceChild))), + MatrixTimelineItem.Event("m.space.parent", anEventTimelineItem(content = StateContent("", OtherState.SpaceParent))), + MatrixTimelineItem.Event("m.room.policy.rule.room", anEventTimelineItem(content = StateContent("", OtherState.PolicyRuleRoom))), + MatrixTimelineItem.Event("m.room.policy.rule.server", anEventTimelineItem(content = StateContent("", OtherState.PolicyRuleServer))), + MatrixTimelineItem.Event("m.room.policy.rule.user", anEventTimelineItem(content = StateContent("", OtherState.PolicyRuleUser))), + ) + + val expected = listOf( + MatrixTimelineItem.Other, + MatrixTimelineItem.Virtual("virtual", VirtualTimelineItem.ReadMarker), + MatrixTimelineItem.Event("event", anEventTimelineItem()), + MatrixTimelineItem.Event("m.room.avatar", anEventTimelineItem(content = StateContent("", OtherState.RoomAvatar("")))), + MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(content = StateContent("", OtherState.RoomCreate))), + MatrixTimelineItem.Event("m.room.encrypted", anEventTimelineItem(content = StateContent("", OtherState.RoomEncryption))), + MatrixTimelineItem.Event("m.room.name", anEventTimelineItem(content = StateContent("", OtherState.RoomName("")))), + MatrixTimelineItem.Event("m.room.third_party_invite", anEventTimelineItem(content = StateContent("", OtherState.RoomThirdPartyInvite("")))), + MatrixTimelineItem.Event("m.room.topic", anEventTimelineItem(content = StateContent("", OtherState.RoomTopic("")))), + MatrixTimelineItem.Event("m.room.custom", anEventTimelineItem(content = StateContent("", OtherState.Custom("")))), + ) + + val processor = FilterHiddenStateEventsProcessor() + + assertThat(processor.process(items)).isEqualTo(expected) + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessorTest.kt index 8d36c45bb6..3343cbbdf4 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessorTest.kt @@ -29,7 +29,6 @@ import java.util.Date private const val FAKE_UNIQUE_ID = "FAKE_UNIQUE_ID" class TimelineEncryptedHistoryPostProcessorTest { - private val defaultLastLoginTimestamp = Date(1_689_061_264L) @Test 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 3977fbb133..a8d1d09818 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 @@ -59,7 +59,6 @@ class FakeMatrixClient( private val encryptionService: FakeEncryptionService = FakeEncryptionService(), private val accountManagementUrlString: Result = Result.success(null), ) : MatrixClient { - var setDisplayNameCalled: Boolean = false private set var uploadAvatarCalled: Boolean = false diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt index 44b9d734a5..945821f7f2 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt @@ -36,7 +36,14 @@ class FakeEncryptionService : EncryptionService { private var recoverFailure: Exception? = null private var doesBackupExistOnServerResult: Result = Result.success(true) + private var enableBackupsFailure: Exception? = null + + fun givenEnableBackupsFailure(exception: Exception?) { + enableBackupsFailure = exception + } + override suspend fun enableBackups(): Result = simulateLongTask { + enableBackupsFailure?.let { return Result.failure(it) } return Result.success(Unit) } @@ -72,12 +79,12 @@ class FakeEncryptionService : EncryptionService { this.isLastDevice = isLastDevice } - override suspend fun isLastDevice(): Result { + override suspend fun isLastDevice(): Result = simulateLongTask { return Result.success(isLastDevice) } override suspend fun resetRecoveryKey(): Result = simulateLongTask { - return Result.success(fakeRecoveryKey) + return Result.success(FAKE_RECOVERY_KEY) } override suspend fun enableRecovery(waitForBackupsToUpload: Boolean): Result = simulateLongTask { @@ -101,6 +108,6 @@ class FakeEncryptionService : EncryptionService { } companion object { - const val fakeRecoveryKey = "fake" + const val FAKE_RECOVERY_KEY = "fake" } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaLoader.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaLoader.kt index 3cf01f49bf..c31beb8e4a 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaLoader.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaLoader.kt @@ -22,7 +22,6 @@ import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.tests.testutils.simulateLongTask class FakeMediaLoader : MatrixMediaLoader { - var shouldFail = false var path: String = "" diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt index 68bc508723..450ada5695 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt @@ -33,7 +33,7 @@ class FakeNotificationSettingsService( initialOneToOneDefaultMode: RoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, initialEncryptedOneToOneDefaultMode: RoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, ) : NotificationSettingsService { - private var _notificationSettingsStateFlow = MutableStateFlow(Unit) + private val notificationSettingsStateFlow = MutableStateFlow(Unit) private var defaultGroupRoomNotificationMode: RoomNotificationMode = initialGroupDefaultMode private var defaultEncryptedGroupRoomNotificationMode: RoomNotificationMode = initialEncryptedGroupDefaultMode private var defaultOneToOneRoomNotificationMode: RoomNotificationMode = initialOneToOneDefaultMode @@ -49,7 +49,7 @@ class FakeNotificationSettingsService( private var setAtRoomError: Throwable? = null private var canHomeServerPushEncryptedEventsToDeviceResult = Result.success(true) override val notificationSettingsChangeFlow: SharedFlow - get() = _notificationSettingsStateFlow + get() = notificationSettingsStateFlow override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result { return Result.success( @@ -94,7 +94,7 @@ class FakeNotificationSettingsService( defaultGroupRoomNotificationMode = mode } } - _notificationSettingsStateFlow.emit(Unit) + notificationSettingsStateFlow.emit(Unit) return Result.success(Unit) } @@ -105,7 +105,7 @@ class FakeNotificationSettingsService( } else { roomNotificationModeIsDefault = false roomNotificationMode = mode - _notificationSettingsStateFlow.emit(Unit) + notificationSettingsStateFlow.emit(Unit) Result.success(Unit) } } @@ -117,7 +117,7 @@ class FakeNotificationSettingsService( } roomNotificationModeIsDefault = true roomNotificationMode = defaultEncryptedGroupRoomNotificationMode - _notificationSettingsStateFlow.emit(Unit) + notificationSettingsStateFlow.emit(Unit) return Result.success(Unit) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 9f036a6b62..9c12ba1f4f 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -80,7 +80,6 @@ class FakeMatrixRoom( private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(), canRedact: Boolean = false, ) : MatrixRoom { - private var ignoreResult: Result = Result.success(Unit) private var unignoreResult: Result = Result.success(Unit) private var userDisplayNameResult = Result.success(null) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt index 073a314e9f..e998a35ecf 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt @@ -24,7 +24,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow class FakeRoomListService : RoomListService { - private val allRoomSummariesFlow = MutableStateFlow>(emptyList()) private val inviteRoomSummariesFlow = MutableStateFlow>(emptyList()) private val allRoomsLoadingStateFlow = MutableStateFlow(RoomList.LoadingState.NotLoaded) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/SimplePagedRoomList.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/SimplePagedRoomList.kt index e94002bd1d..e5f70b1b30 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/SimplePagedRoomList.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/SimplePagedRoomList.kt @@ -28,12 +28,11 @@ data class SimplePagedRoomList( override val loadingState: StateFlow, override val currentFilter: MutableStateFlow ) : DynamicRoomList { - override val pageSize: Int = Int.MAX_VALUE override val loadedPages = MutableStateFlow(1) override suspend fun loadMore() { - //No-op + // No-op loadedPages.getAndUpdate { it + 1 } } @@ -46,6 +45,6 @@ data class SimplePagedRoomList( } override suspend fun rebuildSummaries() { - //No-op + // No-op } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt index 4e618deb9a..d140bafd6f 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt @@ -22,7 +22,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow class FakeSyncService : SyncService { - private val syncStateFlow = MutableStateFlow(SyncState.Idle) fun simulateError() { diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt index 94a8de2ced..5530bb8d13 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt @@ -36,7 +36,6 @@ class FakeMatrixTimeline( beginningOfRoomReached = false, ) ) : MatrixTimeline { - private val _paginationState: MutableStateFlow = MutableStateFlow(initialPaginationState) private val _timelineItems: MutableStateFlow> = MutableStateFlow(initialTimelineItems) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt index 0cbbe81eef..d53bc7b18f 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt @@ -119,7 +119,9 @@ fun aTimelineItemDebugInfo( originalJson: String? = null, latestEditedJson: String? = null, ) = TimelineItemDebugInfo( - model, originalJson, latestEditedJson + model, + originalJson, + latestEditedJson ) fun aPollContent( diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeCallWidgetSettingsProvider.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeCallWidgetSettingsProvider.kt index 74cf94e4ad..d2be886ea6 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeCallWidgetSettingsProvider.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeCallWidgetSettingsProvider.kt @@ -22,7 +22,6 @@ import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings class FakeCallWidgetSettingsProvider( private val provideFn: (String, String) -> MatrixWidgetSettings = { _, _ -> MatrixWidgetSettings("id", true, "url") } ) : CallWidgetSettingsProvider { - val providedBaseUrls = mutableListOf() override fun provide(baseUrl: String, widgetId: String): MatrixWidgetSettings { diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeWidgetDriver.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeWidgetDriver.kt index f7fa2b494a..a64691fb40 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeWidgetDriver.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeWidgetDriver.kt @@ -23,7 +23,6 @@ import java.util.UUID class FakeWidgetDriver( override val id: String = UUID.randomUUID().toString(), ) : MatrixWidgetDriver { - private val _sentMessages = mutableListOf() val sentMessages: List = _sentMessages diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt index e0f3e7748c..3e8534e415 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt @@ -136,7 +136,13 @@ fun AttachmentThumbnail( @Parcelize enum class AttachmentThumbnailType : Parcelable { - Image, Video, File, Audio, Location, Voice, Poll + Image, + Video, + File, + Audio, + Location, + Voice, + Poll, } @Parcelize diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt index 5ca418073e..fd154928ef 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt @@ -34,6 +34,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -43,7 +44,6 @@ import io.element.android.libraries.designsystem.theme.components.ListItemStyle import io.element.android.libraries.designsystem.theme.components.ModalBottomSheetLayout import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.ui.media.AvatarAction -import io.element.android.compound.theme.ElementTheme import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.launch diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeader.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeader.kt index 8a00be4663..581b70dfb1 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeader.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeader.kt @@ -29,15 +29,15 @@ import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.getBestName -import io.element.android.compound.theme.ElementTheme @Composable fun MatrixUserHeader( diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeaderPlaceholder.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeaderPlaceholder.kt index 0df8b027f9..03f0055463 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeaderPlaceholder.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeaderPlaceholder.kt @@ -31,12 +31,12 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.placeholderBackground -import io.element.android.compound.theme.ElementTheme @Composable fun MatrixUserHeaderPlaceholder( diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt index 947afd8bbd..5197ca0ebd 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt @@ -20,8 +20,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.getBestName diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnsavedAvatar.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnsavedAvatar.kt index 0303e2eb6c..c67e918e34 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnsavedAvatar.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnsavedAvatar.kt @@ -35,12 +35,12 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import coil.request.ImageRequest -import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.debugPlaceholderBackground import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.temporaryColorBgSpecial -import io.element.android.compound.theme.ElementTheme /** * An avatar that the user has selected, but which has not yet been uploaded to Matrix. diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt index 8945c46817..455041aabd 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt @@ -41,7 +41,6 @@ internal class CoilMediaFetcher( private val mediaData: MediaRequestData?, private val options: Options ) : Fetcher { - override suspend fun fetch(): FetchResult? { if (mediaData?.source == null) return null return when (mediaData.kind) { @@ -126,9 +125,7 @@ internal class CoilMediaFetcher( class AvatarFactory( private val context: Context, private val client: MatrixClient - ) : - Fetcher.Factory { - + ) : Fetcher.Factory { override fun create( data: AvatarData, options: Options, diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt index 979d42b826..18629aa0e9 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt @@ -33,7 +33,6 @@ data class MediaRequestData( val source: MediaSource?, val kind: Kind ) { - sealed interface Kind { data object Content : Kind data class File(val body: String?, val mimeType: String) : Kind @@ -42,4 +41,3 @@ data class MediaRequestData( } } } - diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataKeyer.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataKeyer.kt index 0064c1b63b..5a7b93ad90 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataKeyer.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataKeyer.kt @@ -34,5 +34,5 @@ internal class MediaRequestDataKeyer : Keyer { private fun MediaRequestData.toKey(): String? { if (source == null) return null - return "${source.url}_${kind}" + return "${source.url}_$kind" } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt index 8db1e6b5db..b3c6a0bf86 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt @@ -32,18 +32,22 @@ import org.jsoup.nodes.Document * @param prefix if not null, the prefix will be inserted at the beginning of the message. */ fun FormattedBody.toHtmlDocument(prefix: String? = null): Document? { - return takeIf { it.format == MessageFormat.HTML }?.body?.let { formattedBody -> - val dom = if (prefix != null) { - Jsoup.parse("$prefix $formattedBody") - } else { - Jsoup.parse(formattedBody) + return takeIf { it.format == MessageFormat.HTML }?.body + // Trim whitespace at the end to avoid having wrong rendering of the message. + // We don't trim the start in case it's used as indentation. + ?.trimEnd() + ?.let { formattedBody -> + val dom = if (prefix != null) { + Jsoup.parse("$prefix $formattedBody") + } else { + Jsoup.parse(formattedBody) + } + + // Prepend `@` to mentions + fixMentions(dom) + + dom } - - // Prepend `@` to mentions - fixMentions(dom) - - dom - } } private fun fixMentions(dom: Document) { diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainText.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainText.kt index f20252c42a..0cea2d7ae2 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainText.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainText.kt @@ -17,8 +17,8 @@ package io.element.android.libraries.matrix.ui.messages import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody -import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat +import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import org.jsoup.nodes.Document import org.jsoup.nodes.Element import org.jsoup.nodes.Node diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt index f2a73545bf..dea6195ba8 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt @@ -37,4 +37,3 @@ fun MatrixRoom.canRedactAsState(updateKey: Long): State { value = canRedact().getOrElse { false } } } - diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToHtmlDocumentTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToHtmlDocumentTest.kt index d6c740b77d..704b87c593 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToHtmlDocumentTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToHtmlDocumentTest.kt @@ -26,7 +26,6 @@ import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class ToHtmlDocumentTest { - @Test fun `toHtmlDocument - returns null if format is not HTML`() { val body = FormattedBody( diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToPlainTextTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToPlainTextTest.kt index 3c1a469f42..7c61075584 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToPlainTextTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToPlainTextTest.kt @@ -28,7 +28,6 @@ import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class ToPlainTextTest { - @Test fun `Document toPlainText - returns a plain text version of the document`() { val document = Jsoup.parse( @@ -40,7 +39,8 @@ class ToPlainTextTest { """.trimIndent() ) - assertThat(document.toPlainText()).isEqualTo(""" + assertThat(document.toPlainText()).isEqualTo( + """ Hello world • This is an unordered list. 1. This is an ordered list. @@ -59,7 +59,8 @@ class ToPlainTextTest {
""".trimIndent() ) - assertThat(formattedBody.toPlainText()).isEqualTo(""" + assertThat(formattedBody.toPlainText()).isEqualTo( + """ Hello world • This is an unordered list. 1. This is an ordered list. @@ -95,7 +96,8 @@ class ToPlainTextTest { """.trimIndent() ) ) - assertThat(messageType.toPlainText()).isEqualTo(""" + assertThat(messageType.toPlainText()).isEqualTo( + """ Hello world • This is an unordered list. 1. This is an ordered list. diff --git a/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerProvider.kt b/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerProvider.kt index 9becdc8aee..c221f511f5 100644 --- a/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerProvider.kt +++ b/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerProvider.kt @@ -21,7 +21,6 @@ import androidx.activity.result.PickVisualMediaRequest import androidx.compose.runtime.Composable interface PickerProvider { - @Composable fun registerGalleryPicker( onResult: (uri: Uri?, mimeType: String?) -> Unit diff --git a/libraries/mediapickers/api/src/test/kotlin/io/element/android/libraries/mediapickers/PickerTypeTest.kt b/libraries/mediapickers/api/src/test/kotlin/io/element/android/libraries/mediapickers/PickerTypeTest.kt index d759dc3020..3a0305daad 100644 --- a/libraries/mediapickers/api/src/test/kotlin/io/element/android/libraries/mediapickers/PickerTypeTest.kt +++ b/libraries/mediapickers/api/src/test/kotlin/io/element/android/libraries/mediapickers/PickerTypeTest.kt @@ -27,7 +27,6 @@ import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class PickerTypeTest { - @Test fun `ImageAndVideo - assert types`() { val pickerType = PickerType.ImageAndVideo @@ -62,5 +61,4 @@ class PickerTypeTest { assertThat(pickerType.getContract()).isInstanceOf(ActivityResultContracts.CaptureVideo::class.java) assertThat(pickerType.getDefaultRequest()).isEqualTo(uri) } - } diff --git a/libraries/mediapickers/impl/src/main/kotlin/io/element/android/libraries/mediapickers/impl/PickerProviderImpl.kt b/libraries/mediapickers/impl/src/main/kotlin/io/element/android/libraries/mediapickers/impl/PickerProviderImpl.kt index f3c186a34f..d5e092f9ca 100644 --- a/libraries/mediapickers/impl/src/main/kotlin/io/element/android/libraries/mediapickers/impl/PickerProviderImpl.kt +++ b/libraries/mediapickers/impl/src/main/kotlin/io/element/android/libraries/mediapickers/impl/PickerProviderImpl.kt @@ -38,9 +38,8 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class PickerProviderImpl(private val isInTest: Boolean) : PickerProvider { - @Inject - constructor(): this(false) + constructor() : this(false) /** * Remembers and returns a [PickerLauncher] for a certain media/file [type]. diff --git a/libraries/mediaplayer/api/src/main/kotlin/io/element/android/libraries/mediaplayer/api/MediaPlayer.kt b/libraries/mediaplayer/api/src/main/kotlin/io/element/android/libraries/mediaplayer/api/MediaPlayer.kt index 3598edccf3..6be7e710fb 100644 --- a/libraries/mediaplayer/api/src/main/kotlin/io/element/android/libraries/mediaplayer/api/MediaPlayer.kt +++ b/libraries/mediaplayer/api/src/main/kotlin/io/element/android/libraries/mediaplayer/api/MediaPlayer.kt @@ -23,7 +23,6 @@ import kotlinx.coroutines.flow.StateFlow * A media player for Element X. */ interface MediaPlayer : AutoCloseable { - /** * The current state of the player. */ diff --git a/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/MediaPlayerImpl.kt b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/MediaPlayerImpl.kt index 04919b0e56..1cd14051a7 100644 --- a/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/MediaPlayerImpl.kt +++ b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/MediaPlayerImpl.kt @@ -46,7 +46,6 @@ import kotlin.time.Duration.Companion.seconds class MediaPlayerImpl @Inject constructor( private val player: SimplePlayer, ) : MediaPlayer { - private val listener = object : SimplePlayer.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { _state.update { diff --git a/libraries/mediaplayer/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeMediaPlayer.kt b/libraries/mediaplayer/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeMediaPlayer.kt index 9bfb974622..fa5d4b57b9 100644 --- a/libraries/mediaplayer/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeMediaPlayer.kt +++ b/libraries/mediaplayer/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeMediaPlayer.kt @@ -30,7 +30,6 @@ class FakeMediaPlayer( private val fakeTotalDurationMs: Long = 10_000L, private val fakePlayedDurationMs: Long = 1000L, ) : MediaPlayer { - private val _state = MutableStateFlow( MediaPlayer.State( isReady = false, diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaPreProcessor.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaPreProcessor.kt index 9a48b50aca..db1197f81a 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaPreProcessor.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaPreProcessor.kt @@ -33,4 +33,3 @@ interface MediaPreProcessor { data class Failure(override val cause: Throwable?) : Exception(cause) } - diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt index dfcef7fb33..89698a577e 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt @@ -30,7 +30,6 @@ class MediaSender @Inject constructor( private val preProcessor: MediaPreProcessor, private val room: MatrixRoom, ) { - private val ongoingUploadJobs = ConcurrentHashMap() val hasOngoingMediaUploads get() = ongoingUploadJobs.isNotEmpty() diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt index 76e6fd7cb1..8fc4430af2 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt @@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.api.media.VideoInfo import java.io.File sealed interface MediaUploadInfo { - val file: File data class Image(override val file: File, val imageInfo: ImageInfo, val thumbnailFile: File?) : MediaUploadInfo diff --git a/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTests.kt b/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTests.kt index 35aee8b2d5..f4e58530f8 100644 --- a/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTests.kt +++ b/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTests.kt @@ -33,7 +33,6 @@ import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class MediaSenderTests { - @Test fun `given an attachment when sending it the preprocessor always runs`() = runTest { val preProcessor = FakeMediaPreProcessor() diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt index 486364a7d7..884336c48e 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt @@ -124,7 +124,6 @@ class AndroidMediaPreProcessor @Inject constructor( } private suspend fun processImage(uri: Uri, mimeType: String, shouldBeCompressed: Boolean): MediaUploadInfo { - suspend fun processImageWithCompression(): MediaUploadInfo { // Read the orientation metadata from its own stream. Trying to reuse this stream for compression will fail. val orientation = contentResolver.openInputStream(uri).use { input -> @@ -272,4 +271,3 @@ private fun MediaMetadataRetriever.extractDuration(): Duration { val durationInMs = extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L return durationInMs.milliseconds } - diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ThumbnailFactory.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ThumbnailFactory.kt index 1ee7b40bb3..bc0e6589a6 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ThumbnailFactory.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ThumbnailFactory.kt @@ -61,7 +61,6 @@ class ThumbnailFactory @Inject constructor( @ApplicationContext private val context: Context, private val sdkIntProvider: BuildVersionSdkIntProvider ) { - @SuppressLint("NewApi") suspend fun createImageThumbnail(file: File): ThumbnailResult? { return createThumbnail { cancellationSignal -> diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt index 762dd04901..228c320f08 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt @@ -33,7 +33,6 @@ import javax.inject.Inject class VideoCompressor @Inject constructor( @ApplicationContext private val context: Context, ) { - fun compress(uri: Uri) = callbackFlow { val tmpFile = context.createTmpFile(extension = "mp4") val future = Transcoder.into(tmpFile.path) diff --git a/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessorTest.kt b/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessorTest.kt index 4086627561..3d0f9be181 100644 --- a/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessorTest.kt +++ b/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessorTest.kt @@ -230,7 +230,8 @@ class AndroidMediaPreProcessorTest { assertThat(info.thumbnailFile).isNotNull() assertThat(info.videoInfo).isEqualTo( VideoInfo( - duration = Duration.ZERO, // Not available with Robolectric? + // Not available with Robolectric? + duration = Duration.ZERO, height = 1_178, width = 1_818, mimetype = MimeTypes.Mp4, @@ -260,9 +261,12 @@ class AndroidMediaPreProcessorTest { assertThat(info.thumbnailFile).isNull() assertThat(info.videoInfo).isEqualTo( VideoInfo( - duration = Duration.ZERO, // Not available with Robolectric? - height = 0, // Not available with Robolectric? - width = 0, // Not available with Robolectric? + // Not available with Robolectric? + duration = Duration.ZERO, + // Not available with Robolectric? + height = 0, + // Not available with Robolectric? + width = 0, mimetype = MimeTypes.Mp4, size = 1_673_712, thumbnailInfo = null, @@ -288,7 +292,8 @@ class AndroidMediaPreProcessorTest { val info = result as MediaUploadInfo.Audio assertThat(info.audioInfo).isEqualTo( AudioInfo( - duration = Duration.ZERO, // Not available with Robolectric? + // Not available with Robolectric? + duration = Duration.ZERO, size = 52_079, mimetype = MimeTypes.Mp3, ) diff --git a/libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaPreProcessor.kt b/libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaPreProcessor.kt index 0c612c8d1c..e841bd7d93 100644 --- a/libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaPreProcessor.kt +++ b/libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaPreProcessor.kt @@ -27,7 +27,6 @@ import java.io.File import kotlin.time.Duration.Companion.seconds class FakeMediaPreProcessor : MediaPreProcessor { - var processCallCount = 0 private set diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/helper/fileExtensionAndSize.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/helper/FileExtensionAndSizeFormatter.kt similarity index 100% rename from libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/helper/fileExtensionAndSize.kt rename to libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/helper/FileExtensionAndSizeFormatter.kt diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaActions.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaActions.kt index 158c748a94..6743d34976 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaActions.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaActions.kt @@ -19,7 +19,6 @@ package io.element.android.libraries.mediaviewer.api.local import androidx.compose.runtime.Composable interface LocalMediaActions { - @Composable fun Configure() @@ -41,4 +40,3 @@ interface LocalMediaActions { */ suspend fun open(localMedia: LocalMedia): Result } - diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaFactory.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaFactory.kt index 64dfbd03d8..2235ff90bd 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaFactory.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaFactory.kt @@ -20,7 +20,6 @@ import android.net.Uri import io.element.android.libraries.matrix.api.media.MediaFile interface LocalMediaFactory { - /** * This method will create a [LocalMedia] with the given [MediaFile] and [MediaInfo]. */ diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaView.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaView.kt index 1e81f75e9f..af6b5f4251 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaView.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaView.kt @@ -56,9 +56,6 @@ import androidx.media3.common.util.UnstableApi import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.PlayerView import io.element.android.compound.theme.ElementTheme -import io.element.android.libraries.mediaviewer.api.local.exoplayer.ExoPlayerWrapper -import io.element.android.libraries.mediaviewer.api.local.pdf.PdfViewer -import io.element.android.libraries.mediaviewer.api.local.pdf.rememberPdfViewerState import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeAudio @@ -70,6 +67,9 @@ import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.designsystem.utils.KeepScreenOn import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize +import io.element.android.libraries.mediaviewer.api.local.exoplayer.ExoPlayerWrapper +import io.element.android.libraries.mediaviewer.api.local.pdf.PdfViewer +import io.element.android.libraries.mediaviewer.api.local.pdf.rememberPdfViewerState import io.element.android.libraries.ui.strings.CommonStrings import me.saket.telephoto.zoomable.ZoomSpec import me.saket.telephoto.zoomable.ZoomableState @@ -107,7 +107,7 @@ fun LocalMediaView( zoomableState = zoomableState, modifier = modifier ) - //TODO handle audio with exoplayer + // TODO handle audio with exoplayer else -> MediaFileView( localMediaViewState = localMediaViewState, uri = localMedia?.uri, diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/MediaInfo.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/MediaInfo.kt index 726c9dcf0b..1def7b4522 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/MediaInfo.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/MediaInfo.kt @@ -29,21 +29,36 @@ data class MediaInfo( ) : Parcelable fun anImageInfo(): MediaInfo = MediaInfo( - "an image file.jpg", MimeTypes.Jpeg, "4MB", "jpg" + "an image file.jpg", + MimeTypes.Jpeg, + "4MB", + "jpg" ) fun aVideoInfo(): MediaInfo = MediaInfo( - "a video file.mp4", MimeTypes.Mp4, "14MB", "mp4" + "a video file.mp4", + MimeTypes.Mp4, + "14MB", + "mp4" ) fun aPdfInfo(): MediaInfo = MediaInfo( - "a pdf file.pdf", MimeTypes.Pdf, "23MB", "pdf" + "a pdf file.pdf", + MimeTypes.Pdf, + "23MB", + "pdf" ) fun aFileInfo(): MediaInfo = MediaInfo( - "an apk file.apk", MimeTypes.Apk, "50MB", "apk" + "an apk file.apk", + MimeTypes.Apk, + "50MB", + "apk" ) fun anAudioInfo(): MediaInfo = MediaInfo( - "an audio file.mp3", MimeTypes.Mp3, "7MB", "mp3" + "an audio file.mp3", + MimeTypes.Mp3, + "7MB", + "mp3" ) diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/exoplayer/ExoPlayerWrapper.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/exoplayer/ExoPlayerWrapper.kt index 581a018c43..fc614dfe2e 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/exoplayer/ExoPlayerWrapper.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/exoplayer/ExoPlayerWrapper.kt @@ -27,7 +27,6 @@ import androidx.media3.exoplayer.ExoPlayer */ @UnstableApi class ExoPlayerWrapper(private val exoPlayer: ExoPlayer) : ExoPlayer by exoPlayer { - override fun isCommandAvailable(command: Int): Boolean { return availableCommands.contains(command) } diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/ParcelFileDescriptorFactory.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/ParcelFileDescriptorFactory.kt index 523e8b593c..29e76f2d34 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/ParcelFileDescriptorFactory.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/ParcelFileDescriptorFactory.kt @@ -22,7 +22,6 @@ import android.os.ParcelFileDescriptor import java.io.File class ParcelFileDescriptorFactory(private val context: Context) { - fun create(model: Any?) = runCatching { when (model) { is File -> ParcelFileDescriptor.open(model, ParcelFileDescriptor.MODE_READ_ONLY) diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfPage.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfPage.kt index 1da6d1a21c..076caff3a9 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfPage.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfPage.kt @@ -39,7 +39,6 @@ class PdfPage( private val pdfRenderer: PdfRenderer, private val coroutineScope: CoroutineScope, ) { - sealed interface State { data class Loading(val width: Int, val height: Int) : State data class Loaded(val bitmap: Bitmap) : State @@ -92,7 +91,9 @@ class PdfPage( private fun PdfRenderer.openPageRenderAndClose(index: Int, bitmapWidth: Int, bitmapHeight: Int): Bitmap { fun createBitmap(bitmapWidth: Int, bitmapHeight: Int): Bitmap { val bitmap = Bitmap.createBitmap( - bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888 + bitmapWidth, + bitmapHeight, + Bitmap.Config.ARGB_8888 ) val canvas = Canvas(bitmap) canvas.drawColor(Color.WHITE) @@ -106,6 +107,3 @@ class PdfPage( } } } - - - diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfRendererManager.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfRendererManager.kt index 56fe175647..e4e3c7a79c 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfRendererManager.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfRendererManager.kt @@ -32,7 +32,6 @@ class PdfRendererManager( private val width: Int, private val coroutineScope: CoroutineScope, ) { - private val mutex = Mutex() private var pdfRenderer: PdfRenderer? = null private val mutablePdfPages = MutableStateFlow>(emptyList()) diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfViewer.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfViewer.kt index 1bad0e75f5..0f00e3faa6 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfViewer.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfViewer.kt @@ -75,7 +75,6 @@ private fun PdfPagesView( modifier = modifier.fillMaxSize(), state = lazyListState, verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically) - ) { items(pdfPages.size) { index -> val pdfPage = pdfPages[index] diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfViewerState.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfViewerState.kt index 8df8bfdecf..7e9f778e5d 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfViewerState.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfViewerState.kt @@ -40,15 +40,14 @@ class PdfViewerState( val zoomableState: ZoomableState, val lazyListState: LazyListState, ) { - var isLoaded by mutableStateOf(false) private var pdfRendererManager by mutableStateOf(null) @Composable - fun getPages(): List{ + fun getPages(): List { return pdfRendererManager?.run { pdfPages.collectAsState().value - }?: emptyList() + } ?: emptyList() } fun openForWidth(maxWidth: Int) { diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerEvents.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerEvents.kt index b9f8b0aa4d..4d82a04105 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerEvents.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerEvents.kt @@ -17,9 +17,9 @@ package io.element.android.libraries.mediaviewer.api.viewer sealed interface MediaViewerEvents { - data object SaveOnDisk: MediaViewerEvents - data object Share: MediaViewerEvents - data object OpenWith: MediaViewerEvents + data object SaveOnDisk : MediaViewerEvents + data object Share : MediaViewerEvents + data object OpenWith : MediaViewerEvents data object RetryLoading : MediaViewerEvents data object ClearLoadingError : MediaViewerEvents } diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerNode.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerNode.kt index 3917d2eba4..09c8397f50 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerNode.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerNode.kt @@ -37,7 +37,6 @@ open class MediaViewerNode @AssistedInject constructor( @Assisted plugins: List, presenterFactory: MediaViewerPresenter.Factory, ) : Node(buildContext, plugins = plugins) { - data class Inputs( val mediaInfo: MediaInfo, val mediaSource: MediaSource, diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerPresenter.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerPresenter.kt index 90d81a4d0c..1160dab28d 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerPresenter.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerPresenter.kt @@ -29,7 +29,7 @@ import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage @@ -51,7 +51,6 @@ class MediaViewerPresenter @AssistedInject constructor( private val localMediaActions: LocalMediaActions, private val snackbarDispatcher: SnackbarDispatcher, ) : Presenter { - @AssistedFactory interface Factory { fun create(inputs: MediaViewerNode.Inputs): MediaViewerPresenter @@ -64,8 +63,8 @@ class MediaViewerPresenter @AssistedInject constructor( val mediaFile: MutableState = remember { mutableStateOf(null) } - val localMedia: MutableState> = remember { - mutableStateOf(Async.Uninitialized) + val localMedia: MutableState> = remember { + mutableStateOf(AsyncData.Uninitialized) } val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() localMediaActions.Configure() @@ -79,7 +78,7 @@ class MediaViewerPresenter @AssistedInject constructor( fun handleEvents(mediaViewerEvents: MediaViewerEvents) { when (mediaViewerEvents) { MediaViewerEvents.RetryLoading -> loadMediaTrigger++ - MediaViewerEvents.ClearLoadingError -> localMedia.value = Async.Uninitialized + MediaViewerEvents.ClearLoadingError -> localMedia.value = AsyncData.Uninitialized MediaViewerEvents.SaveOnDisk -> coroutineScope.saveOnDisk(localMedia.value) MediaViewerEvents.Share -> coroutineScope.share(localMedia.value) MediaViewerEvents.OpenWith -> coroutineScope.open(localMedia.value) @@ -97,8 +96,8 @@ class MediaViewerPresenter @AssistedInject constructor( ) } - private fun CoroutineScope.downloadMedia(mediaFile: MutableState, localMedia: MutableState>) = launch { - localMedia.value = Async.Loading() + private fun CoroutineScope.downloadMedia(mediaFile: MutableState, localMedia: MutableState>) = launch { + localMedia.value = AsyncData.Loading() mediaLoader.downloadMediaFile( source = inputs.mediaSource, mimeType = inputs.mediaInfo.mimeType, @@ -114,15 +113,15 @@ class MediaViewerPresenter @AssistedInject constructor( ) } .onSuccess { - localMedia.value = Async.Success(it) + localMedia.value = AsyncData.Success(it) } .onFailure { - localMedia.value = Async.Failure(it) + localMedia.value = AsyncData.Failure(it) } } - private fun CoroutineScope.saveOnDisk(localMedia: Async) = launch { - if (localMedia is Async.Success) { + private fun CoroutineScope.saveOnDisk(localMedia: AsyncData) = launch { + if (localMedia is AsyncData.Success) { localMediaActions.saveOnDisk(localMedia.data) .onSuccess { val snackbarMessage = SnackbarMessage(CommonStrings.common_file_saved_on_disk_android) @@ -132,27 +131,27 @@ class MediaViewerPresenter @AssistedInject constructor( val snackbarMessage = SnackbarMessage(mediaActionsError(it)) snackbarDispatcher.post(snackbarMessage) } - } else Unit + } } - private fun CoroutineScope.share(localMedia: Async) = launch { - if (localMedia is Async.Success) { + private fun CoroutineScope.share(localMedia: AsyncData) = launch { + if (localMedia is AsyncData.Success) { localMediaActions.share(localMedia.data) .onFailure { val snackbarMessage = SnackbarMessage(mediaActionsError(it)) snackbarDispatcher.post(snackbarMessage) } - } else Unit + } } - private fun CoroutineScope.open(localMedia: Async) = launch { - if (localMedia is Async.Success) { + private fun CoroutineScope.open(localMedia: AsyncData) = launch { + if (localMedia is AsyncData.Success) { localMediaActions.open(localMedia.data) .onFailure { val snackbarMessage = SnackbarMessage(mediaActionsError(it)) snackbarDispatcher.post(snackbarMessage) } - } else Unit + } } private fun mediaActionsError(throwable: Throwable): Int { @@ -163,6 +162,3 @@ class MediaViewerPresenter @AssistedInject constructor( } } } - - - diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerState.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerState.kt index 4b68f4afcc..6d88cc7c06 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerState.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerState.kt @@ -16,7 +16,7 @@ package io.element.android.libraries.mediaviewer.api.viewer -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.mediaviewer.api.local.LocalMedia @@ -25,7 +25,7 @@ import io.element.android.libraries.mediaviewer.api.local.MediaInfo data class MediaViewerState( val mediaInfo: MediaInfo, val thumbnailSource: MediaSource?, - val downloadedMedia: Async, + val downloadedMedia: AsyncData, val snackbarMessage: SnackbarMessage?, val canDownload: Boolean, val canShare: Boolean, diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerStateProvider.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerStateProvider.kt index 14f792c2c6..a9d6e4fc98 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerStateProvider.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerStateProvider.kt @@ -18,7 +18,7 @@ package io.element.android.libraries.mediaviewer.api.viewer import android.net.Uri import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.api.local.MediaInfo import io.element.android.libraries.mediaviewer.api.local.aFileInfo @@ -31,48 +31,48 @@ open class MediaViewerStateProvider : PreviewParameterProvider override val values: Sequence get() = sequenceOf( aMediaViewerState(), - aMediaViewerState(Async.Loading()), - aMediaViewerState(Async.Failure(IllegalStateException("error"))), + aMediaViewerState(AsyncData.Loading()), + aMediaViewerState(AsyncData.Failure(IllegalStateException("error"))), aMediaViewerState( - Async.Success( + AsyncData.Success( LocalMedia(Uri.EMPTY, anImageInfo()) ), anImageInfo(), ), aMediaViewerState( - Async.Success( + AsyncData.Success( LocalMedia(Uri.EMPTY, aVideoInfo()) ), aVideoInfo(), ), aMediaViewerState( - Async.Success( + AsyncData.Success( LocalMedia(Uri.EMPTY, aPdfInfo()) ), aPdfInfo(), ), aMediaViewerState( - Async.Loading(), + AsyncData.Loading(), aFileInfo(), ), aMediaViewerState( - Async.Success( + AsyncData.Success( LocalMedia(Uri.EMPTY, aFileInfo()) ), aFileInfo(), ), aMediaViewerState( - Async.Loading(), + AsyncData.Loading(), anAudioInfo(), ), aMediaViewerState( - Async.Success( + AsyncData.Success( LocalMedia(Uri.EMPTY, anAudioInfo()) ), anAudioInfo(), ), aMediaViewerState( - Async.Success( + AsyncData.Success( LocalMedia(Uri.EMPTY, anImageInfo()) ), anImageInfo(), @@ -83,7 +83,7 @@ open class MediaViewerStateProvider : PreviewParameterProvider } fun aMediaViewerState( - downloadedMedia: Async = Async.Uninitialized, + downloadedMedia: AsyncData = AsyncData.Uninitialized, mediaInfo: MediaInfo = anImageInfo(), canDownload: Boolean = true, canShare: Boolean = true, diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerView.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerView.kt index d7f743f7ad..2d5412cc25 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerView.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerView.kt @@ -47,7 +47,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import coil.compose.AsyncImage -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.dialogs.RetryDialog @@ -75,7 +75,6 @@ fun MediaViewerView( onBackPressed: () -> Unit, modifier: Modifier = Modifier, ) { - fun onRetry() { state.eventSink(MediaViewerEvents.RetryLoading) } @@ -93,7 +92,7 @@ fun MediaViewerView( modifier, topBar = { MediaViewerTopBar( - actionsEnabled = state.downloadedMedia is Async.Success, + actionsEnabled = state.downloadedMedia is AsyncData.Success, mimeType = state.mediaInfo.mimeType, onBackPressed = onBackPressed, canDownload = state.canDownload, @@ -121,7 +120,7 @@ fun MediaViewerView( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { - if (state.downloadedMedia is Async.Failure) { + if (state.downloadedMedia is AsyncData.Failure) { ErrorView( errorMessage = stringResource(id = CommonStrings.error_unknown), onRetry = ::onRetry, @@ -144,7 +143,7 @@ fun MediaViewerView( } @Composable -private fun rememberShowProgress(downloadedMedia: Async): Boolean { +private fun rememberShowProgress(downloadedMedia: AsyncData): Boolean { var showProgress by remember { mutableStateOf(false) } diff --git a/libraries/mediaviewer/api/src/test/kotlin/io/element/android/libraries/mediaviewer/MediaViewerPresenterTest.kt b/libraries/mediaviewer/api/src/test/kotlin/io/element/android/libraries/mediaviewer/MediaViewerPresenterTest.kt index ce2b4651a5..c5b761411a 100644 --- a/libraries/mediaviewer/api/src/test/kotlin/io/element/android/libraries/mediaviewer/MediaViewerPresenterTest.kt +++ b/libraries/mediaviewer/api/src/test/kotlin/io/element/android/libraries/mediaviewer/MediaViewerPresenterTest.kt @@ -23,16 +23,16 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.matrix.test.media.FakeMediaLoader import io.element.android.libraries.matrix.test.media.aMediaSource import io.element.android.libraries.mediaviewer.api.local.aFileInfo -import io.element.android.libraries.mediaviewer.test.FakeLocalMediaActions -import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerEvents import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerNode import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerPresenter +import io.element.android.libraries.mediaviewer.test.FakeLocalMediaActions +import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory import io.element.android.tests.testutils.WarmUpRule import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -43,11 +43,9 @@ import org.junit.Test private val TESTED_MEDIA_INFO = aFileInfo() class MediaViewerPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() - private val mockMediaUri: Uri = mockk("localMediaUri") private val localMediaFactory = FakeLocalMediaFactory(mockMediaUri) @@ -60,13 +58,13 @@ class MediaViewerPresenterTest { presenter.present() }.test { var state = awaitItem() - assertThat(state.downloadedMedia).isEqualTo(Async.Uninitialized) + assertThat(state.downloadedMedia).isEqualTo(AsyncData.Uninitialized) assertThat(state.mediaInfo).isEqualTo(TESTED_MEDIA_INFO) state = awaitItem() - assertThat(state.downloadedMedia).isInstanceOf(Async.Loading::class.java) + assertThat(state.downloadedMedia).isInstanceOf(AsyncData.Loading::class.java) state = awaitItem() val successData = state.downloadedMedia.dataOrNull() - assertThat(state.downloadedMedia).isInstanceOf(Async.Success::class.java) + assertThat(state.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) assertThat(successData).isNotNull() } } @@ -81,15 +79,15 @@ class MediaViewerPresenterTest { presenter.present() }.test { var state = awaitItem() - assertThat(state.downloadedMedia).isEqualTo(Async.Uninitialized) + assertThat(state.downloadedMedia).isEqualTo(AsyncData.Uninitialized) state = awaitItem() - assertThat(state.downloadedMedia).isInstanceOf(Async.Loading::class.java) + assertThat(state.downloadedMedia).isInstanceOf(AsyncData.Loading::class.java) // no state changes while media is loading state.eventSink(MediaViewerEvents.OpenWith) state.eventSink(MediaViewerEvents.Share) state.eventSink(MediaViewerEvents.SaveOnDisk) state = awaitItem() - assertThat(state.downloadedMedia).isInstanceOf(Async.Success::class.java) + assertThat(state.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) // Should succeed without change of state state.eventSink(MediaViewerEvents.OpenWith) // Should succeed without change of state @@ -128,21 +126,21 @@ class MediaViewerPresenterTest { }.test { mediaLoader.shouldFail = true val initialState = awaitItem() - assertThat(initialState.downloadedMedia).isEqualTo(Async.Uninitialized) + assertThat(initialState.downloadedMedia).isEqualTo(AsyncData.Uninitialized) assertThat(initialState.mediaInfo).isEqualTo(TESTED_MEDIA_INFO) val loadingState = awaitItem() - assertThat(loadingState.downloadedMedia).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState.downloadedMedia).isInstanceOf(AsyncData.Loading::class.java) val failureState = awaitItem() - assertThat(failureState.downloadedMedia).isInstanceOf(Async.Failure::class.java) + assertThat(failureState.downloadedMedia).isInstanceOf(AsyncData.Failure::class.java) mediaLoader.shouldFail = false failureState.eventSink(MediaViewerEvents.RetryLoading) - //There is one recomposition because of the retry mechanism + // There is one recomposition because of the retry mechanism skipItems(1) val retryLoadingState = awaitItem() - assertThat(retryLoadingState.downloadedMedia).isInstanceOf(Async.Loading::class.java) + assertThat(retryLoadingState.downloadedMedia).isInstanceOf(AsyncData.Loading::class.java) val successState = awaitItem() val successData = successState.downloadedMedia.dataOrNull() - assertThat(successState.downloadedMedia).isInstanceOf(Async.Success::class.java) + assertThat(successState.downloadedMedia).isInstanceOf(AsyncData.Success::class.java) assertThat(successData).isNotNull() } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt index 35ebc0e022..4e147a935b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt @@ -59,7 +59,6 @@ class AndroidLocalMediaActions @Inject constructor( private val coroutineDispatchers: CoroutineDispatchers, private val buildMeta: BuildMeta, ) : LocalMediaActions { - private var activityContext: Context? = null private var apkInstallLauncher: ManagedActivityResultLauncher? = null private var pendingMedia: LocalMedia? = null diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt index 1c026ce53b..9dc88c5deb 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt @@ -41,7 +41,6 @@ class AndroidLocalMediaFactory @Inject constructor( private val fileSizeFormatter: FileSizeFormatter, private val fileExtensionExtractor: FileExtensionExtractor, ) : LocalMediaFactory { - override fun createFromMediaFile(mediaFile: MediaFile, mediaInfo: MediaInfo): LocalMedia { val uri = mediaFile.toFile().toUri() return createFromUri( @@ -73,4 +72,3 @@ class AndroidLocalMediaFactory @Inject constructor( ) } } - diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActionsTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActionsTest.kt index 0e981d73af..8a0ba56609 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActionsTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActionsTest.kt @@ -36,7 +36,6 @@ import org.robolectric.RuntimeEnvironment @RunWith(RobolectricTestRunner::class) class AndroidLocalMediaActionsTest { - @Test fun `present - AndroidLocalMediaAction configure`() = runTest { val sut = createAndroidLocalMediaActions() diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt index c6209ae7c3..2563511ee2 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt @@ -31,7 +31,6 @@ import org.robolectric.RuntimeEnvironment @RunWith(RobolectricTestRunner::class) class AndroidLocalMediaFactoryTest { - @Test fun `test AndroidLocalMediaFactory`() { val sut = createAndroidLocalMediaFactory() diff --git a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaActions.kt b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaActions.kt index 8c303de541..1409a46146 100644 --- a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaActions.kt +++ b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaActions.kt @@ -22,12 +22,11 @@ import io.element.android.libraries.mediaviewer.api.local.LocalMediaActions import io.element.android.tests.testutils.simulateLongTask class FakeLocalMediaActions : LocalMediaActions { - var shouldFail = false @Composable override fun Configure() { - //NOOP + // NOOP } override suspend fun saveOnDisk(localMedia: LocalMedia): Result = simulateLongTask { diff --git a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt index 84f1d87613..0bb8a4b993 100644 --- a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt +++ b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt @@ -22,15 +22,14 @@ import io.element.android.libraries.matrix.api.media.MediaFile import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory import io.element.android.libraries.mediaviewer.api.local.MediaInfo -import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractor import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractorWithoutValidation +import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia class FakeLocalMediaFactory( private val localMediaUri: Uri, private val fileExtensionExtractor: FileExtensionExtractor = FileExtensionExtractorWithoutValidation() ) : LocalMediaFactory { - var fallbackMimeType: String = MimeTypes.OctetStream var fallbackName: String = "File name" var fallbackFileSize = "0B" diff --git a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/viewer/media.kt b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/viewer/LocalMedia.kt similarity index 99% rename from libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/viewer/media.kt rename to libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/viewer/LocalMedia.kt index e19113c163..876e82000d 100644 --- a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/viewer/media.kt +++ b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/viewer/LocalMedia.kt @@ -28,4 +28,3 @@ fun aLocalMedia( uri = uri, info = mediaInfo ) - diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/headers/HttpHeaders.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/headers/HttpHeaders.kt index 315835d584..3d8ee20ee0 100644 --- a/libraries/network/src/main/kotlin/io/element/android/libraries/network/headers/HttpHeaders.kt +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/headers/HttpHeaders.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.network.headers +@Suppress("ktlint:standard:property-naming") internal object HttpHeaders { const val Authorization = "Authorization" const val UserAgent = "User-Agent" diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/FormattedJsonHttpLogger.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/FormattedJsonHttpLogger.kt index d6c3144c5c..47048da5e9 100644 --- a/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/FormattedJsonHttpLogger.kt +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/FormattedJsonHttpLogger.kt @@ -23,9 +23,8 @@ import org.json.JSONObject import timber.log.Timber internal class FormattedJsonHttpLogger( - private val level: HttpLoggingInterceptor.Level + private val level: HttpLoggingInterceptor.Level ) : HttpLoggingInterceptor.Logger { - companion object { private const val INDENT_SPACE = 2 } @@ -68,8 +67,8 @@ internal class FormattedJsonHttpLogger( private fun logJson(formattedJson: String) { formattedJson - .lines() - .dropLastWhile { it.isEmpty() } - .forEach { Timber.v(it) } + .lines() + .dropLastWhile { it.isEmpty() } + .forEach { Timber.v(it) } } } diff --git a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsPresenter.kt b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsPresenter.kt index c4ab065ca0..3a8dd7e6ad 100644 --- a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsPresenter.kt +++ b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsPresenter.kt @@ -19,7 +19,6 @@ package io.element.android.libraries.permissions.api import io.element.android.libraries.architecture.Presenter interface PermissionsPresenter : Presenter { - interface Factory { fun create(permission: String): PermissionsPresenter } diff --git a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt index 9e2413fff9..38b40e6403 100644 --- a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt +++ b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt @@ -22,8 +22,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.ui.strings.CommonStrings @Composable diff --git a/libraries/permissions/api/src/main/res/values-es/translations.xml b/libraries/permissions/api/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..d530767ae0 --- /dev/null +++ b/libraries/permissions/api/src/main/res/values-es/translations.xml @@ -0,0 +1,7 @@ + + + "Para permitir que la aplicación utilice la cámara, por favor concede el permiso en los ajustes del sistema." + "Por favor concede el permiso en los ajustes del sistema." + "Para permitir que la aplicación utilice el micrófono, por favor conceda el permiso en los ajustes del sistema." + "Para permitir que la aplicación muestre notificaciones, por favor concede el permiso en los ajustes del sistema." + diff --git a/libraries/permissions/api/src/main/res/values-hu/translations.xml b/libraries/permissions/api/src/main/res/values-hu/translations.xml index de3573fce2..aea9a1f772 100644 --- a/libraries/permissions/api/src/main/res/values-hu/translations.xml +++ b/libraries/permissions/api/src/main/res/values-hu/translations.xml @@ -1,7 +1,7 @@ "Hogy az alkalmazás használhassa a kamerát, adja meg az engedélyt a rendszerbeállításokban." - "Adja meg az engedélyt a rendszerbeállításokban." + "Add meg az engedélyt a rendszerbeállításokban." "Hogy az alkalmazás használhassa a mikrofont, adja meg az engedélyt a rendszerbeállításokban." "Hogy az alkalmazás megjeleníthesse az értesítéseket, adja meg az engedélyt a rendszerbeállításokban." diff --git a/libraries/permissions/api/src/main/res/values-it/translations.xml b/libraries/permissions/api/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..2fe09c68ba --- /dev/null +++ b/libraries/permissions/api/src/main/res/values-it/translations.xml @@ -0,0 +1,7 @@ + + + "Per permettere all\'applicazione di usare la fotocamera, concedi l\'autorizzazione nelle impostazioni di sistema." + "Concedi l\'autorizzazione nelle impostazioni di sistema." + "Per permettere all\'applicazione di usare il microfono, concedi l\'autorizzazione nelle impostazioni di sistema." + "Per permettere all\'applicazione di mostrare notifiche, concedi l\'autorizzazione nelle impostazioni di sistema." + diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt index 86cc646982..c05df3de46 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt @@ -31,7 +31,7 @@ import javax.inject.Inject class DefaultPermissionStateProvider @Inject constructor( @ApplicationContext private val context: Context, private val permissionsStore: PermissionsStore, -): PermissionStateProvider { +) : PermissionStateProvider { override fun isPermissionGranted(permission: String): Boolean { return context.checkSelfPermission(permission) == android.content.pm.PackageManager.PERMISSION_GRANTED } diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt index 8fccfcc09d..45e5785ade 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt @@ -51,7 +51,6 @@ class DefaultPermissionsPresenter @AssistedInject constructor( private val composablePermissionStateProvider: ComposablePermissionStateProvider, private val permissionActions: PermissionActions, ) : PermissionsPresenter { - @AssistedFactory @ContributesBinding(AppScope::class) interface Factory : PermissionsPresenter.Factory { diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/FakePermissionStateProvider.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/FakePermissionStateProvider.kt index 364f8072a1..9d7d4d4e65 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/FakePermissionStateProvider.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/FakePermissionStateProvider.kt @@ -24,7 +24,7 @@ class FakePermissionStateProvider( private var permissionGranted: Boolean = true, permissionDenied: Boolean = false, permissionAsked: Boolean = false, -): PermissionStateProvider { +) : PermissionStateProvider { private val permissionDeniedFlow = MutableStateFlow(permissionDenied) private val permissionAskedFlow = MutableStateFlow(permissionAsked) diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt index a694c079e3..6370e839a3 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt @@ -27,7 +27,6 @@ import javax.inject.Inject class AndroidPermissionActions @Inject constructor( @ApplicationContext private val context: Context ) : PermissionActions { - override fun openSettings() { context.openAppSettingsPage() } diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenterTest.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenterTest.kt index ca96833d69..76034e2b1e 100644 --- a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenterTest.kt +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenterTest.kt @@ -35,7 +35,6 @@ import org.junit.Test const val A_PERMISSION = "A_PERMISSION" class DefaultPermissionsPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakeComposablePermissionStateProvider.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakeComposablePermissionStateProvider.kt index 948cc603c3..a4c423271f 100644 --- a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakeComposablePermissionStateProvider.kt +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakeComposablePermissionStateProvider.kt @@ -50,7 +50,6 @@ class FakePermissionState( override val permission: String, initialStatus: PermissionStatus, ) : PermissionState { - override var status: PermissionStatus by mutableStateOf(initialStatus) var launchPermissionRequestCalled = false diff --git a/libraries/permissions/noop/src/main/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenter.kt b/libraries/permissions/noop/src/main/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenter.kt index 653fe49268..4bbc3a6ee0 100644 --- a/libraries/permissions/noop/src/main/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenter.kt +++ b/libraries/permissions/noop/src/main/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenter.kt @@ -21,7 +21,6 @@ import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.api.PermissionsState class NoopPermissionsPresenter : PermissionsPresenter { - @Composable override fun present(): PermissionsState { return PermissionsState( diff --git a/libraries/permissions/noop/src/test/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenterTest.kt b/libraries/permissions/noop/src/test/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenterTest.kt index 828610a6fb..454b7b1dc3 100644 --- a/libraries/permissions/noop/src/test/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenterTest.kt +++ b/libraries/permissions/noop/src/test/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenterTest.kt @@ -26,7 +26,6 @@ import org.junit.Rule import org.junit.Test class NoopPermissionsPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() diff --git a/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionsPresenter.kt b/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionsPresenter.kt index 871f562489..e773ee89f2 100644 --- a/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionsPresenter.kt +++ b/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionsPresenter.kt @@ -26,7 +26,6 @@ import io.element.android.libraries.permissions.api.aPermissionsState class FakePermissionsPresenter( private val initialState: PermissionsState = aPermissionsState(showDialog = false), ) : PermissionsPresenter { - private fun eventSink(events: PermissionsEvents) { when (events) { PermissionsEvents.RequestPermissions -> state.value = state.value.copy(showDialog = true, permissionAlreadyAsked = true) diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt index c4a26215c2..c143b3ff6c 100644 --- a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt @@ -26,41 +26,41 @@ class InMemoryPreferencesStore( customElementCallBaseUrl: String? = null, theme: String? = null, ) : PreferencesStore { - private var _isRichTextEditorEnabled = MutableStateFlow(isRichTextEditorEnabled) - private var _isDeveloperModeEnabled = MutableStateFlow(isDeveloperModeEnabled) - private var _customElementCallBaseUrl = MutableStateFlow(customElementCallBaseUrl) - private var _theme = MutableStateFlow(theme) + private val isRichTextEditorEnabled = MutableStateFlow(isRichTextEditorEnabled) + private val isDeveloperModeEnabled = MutableStateFlow(isDeveloperModeEnabled) + private val customElementCallBaseUrl = MutableStateFlow(customElementCallBaseUrl) + private val theme = MutableStateFlow(theme) override suspend fun setRichTextEditorEnabled(enabled: Boolean) { - _isRichTextEditorEnabled.value = enabled + isRichTextEditorEnabled.value = enabled } override fun isRichTextEditorEnabledFlow(): Flow { - return _isRichTextEditorEnabled + return isRichTextEditorEnabled } override suspend fun setDeveloperModeEnabled(enabled: Boolean) { - _isDeveloperModeEnabled.value = enabled + isDeveloperModeEnabled.value = enabled } override fun isDeveloperModeEnabledFlow(): Flow { - return _isDeveloperModeEnabled + return isDeveloperModeEnabled } override suspend fun setCustomElementCallBaseUrl(string: String?) { - _customElementCallBaseUrl.tryEmit(string) + customElementCallBaseUrl.tryEmit(string) } override fun getCustomElementCallBaseUrlFlow(): Flow { - return _customElementCallBaseUrl + return customElementCallBaseUrl } override suspend fun setTheme(theme: String) { - _theme.value = theme + this.theme.value = theme } override fun getThemeFlow(): Flow { - return _theme + return theme } override suspend fun reset() { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt index d2650f853d..d4424da492 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt @@ -49,9 +49,11 @@ class PushersManager @Inject constructor( suspend fun testPush() { pushGatewayNotifyRequest.execute( PushGatewayNotifyRequest.Params( - url = "TODO", // unifiedPushHelper.getPushGateway() ?: return, - appId = PushConfig.pusher_app_id, - pushKey = "TODO", // unifiedPushHelper.getEndpointOrToken().orEmpty(), + // unifiedPushHelper.getPushGateway() ?: return + url = "TODO", + appId = PushConfig.PUSHER_APP_ID, + // unifiedPushHelper.getEndpointOrToken().orEmpty() + pushKey = "TODO", eventId = TEST_EVENT_ID ) ) @@ -85,11 +87,14 @@ class PushersManager @Inject constructor( ): SetHttpPusherData = SetHttpPusherData( pushKey = pushKey, - appId = PushConfig.pusher_app_id, - profileTag = DEFAULT_PUSHER_FILE_TAG + "_" /* TODO + abs(activeSessionHolder.getActiveSession().myUserId.hashCode())*/, - lang = "en", // TODO localeProvider.current().language, + appId = PushConfig.PUSHER_APP_ID, + // TODO + abs(activeSessionHolder.getActiveSession().myUserId.hashCode()) + profileTag = DEFAULT_PUSHER_FILE_TAG + "_", + // TODO localeProvider.current().language + lang = "en", appDisplayName = buildMeta.applicationName, - deviceDisplayName = "MyDevice", // TODO getDeviceInfoUseCase.execute().displayName().orEmpty(), + // TODO getDeviceInfoUseCase.execute().displayName().orEmpty() + deviceDisplayName = "MyDevice", url = gateway, defaultPayload = createDefaultPayload(pushClientSecret.getSecretForUser(userId)) ) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FilteredEventDetector.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FilteredEventDetector.kt index 2d4d27472b..3219427b8a 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FilteredEventDetector.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FilteredEventDetector.kt @@ -20,9 +20,8 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableEven import javax.inject.Inject class FilteredEventDetector @Inject constructor( - //private val activeSessionDataSource: ActiveSessionDataSource + // private val activeSessionDataSource: ActiveSessionDataSource ) { - /** * Returns true if the given event should be ignored. * Used to skip notifications if a non expected message is received. @@ -41,10 +40,10 @@ class FilteredEventDetector @Inject constructor( return false } + /* /** * Whether the timeline event should be ignored. */ - /* private fun TimelineEvent.shouldBeIgnored(): Boolean { if (root.isVoiceMessage()) { val audioEvent = root.asMessageAudioEvent() diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessor.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessor.kt index 7f4c04da7b..4da6dbdd59 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessor.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessor.kt @@ -36,7 +36,6 @@ class NotifiableEventProcessor @Inject constructor( private val outdatedDetector: OutdatedEventDetector, private val appNavigationStateService: AppNavigationStateService, ) { - fun process( queuedEvents: List, renderedEvents: ProcessedEvents, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt index 017627305e..ebf8ac6a89 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt @@ -38,6 +38,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageT import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType @@ -68,7 +69,6 @@ class NotifiableEventResolver @Inject constructor( private val notificationMediaRepoFactory: NotificationMediaRepo.Factory, @ApplicationContext private val context: Context, ) { - suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? { // Restore session val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null @@ -128,8 +128,10 @@ class NotifiableEventResolver @Inject constructor( isRedacted = false, isUpdated = false, description = descriptionFromRoomMembershipInvite(isDirect), - type = null, // TODO check if type is needed anymore - title = null, // TODO check if title is needed anymore + // TODO check if type is needed anymore + type = null, + // TODO check if title is needed anymore + title = null, ) } else { Timber.tag(loggerTag.value).d("Ignoring notification state event for membership ${content.membershipState}") @@ -231,6 +233,7 @@ class NotifiableEventResolver @Inject constructor( is EmoteMessageType -> "* $senderDisplayName ${messageType.body}" is FileMessageType -> messageType.body is ImageMessageType -> messageType.body + is StickerMessageType -> messageType.body is NoticeMessageType -> messageType.body is TextMessageType -> messageType.toPlainText() is VideoMessageType -> messageType.body diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationAction.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationAction.kt index b3f0b1e0f2..4b04c7dc5d 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationAction.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationAction.kt @@ -17,9 +17,9 @@ package io.element.android.libraries.push.impl.notifications data class NotificationAction( - val shouldNotify: Boolean, - val highlight: Boolean, - val soundName: String? + val shouldNotify: Boolean, + val highlight: Boolean, + val soundName: String? ) /* diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBitmapLoader.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBitmapLoader.kt index 4c1e3043ad..97232320c2 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBitmapLoader.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBitmapLoader.kt @@ -35,7 +35,6 @@ class NotificationBitmapLoader @Inject constructor( @ApplicationContext private val context: Context, private val sdkIntProvider: BuildVersionSdkIntProvider, ) { - /** * Get icon of a room. * @param path mxc url diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt index 0fdef871a1..360357af54 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt @@ -33,7 +33,6 @@ private val loggerTag = LoggerTag("NotificationBroadcastReceiver", LoggerTag.Not * Receives actions broadcast by notification (on click, on dismiss, inline replies, etc.). */ class NotificationBroadcastReceiver : BroadcastReceiver() { - @Inject lateinit var defaultNotificationDrawerManager: DefaultNotificationDrawerManager @Inject lateinit var actionIds: NotificationActionIds @@ -223,12 +222,9 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { VectorApp.getInstance().notificationDrawerManager.refreshNotificationDrawer(null) } }) - */ + */ } - */ - - /* private fun getReplyMessage(intent: Intent?): String? { if (intent != null) { val remoteInput = RemoteInput.getResultsFromIntent(intent) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueue.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueue.kt index bb76de47d2..c78244356e 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueue.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueue.kt @@ -34,11 +34,10 @@ data class NotificationEventQueue( * An in memory FIFO cache of the seen events. * Acts as a notification debouncer to stop already dismissed push notifications from * displaying again when the /sync response is delayed. + * TODO Should be per session, so the key must be Pair. */ - // TODO Should be per session, so the key must be Pair. private val seenEventIds: CircularCache ) { - fun markRedacted(eventIds: List) { eventIds.forEach { redactedId -> queue.replace(redactedId) { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt index 4abdc03123..ef3623f302 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt @@ -34,7 +34,6 @@ class NotificationFactory @Inject constructor( private val roomGroupMessageCreator: RoomGroupMessageCreator, private val summaryGroupMessageCreator: SummaryGroupMessageCreator ) { - suspend fun Map.toNotifications( currentUser: MatrixUser, imageLoader: ImageLoader, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationMediaRepo.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationMediaRepo.kt index 505a4f4a3c..6e17d7ba55 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationMediaRepo.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationMediaRepo.kt @@ -35,7 +35,6 @@ import java.io.File * Whenever a given mxc is found in the cache, it is returned immediately. */ interface NotificationMediaRepo { - /** * Factory for [NotificationMediaRepo]. */ @@ -72,7 +71,6 @@ class DefaultNotificationMediaRepo @AssistedInject constructor( private val mxcTools: MxcTools, @Assisted private val client: MatrixClient, ) : NotificationMediaRepo { - @ContributesBinding(AppScope::class) @AssistedFactory fun interface Factory : NotificationMediaRepo.Factory { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt index d05f3a1d03..2c826abf10 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt @@ -35,7 +35,6 @@ class NotificationRenderer @Inject constructor( private val notificationDisplayer: NotificationDisplayer, private val notificationFactory: NotificationFactory, ) { - suspend fun render( currentUser: MatrixUser, useCompleteNotificationFormat: Boolean, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationState.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationState.kt index 0d8731548d..fb19bd76fd 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationState.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationState.kt @@ -29,7 +29,6 @@ class NotificationState( * Events are unique by their properties, we should be careful not to insert multiple events with the same event-id. */ private val queuedEvents: NotificationEventQueue, - /** * The last known rendered notifiable events. * We keep track of them in order to know which events have been removed from the eventList @@ -37,7 +36,6 @@ class NotificationState( */ private val renderedEvents: MutableList>, ) { - fun updateQueuedEvents( action: (NotificationEventQueue, List>) -> T ): T { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/OutdatedEventDetector.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/OutdatedEventDetector.kt index 27713399fc..52e61a7ec6 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/OutdatedEventDetector.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/OutdatedEventDetector.kt @@ -20,9 +20,8 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableEven import javax.inject.Inject class OutdatedEventDetector @Inject constructor( - /// private val activeSessionDataSource: ActiveSessionDataSource + // / private val activeSessionDataSource: ActiveSessionDataSource ) { - /** * Returns true if the given event is outdated. * Used to clean up notifications if a displayed message has been read on an diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt index 015b32c1d0..d4bd8e7155 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt @@ -38,7 +38,6 @@ class RoomGroupMessageCreator @Inject constructor( private val stringProvider: StringProvider, private val notificationCreator: NotificationCreator ) { - suspend fun createRoomMessage( currentUser: MatrixUser, events: List, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt index f46df7d3a5..18aadb5de9 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt @@ -43,7 +43,6 @@ class SummaryGroupMessageCreator @Inject constructor( private val stringProvider: StringProvider, private val notificationCreator: NotificationCreator, ) { - fun createSummaryNotification( currentUser: MatrixUser, roomNotifications: List, @@ -73,7 +72,7 @@ class SummaryGroupMessageCreator @Inject constructor( val nbEvents = roomNotifications.size + simpleNotifications.size val sumTitle = stringProvider.getQuantityString(R.plurals.notification_compat_summary_title, nbEvents, nbEvents) summaryInboxStyle.setBigContentTitle(sumTitle.annotateForDebug(43)) - //.setSummaryText(stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages, nbEvents, nbEvents).annotateForDebug(44)) + // .setSummaryText(stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages, nbEvents, nbEvents).annotateForDebug(44)) // Use account name now, for multi-session .setSummaryText(currentUser.userId.value.annotateForDebug(44)) return if (useCompleteNotificationFormat) { @@ -119,7 +118,8 @@ class SummaryGroupMessageCreator @Inject constructor( // Invitation and message val messageStr = stringProvider.getQuantityString( R.plurals.notification_new_messages_for_room, - messageNotificationCount, messageNotificationCount + messageNotificationCount, + messageNotificationCount ) if (roomCount > 1) { // In several rooms diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt index ec2cd227ca..152ed0a03e 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt @@ -21,7 +21,6 @@ import android.content.Context import android.content.Intent class TestNotificationReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { // TODO The test notification has been clicked, notify the ui } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannels.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannels.kt index 74020f9323..c66e530837 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannels.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannels.kt @@ -91,7 +91,8 @@ class NotificationChannels @Inject constructor( enableVibration(true) enableLights(true) lightColor = accentColor - }) + } + ) /** * Low notification importance: shows everywhere, but is not intrusive. @@ -107,7 +108,8 @@ class NotificationChannels @Inject constructor( setSound(null, null) enableLights(true) lightColor = accentColor - }) + } + ) notificationManager.createNotificationChannel( NotificationChannel( @@ -119,7 +121,8 @@ class NotificationChannels @Inject constructor( description = stringProvider.getString(R.string.notification_channel_listening_for_events) setSound(null, null) setShowBadge(false) - }) + } + ) notificationManager.createNotificationChannel( NotificationChannel( @@ -132,7 +135,8 @@ class NotificationChannels @Inject constructor( setSound(null, null) enableLights(true) lightColor = accentColor - }) + } + ) } private fun getChannel(channelId: String): NotificationChannel? { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/MarkAsReadActionFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/MarkAsReadActionFactory.kt index 7e208f414a..2dc724fb3d 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/MarkAsReadActionFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/MarkAsReadActionFactory.kt @@ -38,7 +38,7 @@ class MarkAsReadActionFactory @Inject constructor( private val clock: SystemClock, ) { fun create(roomInfo: RoomEventGroupInfo): NotificationCompat.Action? { - if (!NotificationConfig.supportMarkAsReadAction) return null + if (!NotificationConfig.SUPPORT_MARK_AS_READ_ACTION) return null val sessionId = roomInfo.sessionId.value val roomId = roomInfo.roomId.value val intent = Intent(context, NotificationBroadcastReceiver::class.java) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt index fd89ce455f..53af2c71d5 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt @@ -43,7 +43,7 @@ class QuickReplyActionFactory @Inject constructor( private val clock: SystemClock, ) { fun create(roomInfo: RoomEventGroupInfo, threadId: ThreadId?): NotificationCompat.Action? { - if (!NotificationConfig.supportQuickReplyAction) return null + if (!NotificationConfig.SUPPORT_QUICK_REPLY_ACTION) return null val sessionId = roomInfo.sessionId val roomId = roomInfo.roomId return buildQuickReplyIntent(sessionId, roomId, threadId)?.let { replyPendingIntent -> diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt index 4b30bbe86d..cd972e31b1 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt @@ -53,7 +53,6 @@ data class NotifiableMessageEvent( override val isRedacted: Boolean = false, override val isUpdated: Boolean = false ) : NotifiableEvent { - val type: String = EventType.MESSAGE override val description: String = body ?: "" val title: String = senderName ?: "" @@ -71,9 +70,9 @@ fun NotifiableEvent.shouldIgnoreEventInRoom(appNavigationState: AppNavigationSta val currentSessionId = appNavigationState.navigationState.currentSessionId() ?: return false return when (val currentRoomId = appNavigationState.navigationState.currentRoomId()) { null -> false - else -> appNavigationState.isInForeground - && sessionId == currentSessionId - && roomId == currentRoomId - && (this as? NotifiableMessageEvent)?.threadId == appNavigationState.navigationState.currentThreadId() + else -> appNavigationState.isInForeground && + sessionId == currentSessionId && + roomId == currentRoomId && + (this as? NotifiableMessageEvent)?.threadId == appNavigationState.navigationState.currentThreadId() } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/permission/NotificationPermissionManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/permission/NotificationPermissionManager.kt index e1fd17332e..7496b3a16b 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/permission/NotificationPermissionManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/permission/NotificationPermissionManager.kt @@ -32,7 +32,6 @@ class NotificationPermissionManager @Inject constructor( private val sdkIntProvider: BuildVersionSdkIntProvider, @ApplicationContext private val context: Context, ) { - @RequiresApi(Build.VERSION_CODES.TIRAMISU) fun isPermissionGranted(): Boolean { return ContextCompat.checkSelfPermission( @@ -57,7 +56,7 @@ class NotificationPermissionManager @Inject constructor( if (showRationale) R.string.permissions_rationale_msg_notification else 0 ) } - */ + */ fun eventuallyRevokePermission( activity: Activity, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt index cf5b13ce9b..a930db2708 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt @@ -52,7 +52,6 @@ class DefaultPushHandler @Inject constructor( private val buildMeta: BuildMeta, private val matrixAuthenticationService: MatrixAuthenticationService, ) : PushHandler { - private val coroutineScope = CoroutineScope(SupervisorJob()) // UI handler diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayAPI.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayAPI.kt index 02bd7850e9..d8de5429ef 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayAPI.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayAPI.kt @@ -15,7 +15,6 @@ */ package io.element.android.libraries.push.impl.pushgateway - import retrofit2.http.Body import retrofit2.http.POST diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt index b7649f6800..9e52d94049 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt @@ -23,7 +23,6 @@ import kotlinx.serialization.Serializable internal data class PushGatewayNotification( @SerialName("event_id") val eventId: String, - /** * Required. This is an array of devices that the notification should be sent to. */ diff --git a/libraries/push/impl/src/main/res/values-de/translations.xml b/libraries/push/impl/src/main/res/values-de/translations.xml index a600cb83f7..b807d7548e 100644 --- a/libraries/push/impl/src/main/res/values-de/translations.xml +++ b/libraries/push/impl/src/main/res/values-de/translations.xml @@ -48,6 +48,6 @@ "Hintergrundsynchronisation" "Google-Dienste" "Keine gültigen Google Play-Dienste gefunden. Benachrichtigungen funktionieren möglicherweise nicht richtig." - "Benachrichtigung" + "Mitteilung" "Schnelle Antwort" diff --git a/libraries/push/impl/src/main/res/values-es/translations.xml b/libraries/push/impl/src/main/res/values-es/translations.xml index 138a2ce8e1..6f5baf70d7 100644 --- a/libraries/push/impl/src/main/res/values-es/translations.xml +++ b/libraries/push/impl/src/main/res/values-es/translations.xml @@ -1,5 +1,53 @@ + "Llamada" + "Esperando eventos" + "Notificaciones ruidosas" + "Notificaciones silenciosas" + "** No se ha podido enviar - por favor, abre la sala" + "Unirse" + "Rechazar" + "Te invitó a chatear" + "Te mencionó: %1$s" + "Mensajes nuevos" + "Reaccionó con %1$s" + "Marcar como leído" + "Te invitó a unirte a la sala" + "Yo" + "¡Estás viendo la notificación! ¡Haz clic en mí!" + "%1$s: %2$s" + "%1$s: %2$s %3$s" + "%1$s y %2$s" + "%1$s en %2$s" + "%1$s en %2$s y %3$s" + + "%1$s: %2$d mensaje" + "%1$s: %2$d mensajes" + + + "%d notificación" + "%d notificaciones" + + + "%d invitación" + "%d invitaciones" + + + "%d mensaje nuevo" + "%d mensajes nuevos" + + + "%d mensaje notificado no leído" + "%d mensajes notificados no leídos" + + + "%d sala" + "%d salas" + + "Elige cómo recibir las notificaciones" + "Sincronización en segundo plano" + "Servicios de Google" + "No se han encontrado Servicios de Google Play válidos. Es posible que las notificaciones no funcionen correctamente." "Notificación" "Respuesta rápida" diff --git a/libraries/push/impl/src/main/res/values-hu/translations.xml b/libraries/push/impl/src/main/res/values-hu/translations.xml index f87455630b..23c3998bb8 100644 --- a/libraries/push/impl/src/main/res/values-hu/translations.xml +++ b/libraries/push/impl/src/main/res/values-hu/translations.xml @@ -4,7 +4,7 @@ "Események figyelése" "Zajos értesítések" "Csendes értesítések" - "** Nem sikerült elküldeni – nyissa meg a szobát" + "** Nem sikerült elküldeni – kérlek nyisd meg a szobát" "Csatlakozás" "Elutasítás" "Meghívta, hogy csevegjen" @@ -14,7 +14,7 @@ "Megjelölés olvasottként" "Meghívta, hogy csatlakozzon a szobához" "Én" - "Az értesítést nézi! Kattintson ide!" + "Az értesítést nézed! Kattints ide!" "%1$s: %2$s" "%1$s: %2$s %3$s" "%1$s és %2$s" @@ -44,7 +44,7 @@ "%d szoba" "%d szoba" - "Válassza ki az értesítések fogadásának módját" + "Válaszd ki az értesítések fogadásának módját" "Háttérszinkronizálás" "Google szolgáltatások" "A Google Play szolgáltatások nem találhatók. Előfordulhat, hogy az értesítések nem működnek megfelelően." diff --git a/libraries/push/impl/src/main/res/values-it/translations.xml b/libraries/push/impl/src/main/res/values-it/translations.xml index 92c5c06f9b..9e512001be 100644 --- a/libraries/push/impl/src/main/res/values-it/translations.xml +++ b/libraries/push/impl/src/main/res/values-it/translations.xml @@ -1,5 +1,51 @@ + "Chiamata" + "Notifiche silenziose" + "** Invio fallito - si prega di aprire la stanza" + "Entra" + "Rifiuta" + "Ti ha invitato a chattare" + "Ti ha menzionato: %1$s" + "Nuovi messaggi" + "Ha reagito con %1$s" + "Segna come letto" + "Ti ha invitato ad entrare nella stanza" + "Io" + "Stai visualizzando la notifica! Cliccami!" + "%1$s: %2$s" + "%1$s: %2$s %3$s" + "%1$s e %2$s" + "%1$s in %2$s" + "%1$s in %2$s e %3$s" + + "%1$s: %2$d messaggio" + "%1$s: %2$d messaggi" + + + "%d notifica" + "%d notifiche" + + + "%d invito" + "%d inviti" + + + "%d nuovo messaggio" + "%d nuovi messaggi" + + + "%d messaggio notificato non letto" + "%d messaggi notificati non letti" + + + "%d stanza" + "%d stanze" + + "Scegli come ricevere le notifiche" + "Sincronizzazione in background" + "Servizi Google" + "Google Play Services non trovato. Le notifiche non funzioneranno bene." "Notifica" "Risposta rapida" diff --git a/libraries/push/impl/src/main/res/values-zh-rTW/translations.xml b/libraries/push/impl/src/main/res/values-zh-rTW/translations.xml index 973981253b..76a2ada95b 100644 --- a/libraries/push/impl/src/main/res/values-zh-rTW/translations.xml +++ b/libraries/push/impl/src/main/res/values-zh-rTW/translations.xml @@ -6,7 +6,9 @@ "加入" "拒絕" "邀請您聊天" + "提及您:%1$s" "新訊息" + "回應 %1$s" "標示為已讀" "邀請您加入聊天室" "我" diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt index 28b001ca28..02da10a351 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt @@ -30,8 +30,8 @@ import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiable import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent -import io.element.android.services.appnavstate.api.NavigationState import io.element.android.services.appnavstate.api.AppNavigationState +import io.element.android.services.appnavstate.api.NavigationState import io.element.android.services.appnavstate.test.FakeAppNavigationStateService import io.element.android.services.appnavstate.test.aNavigationState import kotlinx.coroutines.flow.MutableStateFlow @@ -42,7 +42,6 @@ private val VIEWING_A_ROOM = aNavigationState(A_SESSION_ID, A_SPACE_ID, A_ROOM_I private val VIEWING_A_THREAD = aNavigationState(A_SESSION_ID, A_SPACE_ID, A_ROOM_ID, A_THREAD_ID) class NotifiableEventProcessorTest { - private val outdatedDetector = FakeOutdatedEventDetector() @Test diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt index bad49b64ef..d824a193d2 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt @@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageT import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType @@ -58,7 +59,6 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) class NotifiableEventResolverTest { - @Test fun `resolve event no session`() = runTest { val sut = createNotifiableEventResolver(notificationService = null) @@ -234,6 +234,23 @@ class NotifiableEventResolverTest { assertThat(result).isEqualTo(expectedResult) } + @Test + fun `resolve event message sticker`() = runTest { + val sut = createNotifiableEventResolver( + notificationResult = Result.success( + createNotificationData( + content = NotificationContent.MessageLike.RoomMessage( + senderId = A_USER_ID_2, + messageType = StickerMessageType("Sticker", MediaSource("url"), null), + ) + ) + ) + ) + val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) + val expectedResult = createNotifiableMessageEvent(body = "Sticker") + assertThat(result).isEqualTo(expectedResult) + } + @Test fun `resolve event message file`() = runTest { val sut = createNotifiableEventResolver( diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueueTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueueTest.kt index f9b63eb9dc..7cc2687207 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueueTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueueTest.kt @@ -28,7 +28,6 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableEven import org.junit.Test class NotificationEventQueueTest { - private val seenIdsCache = CircularCache.create(5) @Test diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt index ca41fcc15a..50ee91f448 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt @@ -41,7 +41,6 @@ private val A_MESSAGE_EVENT = aNotifiableMessageEvent(eventId = AN_EVENT_ID, roo @RunWith(RobolectricTestRunner::class) class NotificationFactoryTest { - private val androidNotificationFactory = FakeAndroidNotificationFactory() private val roomGroupMessageCreator = FakeRoomGroupMessageCreator() private val summaryGroupMessageCreator = FakeSummaryGroupMessageCreator() @@ -130,7 +129,9 @@ class NotificationFactoryTest { fun `given room with message when mapping to notification then delegates to room group message creator`() = testWith(notificationFactory) { val events = listOf(A_MESSAGE_EVENT) val expectedNotification = roomGroupMessageCreator.givenCreatesRoomMessageFor( - MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), events, A_ROOM_ID + MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), + events, + A_ROOM_ID ) val roomWithMessage = mapOf(A_ROOM_ID to listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, A_MESSAGE_EVENT))) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt index 18f2266b87..4780ae3914 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt @@ -41,13 +41,16 @@ private val A_SUMMARY_NOTIFICATION = SummaryNotification.Update(mockk()) private val A_REMOVE_SUMMARY_NOTIFICATION = SummaryNotification.Removed private val A_NOTIFICATION = mockk() private val MESSAGE_META = RoomNotification.Message.Meta( - summaryLine = "ignored", messageCount = 1, latestTimestamp = -1, roomId = A_ROOM_ID, shouldBing = false + summaryLine = "ignored", + messageCount = 1, + latestTimestamp = -1, + roomId = A_ROOM_ID, + shouldBing = false ) private val ONE_SHOT_META = OneShotNotification.Append.Meta(key = "ignored", summaryLine = "ignored", isNoisy = false, timestamp = -1) @RunWith(RobolectricTestRunner::class) class NotificationRendererTest { - private val notificationDisplayer = FakeNotificationDisplayer() private val notificationFactory = FakeNotificationFactory() private val notificationIdProvider = NotificationIdProvider() diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt index 946b0ab0db..a41a4aadc2 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt @@ -25,7 +25,6 @@ import io.mockk.coEvery import io.mockk.mockk class FakeRoomGroupMessageCreator { - val instance = mockk() fun givenCreatesRoomMessageFor( diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt index fc7b0553eb..546cb1e054 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt @@ -20,6 +20,5 @@ import io.element.android.libraries.push.impl.notifications.SummaryGroupMessageC import io.mockk.mockk class FakeSummaryGroupMessageCreator { - val instance = mockk() } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseConfig.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseConfig.kt index 62081a9e56..581b8c6dea 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseConfig.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseConfig.kt @@ -21,8 +21,8 @@ object FirebaseConfig { * It is the push gateway for firebase. * Note: pusher_http_url should have path '/_matrix/push/v1/notify' --> */ - const val pusher_http_url: String = "https://matrix.org/_matrix/push/v1/notify" + const val PUSHER_HTTP_URL: String = "https://matrix.org/_matrix/push/v1/notify" - const val index = 0 - const val name = "Firebase" + const val INDEX = 0 + const val NAME = "Firebase" } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt index 3e077841a4..313b9ab706 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt @@ -45,9 +45,9 @@ class FirebaseNewTokenHandler @Inject constructor( .map { SessionId(it) } .forEach { userId -> val userDataStore = userPushStoreFactory.create(userId) - if (userDataStore.getPushProviderName() == FirebaseConfig.name) { + if (userDataStore.getPushProviderName() == FirebaseConfig.NAME) { matrixAuthenticationService.restoreSession(userId).getOrNull()?.use { client -> - pusherSubscriber.registerPusher(client, firebaseToken, FirebaseConfig.pusher_http_url) + pusherSubscriber.registerPusher(client, firebaseToken, FirebaseConfig.PUSHER_HTTP_URL) } } else { Timber.tag(loggerTag.value).d("This session is not using Firebase pusher") diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt index 63611a0ed9..1fad74e1be 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt @@ -34,8 +34,8 @@ class FirebasePushProvider @Inject constructor( private val firebaseTroubleshooter: FirebaseTroubleshooter, private val pusherSubscriber: PusherSubscriber, ) : PushProvider { - override val index = FirebaseConfig.index - override val name = FirebaseConfig.name + override val index = FirebaseConfig.INDEX + override val name = FirebaseConfig.NAME override fun getDistributors(): List { return listOf(Distributor("Firebase", "Firebase")) @@ -45,14 +45,14 @@ class FirebasePushProvider @Inject constructor( val pushKey = firebaseStore.getFcmToken() ?: return Unit.also { Timber.tag(loggerTag.value).w("Unable to register pusher, Firebase token is not known.") } - pusherSubscriber.registerPusher(matrixClient, pushKey, FirebaseConfig.pusher_http_url) + pusherSubscriber.registerPusher(matrixClient, pushKey, FirebaseConfig.PUSHER_HTTP_URL) } override suspend fun unregister(matrixClient: MatrixClient) { val pushKey = firebaseStore.getFcmToken() ?: return Unit.also { Timber.tag(loggerTag.value).w("Unable to unregister pusher, Firebase token is not known.") } - pusherSubscriber.unregisterPusher(matrixClient, pushKey, FirebaseConfig.pusher_http_url) + pusherSubscriber.unregisterPusher(matrixClient, pushKey, FirebaseConfig.PUSHER_HTTP_URL) } override suspend fun troubleshoot(): Result { diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/RegisterUnifiedPushUseCase.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/RegisterUnifiedPushUseCase.kt index 4877eff555..8dd71118b3 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/RegisterUnifiedPushUseCase.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/RegisterUnifiedPushUseCase.kt @@ -29,7 +29,6 @@ class RegisterUnifiedPushUseCase @Inject constructor( private val pusherSubscriber: PusherSubscriber, private val unifiedPushStore: UnifiedPushStore, ) { - sealed interface RegisterUnifiedPushResult { data object Success : RegisterUnifiedPushResult data object NeedToAskUserForDistributor : RegisterUnifiedPushResult diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt index aad00c5bd7..07c57496db 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt @@ -21,8 +21,8 @@ object UnifiedPushConfig { * It is the push gateway for UnifiedPush. * Note: default_push_gateway_http_url should have path '/_matrix/push/v1/notify' */ - const val default_push_gateway_http_url: String = "https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify" + const val DEFAULT_PUSH_GATEWAY_HTTP_URL: String = "https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify" - const val index = 1 - const val name = "UnifiedPush" + const val INDEX = 1 + const val NAME = "UnifiedPush" } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayResolver.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayResolver.kt index 3b2abaf38a..54b80a5110 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayResolver.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayResolver.kt @@ -29,10 +29,10 @@ class UnifiedPushGatewayResolver @Inject constructor( private val coroutineDispatchers: CoroutineDispatchers, ) { suspend fun getGateway(endpoint: String): String? { - val gateway = UnifiedPushConfig.default_push_gateway_http_url + val gateway = UnifiedPushConfig.DEFAULT_PUSH_GATEWAY_HTTP_URL val url = URL(endpoint) - val port = if (url.port != -1) { ":${url.port}" } else { "" } - val customBase = "${url.protocol}://${url.host}${port}" + val port = if (url.port != -1) ":${url.port}" else "" + val customBase = "${url.protocol}://${url.host}$port" val customUrl = "$customBase/_matrix/push/v1/notify" Timber.i("Testing $customUrl") try { diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt index c10bc814d7..2ae8753a1e 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt @@ -41,7 +41,7 @@ class UnifiedPushNewGatewayHandler @Inject constructor( Timber.w("Unable to retrieve session") } val userDataStore = userPushStoreFactory.create(userId) - if (userDataStore.getPushProviderName() == UnifiedPushConfig.name) { + if (userDataStore.getPushProviderName() == UnifiedPushConfig.NAME) { matrixAuthenticationService.restoreSession(userId).getOrNull()?.use { client -> pusherSubscriber.registerPusher(client, endpoint, pushGateway) } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt index 6f74986ae5..bca4fd3ed3 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt @@ -35,8 +35,8 @@ class UnifiedPushProvider @Inject constructor( private val unRegisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase, private val pushClientSecret: PushClientSecret, ) : PushProvider { - override val index = UnifiedPushConfig.index - override val name = UnifiedPushConfig.name + override val index = UnifiedPushConfig.INDEX + override val name = UnifiedPushConfig.NAME override fun getDistributors(): List { val distributors = UnifiedPush.getDistributors(context) diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnregisterUnifiedPushUseCase.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnregisterUnifiedPushUseCase.kt index 4bf8217914..b6030564ca 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnregisterUnifiedPushUseCase.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnregisterUnifiedPushUseCase.kt @@ -24,14 +24,13 @@ import javax.inject.Inject class UnregisterUnifiedPushUseCase @Inject constructor( @ApplicationContext private val context: Context, - //private val pushDataStore: PushDataStore, + // private val pushDataStore: PushDataStore, private val unifiedPushStore: UnifiedPushStore, // private val unifiedPushGatewayResolver: UnifiedPushGatewayResolver, ) { - - suspend fun execute(clientSecret: String /*pushersManager: PushersManager?*/) { - //val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME - //pushDataStore.setFdroidSyncBackgroundMode(mode) + suspend fun execute(clientSecret: String) { + // val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME + // pushDataStore.setFdroidSyncBackgroundMode(mode) try { unifiedPushStore.getEndpoint(clientSecret)?.let { Timber.d("Removing $it") diff --git a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStore.kt b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStore.kt index a10413fdf5..fcfd6475c3 100644 --- a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStore.kt +++ b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStore.kt @@ -17,7 +17,6 @@ package io.element.android.libraries.pushstore.api import kotlinx.coroutines.flow.Flow - /** * Store data related to push about a user. */ diff --git a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecret.kt b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecret.kt index dbdd22ce07..1700d41181 100644 --- a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecret.kt +++ b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecret.kt @@ -29,9 +29,4 @@ interface PushClientSecret { * Return null if not found. */ suspend fun getUserIdFromSecret(clientSecret: String): SessionId? - - /** - * To call when the user signs out. - */ - suspend fun resetSecretForUser(userId: SessionId) } diff --git a/libraries/pushstore/impl/build.gradle.kts b/libraries/pushstore/impl/build.gradle.kts index 17e0268af1..28e53e011c 100644 --- a/libraries/pushstore/impl/build.gradle.kts +++ b/libraries/pushstore/impl/build.gradle.kts @@ -34,6 +34,7 @@ anvil { dependencies { implementation(libs.dagger) implementation(projects.libraries.architecture) + implementation(projects.libraries.androidutils) implementation(projects.libraries.core) implementation(projects.libraries.matrix.api) implementation(projects.libraries.pushstore.api) @@ -48,6 +49,7 @@ dependencies { testImplementation(libs.coroutines.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.services.appnavstate.test) + testImplementation(projects.libraries.sessionStorage.test) androidTestImplementation(libs.coroutines.test) androidTestImplementation(libs.test.core) diff --git a/libraries/pushstore/impl/src/androidTest/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactoryTest.kt b/libraries/pushstore/impl/src/androidTest/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactoryTest.kt index 67b07bfd1d..b7bbf46dc4 100644 --- a/libraries/pushstore/impl/src/androidTest/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactoryTest.kt +++ b/libraries/pushstore/impl/src/androidTest/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactoryTest.kt @@ -30,7 +30,6 @@ import kotlin.concurrent.thread * adb uninstall io.element.android.libraries.push.pushstore.impl.test */ class DefaultUserPushStoreFactoryTest { - /** * Ensure that creating UserPushStore is thread safe. */ diff --git a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt index 718ddb51fa..f7a159f6c3 100644 --- a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt +++ b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt @@ -23,12 +23,15 @@ import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore +import androidx.datastore.preferences.preferencesDataStoreFile +import io.element.android.libraries.androidutils.hash.hash import io.element.android.libraries.core.bool.orTrue import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.pushstore.api.UserPushStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import timber.log.Timber /** * Store data related to push about a user. @@ -37,7 +40,24 @@ class UserPushStoreDataStore( private val context: Context, userId: SessionId, ) : UserPushStore { - private val Context.dataStore: DataStore by preferencesDataStore(name = "push_store_$userId") + // Hash the sessionId to get rid of exotic chars and take only the first 16 chars. + // The risk of collision is not high. + private val preferenceName = "push_store_${userId.value.hash().take(16)}" + + init { + // Migrate legacy data. Previous file can be too long if the userId is too long. The userId can be up to 255 chars. + // Example of long file path, with `averylonguserid` replacing a very longer name + // /data/user/0/io.element.android.x.debug/files/datastore/push_store_@averylonguserid:example.org.preferences_pb + val legacyFile = context.preferencesDataStoreFile("push_store_$userId") + if (legacyFile.exists()) { + Timber.d("Migrating legacy push data store for $userId") + if (!legacyFile.renameTo(context.preferencesDataStoreFile(preferenceName))) { + Timber.w("Failed to migrate legacy push data store for $userId") + } + } + } + + private val Context.dataStore: DataStore by preferencesDataStore(name = preferenceName) private val pushProviderName = stringPreferencesKey("pushProviderName") private val currentPushKey = stringPreferencesKey("currentPushKey") private val notificationEnabled = booleanPreferencesKey("notificationEnabled") @@ -63,7 +83,7 @@ class UserPushStoreDataStore( } override fun getNotificationEnabledForDevice(): Flow { - return context.dataStore.data.map{ it[notificationEnabled].orTrue() } + return context.dataStore.data.map { it[notificationEnabled].orTrue() } } override suspend fun setNotificationEnabledForDevice(enabled: Boolean) { @@ -80,5 +100,7 @@ class UserPushStoreDataStore( context.dataStore.edit { it.clear() } + // Also delete the file + context.preferencesDataStoreFile(preferenceName).delete() } } diff --git a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImpl.kt b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImpl.kt index ca0ed14e33..5f0c83b6d7 100644 --- a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImpl.kt +++ b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImpl.kt @@ -18,17 +18,26 @@ package io.element.android.libraries.pushstore.impl.clientsecret import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecretFactory import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecretStore +import io.element.android.libraries.sessionstorage.api.observer.SessionListener +import io.element.android.libraries.sessionstorage.api.observer.SessionObserver import javax.inject.Inject -@ContributesBinding(AppScope::class) +@SingleIn(AppScope::class) +@ContributesBinding(AppScope::class, boundType = PushClientSecret::class) class PushClientSecretImpl @Inject constructor( private val pushClientSecretFactory: PushClientSecretFactory, private val pushClientSecretStore: PushClientSecretStore, -) : PushClientSecret { + private val sessionObserver: SessionObserver, +) : PushClientSecret, SessionListener { + init { + observeSessions() + } + override suspend fun getSecretForUser(userId: SessionId): String { val existingSecret = pushClientSecretStore.getSecret(userId) if (existingSecret != null) { @@ -43,7 +52,16 @@ class PushClientSecretImpl @Inject constructor( return pushClientSecretStore.getUserIdFromSecret(clientSecret) } - override suspend fun resetSecretForUser(userId: SessionId) { - pushClientSecretStore.resetSecret(userId) + private fun observeSessions() { + sessionObserver.addListener(this) + } + + override suspend fun onSessionCreated(userId: String) { + // Nothing to do + } + + override suspend fun onSessionDeleted(userId: String) { + // Delete the secret + pushClientSecretStore.resetSecret(SessionId(userId)) } } diff --git a/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImplTest.kt b/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImplTest.kt index 48e4daa40c..dc0e5b3651 100644 --- a/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImplTest.kt +++ b/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImplTest.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.pushstore.impl.clientsecret import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.sessionstorage.test.observer.NoOpSessionObserver import kotlinx.coroutines.test.runTest import org.junit.Test @@ -27,12 +28,11 @@ private val A_USER_ID_1 = SessionId("@A_USER_ID_1:domain") private const val A_UNKNOWN_SECRET = "A_UNKNOWN_SECRET" internal class PushClientSecretImplTest { - @Test fun test() = runTest { val factory = FakePushClientSecretFactory() val store = InMemoryPushClientSecretStore() - val sut = PushClientSecretImpl(factory, store) + val sut = PushClientSecretImpl(factory, store, NoOpSessionObserver()) val secret0 = factory.getSecretForUser(0) val secret1 = factory.getSecretForUser(1) @@ -57,7 +57,7 @@ internal class PushClientSecretImplTest { assertThat(sut.getUserIdFromSecret(A_UNKNOWN_SECRET)).isNull() // User signs out - sut.resetSecretForUser(A_USER_ID_0) + sut.onSessionDeleted(A_USER_ID_0.value) assertThat(store.getSecrets()).hasSize(1) // Create a new secret after reset assertThat(sut.getSecretForUser(A_USER_ID_0)).isEqualTo(secret2) diff --git a/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStore.kt b/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStore.kt index c697e0d3c9..2afbf3210e 100644 --- a/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStore.kt +++ b/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStore.kt @@ -20,8 +20,7 @@ import io.element.android.libraries.pushstore.api.UserPushStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -class FakeUserPushStore: UserPushStore { - +class FakeUserPushStore : UserPushStore { private var pushProviderName: String? = null private var currentRegisteredPushKey: String? = null private val notificationEnabledForDevice = MutableStateFlow(true) @@ -54,6 +53,5 @@ class FakeUserPushStore: UserPushStore { } override suspend fun reset() { - } } diff --git a/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt b/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt index f51893b00a..a529e34bc1 100644 --- a/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt +++ b/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt @@ -20,9 +20,8 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.pushstore.api.UserPushStore import io.element.android.libraries.pushstore.api.UserPushStoreFactory -class FakeUserPushStoreFactory: UserPushStoreFactory { +class FakeUserPushStoreFactory : UserPushStoreFactory { override fun create(userId: SessionId): UserPushStore { return FakeUserPushStore() } } - diff --git a/libraries/roomselect/api/src/main/kotlin/io/element/android/libraries/roomselect/api/RoomSelectEntryPoint.kt b/libraries/roomselect/api/src/main/kotlin/io/element/android/libraries/roomselect/api/RoomSelectEntryPoint.kt index 873d6f4a9b..e8ff7f2328 100644 --- a/libraries/roomselect/api/src/main/kotlin/io/element/android/libraries/roomselect/api/RoomSelectEntryPoint.kt +++ b/libraries/roomselect/api/src/main/kotlin/io/element/android/libraries/roomselect/api/RoomSelectEntryPoint.kt @@ -39,4 +39,3 @@ interface RoomSelectEntryPoint : FeatureEntryPoint { fun onCancel() } } - diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPoint.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPoint.kt index d3700c96f5..29f879bc50 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPoint.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPoint.kt @@ -47,4 +47,3 @@ class DefaultRoomSelectEntryPoint @Inject constructor() : RoomSelectEntryPoint { } } } - diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt index 16cd7b813d..51a1436e11 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt @@ -26,12 +26,12 @@ import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import io.element.android.libraries.roomselect.api.RoomSelectMode import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails +import io.element.android.libraries.roomselect.api.RoomSelectMode import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList @@ -40,7 +40,6 @@ class RoomSelectPresenter @AssistedInject constructor( @Assisted private val mode: RoomSelectMode, private val client: MatrixClient, ) : Presenter { - @AssistedFactory interface Factory { fun create(mode: RoomSelectMode): RoomSelectPresenter @@ -51,7 +50,7 @@ class RoomSelectPresenter @AssistedInject constructor( var selectedRooms by remember { mutableStateOf(persistentListOf()) } var query by remember { mutableStateOf("") } var isSearchActive by remember { mutableStateOf(false) } - var results: SearchBarResultState> by remember { mutableStateOf(SearchBarResultState.NotSearching()) } + var results: SearchBarResultState> by remember { mutableStateOf(SearchBarResultState.Initial()) } val summaries by client.roomListService.allRooms.summaries.collectAsState() @@ -64,7 +63,7 @@ class RoomSelectPresenter @AssistedInject constructor( results = if (filteredSummaries.isNotEmpty()) { SearchBarResultState.Results(filteredSummaries) } else { - SearchBarResultState.NoResults() + SearchBarResultState.NoResultsFound() } } diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt index 20fa7d910d..c451570cf5 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt @@ -16,9 +16,9 @@ package io.element.android.libraries.roomselect.impl -import io.element.android.libraries.roomselect.api.RoomSelectMode import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails +import io.element.android.libraries.roomselect.api.RoomSelectMode import kotlinx.collections.immutable.ImmutableList data class RoomSelectState( diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt index d06e0be6d6..032dc4a8d0 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt @@ -48,7 +48,7 @@ open class RoomSelectStateProvider : PreviewParameterProvider { } private fun aRoomSelectState( - resultState: SearchBarResultState> = SearchBarResultState.NotSearching(), + resultState: SearchBarResultState> = SearchBarResultState.Initial(), query: String = "", isSearchActive: Boolean = false, selectedRooms: ImmutableList = persistentListOf(), diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt index 264be0cca7..83b3608eae 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt @@ -138,7 +138,8 @@ fun RoomSelectView( LazyColumn { item { SelectedRoomsHelper( - isForwarding = false, // TODO state.isForwarding, + // TODO state.isForwarding + isForwarding = false, selectedRooms = state.selectedRooms ) } diff --git a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTests.kt b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTests.kt index 88ae560bb2..72ab751c39 100644 --- a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTests.kt +++ b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTests.kt @@ -20,12 +20,12 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.roomselect.api.RoomSelectMode import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.aRoomSummaryDetail import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService +import io.element.android.libraries.roomselect.api.RoomSelectMode import io.element.android.tests.testutils.WarmUpRule import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest @@ -33,7 +33,6 @@ import org.junit.Rule import org.junit.Test class RoomSelectPresenterTests { - @get:Rule val warmUpRule = WarmUpRule() @@ -45,11 +44,11 @@ class RoomSelectPresenterTests { }.test { val initialState = awaitItem() assertThat(initialState.selectedRooms).isEmpty() - assertThat(initialState.resultState).isInstanceOf(SearchBarResultState.NotSearching::class.java) + assertThat(initialState.resultState).isInstanceOf(SearchBarResultState.Initial::class.java) assertThat(initialState.isSearchActive).isFalse() // Search is run automatically val searchState = awaitItem() - assertThat(searchState.resultState).isInstanceOf(SearchBarResultState.NoResults::class.java) + assertThat(searchState.resultState).isInstanceOf(SearchBarResultState.NoResultsFound::class.java) } } @@ -85,7 +84,7 @@ class RoomSelectPresenterTests { initialState.eventSink(RoomSelectEvents.UpdateQuery("string not contained")) assertThat(awaitItem().query).isEqualTo("string not contained") - assertThat(awaitItem().resultState).isInstanceOf(SearchBarResultState.NoResults::class.java) + assertThat(awaitItem().resultState).isInstanceOf(SearchBarResultState.NoResultsFound::class.java) } } diff --git a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/LoginType.kt b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/LoginType.kt index ce29e3729a..e66818e275 100644 --- a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/LoginType.kt +++ b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/LoginType.kt @@ -28,7 +28,6 @@ enum class LoginType { QR; companion object { - fun fromName(name: String) = when (name) { PASSWORD.name -> PASSWORD OIDC.name -> OIDC diff --git a/libraries/session-storage/impl-memory/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/memory/InMemorySessionStore.kt b/libraries/session-storage/impl-memory/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/memory/InMemorySessionStore.kt index 4b76e82e8b..8302127214 100644 --- a/libraries/session-storage/impl-memory/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/memory/InMemorySessionStore.kt +++ b/libraries/session-storage/impl-memory/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/memory/InMemorySessionStore.kt @@ -24,7 +24,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map class InMemorySessionStore : SessionStore { - private var sessionDataFlow = MutableStateFlow(null) override fun isLoggedIn(): Flow { diff --git a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt index c437a4ef08..23d362d0b5 100644 --- a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt +++ b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt @@ -37,7 +37,6 @@ class DatabaseSessionStore @Inject constructor( private val database: SessionDatabase, private val dispatchers: CoroutineDispatchers, ) : SessionStore { - override fun isLoggedIn(): Flow { return database.sessionDataQueries.selectFirst() .asFlow() diff --git a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/di/SessionStorageModule.kt b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/di/SessionStorageModule.kt index 323aa93bb7..052943388e 100644 --- a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/di/SessionStorageModule.kt +++ b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/di/SessionStorageModule.kt @@ -34,10 +34,10 @@ object SessionStorageModule { @SingleIn(AppScope::class) fun provideMatrixDatabase(@ApplicationContext context: Context): SessionDatabase { val name = "session_database" - val secretFile = context.getDatabasePath("${name}.key") + val secretFile = context.getDatabasePath("$name.key") val passphraseProvider = RandomSecretPassphraseProvider(context, secretFile) val driver = SqlCipherDriverFactory(passphraseProvider) - .create(SessionDatabase.Schema, "${name}.db", context) + .create(SessionDatabase.Schema, "$name.db", context) return SessionDatabase(driver) } } diff --git a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt index cb5569fb62..a195c46c5c 100644 --- a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt +++ b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt @@ -30,7 +30,6 @@ import org.junit.Before import org.junit.Test class DatabaseSessionStoreTests { - private lateinit var database: SessionDatabase private lateinit var databaseSessionStore: DatabaseSessionStore @@ -166,7 +165,8 @@ class DatabaseSessionStoreTests { assertThat(alteredSession.refreshToken).isEqualTo(secondSessionData.refreshToken) assertThat(alteredSession.homeserverUrl).isEqualTo(secondSessionData.homeserverUrl) assertThat(alteredSession.slidingSyncProxy).isEqualTo(secondSessionData.slidingSyncProxy) - assertThat(alteredSession.loginTimestamp).isEqualTo(/* Not altered! */ firstSessionData.loginTimestamp) + // Check that alteredSession.loginTimestamp is not altered, so equal to firstSessionData.loginTimestamp + assertThat(alteredSession.loginTimestamp).isEqualTo(firstSessionData.loginTimestamp) assertThat(alteredSession.oidcData).isEqualTo(secondSessionData.oidcData) } } diff --git a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt index 6c31db3a9f..7542194bab 100644 --- a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt +++ b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt @@ -65,5 +65,3 @@ object TestTags { val dialogNegative = TestTag("dialog-negative") val dialogNeutral = TestTag("dialog-neutral") } - - diff --git a/libraries/textcomposer/impl/build.gradle.kts b/libraries/textcomposer/impl/build.gradle.kts index 6cce2b855d..0bbb508b3c 100644 --- a/libraries/textcomposer/impl/build.gradle.kts +++ b/libraries/textcomposer/impl/build.gradle.kts @@ -34,8 +34,15 @@ dependencies { implementation(projects.libraries.testtags) implementation(projects.libraries.uiUtils) - api(libs.matrix.richtexteditor) - api(libs.matrix.richtexteditor.compose) + releaseApi(libs.matrix.richtexteditor) + releaseApi(libs.matrix.richtexteditor.compose) + if (file("${rootDir.path}/libraries/textcomposer/lib/library-compose.aar").exists()) { + println("\nNote: Using local binaries of the Rich Text Editor.\n") + debugApi(projects.libraries.textcomposer.lib) + } else { + debugApi(libs.matrix.richtexteditor) + debugApi(libs.matrix.richtexteditor.compose) + } ksp(libs.showkase.processor) diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ElementRichTextEditorStyle.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ElementRichTextEditorStyle.kt index a7620cced0..ba98c0f973 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ElementRichTextEditorStyle.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ElementRichTextEditorStyle.kt @@ -31,7 +31,7 @@ object ElementRichTextEditorStyle { @Composable fun composerStyle( hasFocus: Boolean, - ) : RichTextEditorStyle { + ): RichTextEditorStyle { val baseStyle = common() return baseStyle.copy( text = baseStyle.text.copy( diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt index 0d6d01f000..06585d3b31 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.textcomposer +import android.net.Uri import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -111,6 +112,7 @@ fun TextComposer( onDeleteVoiceMessage: () -> Unit, onError: (Throwable) -> Unit, onSuggestionReceived: (Suggestion?) -> Unit, + onRichContentSelected: ((Uri) -> Unit)?, modifier: Modifier = Modifier, showTextFormatting: Boolean = false, subcomposing: Boolean = false, @@ -162,6 +164,7 @@ fun TextComposer( resolveMentionDisplay = { text, url -> TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor(text, url)) }, resolveRoomMentionDisplay = { TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor("@room", "#")) }, onError = onError, + onRichContentSelected = onRichContentSelected, ) } } @@ -397,6 +400,7 @@ private fun TextInput( resolveMentionDisplay: (text: String, url: String) -> TextDisplay, modifier: Modifier = Modifier, onError: (Throwable) -> Unit = {}, + onRichContentSelected: ((Uri) -> Unit)? = null, ) { val bgColor = ElementTheme.colors.bgSubtleSecondary val borderColor = ElementTheme.colors.borderDisabled @@ -443,7 +447,8 @@ private fun TextInput( style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.hasFocus), resolveMentionDisplay = resolveMentionDisplay, resolveRoomMentionDisplay = resolveRoomMentionDisplay, - onError = onError + onError = onError, + onRichContentSelected = onRichContentSelected, ) } } @@ -586,47 +591,52 @@ private fun ReplyToModeView( @PreviewsDayNight @Composable internal fun TextComposerSimplePreview() = ElementPreview { - PreviewColumn(items = persistentListOf( - { - ATextComposer( - RichTextEditorState("", initialFocus = true), - voiceMessageState = VoiceMessageState.Idle, - composerMode = MessageComposerMode.Normal, - enableTextFormatting = true, - enableVoiceMessages = true, - currentUserId = UserId("@alice:localhost"), - ) - }, { - ATextComposer( - RichTextEditorState("A message", initialFocus = true), - voiceMessageState = VoiceMessageState.Idle, - composerMode = MessageComposerMode.Normal, - enableTextFormatting = true, - enableVoiceMessages = true, - currentUserId = UserId("@alice:localhost") + PreviewColumn( + items = persistentListOf( + { + ATextComposer( + RichTextEditorState("", initialFocus = true), + voiceMessageState = VoiceMessageState.Idle, + composerMode = MessageComposerMode.Normal, + enableTextFormatting = true, + enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost"), + ) + }, + { + ATextComposer( + RichTextEditorState("A message", initialFocus = true), + voiceMessageState = VoiceMessageState.Idle, + composerMode = MessageComposerMode.Normal, + enableTextFormatting = true, + enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") + ) + }, + { + ATextComposer( + RichTextEditorState( + "A message\nWith several lines\nTo preview larger textfields and long lines with overflow", + initialFocus = true + ), + voiceMessageState = VoiceMessageState.Idle, + composerMode = MessageComposerMode.Normal, + enableTextFormatting = true, + enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") + ) + }, + { + ATextComposer( + RichTextEditorState("A message without focus", initialFocus = false), + voiceMessageState = VoiceMessageState.Idle, + composerMode = MessageComposerMode.Normal, + enableTextFormatting = true, + enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") + ) + } ) - }, { - ATextComposer( - RichTextEditorState( - "A message\nWith several lines\nTo preview larger textfields and long lines with overflow", - initialFocus = true - ), - voiceMessageState = VoiceMessageState.Idle, - composerMode = MessageComposerMode.Normal, - enableTextFormatting = true, - enableVoiceMessages = true, - currentUserId = UserId("@alice:localhost") - ) - }, { - ATextComposer( - RichTextEditorState("A message without focus", initialFocus = false), - voiceMessageState = VoiceMessageState.Idle, - composerMode = MessageComposerMode.Normal, - enableTextFormatting = true, - enableVoiceMessages = true, - currentUserId = UserId("@alice:localhost") - ) - }) ) } @@ -684,122 +694,129 @@ internal fun TextComposerEditPreview() = ElementPreview { @PreviewsDayNight @Composable internal fun TextComposerReplyPreview() = ElementPreview { - PreviewColumn(items = persistentListOf({ - ATextComposer( - RichTextEditorState(""), - voiceMessageState = VoiceMessageState.Idle, - composerMode = MessageComposerMode.Reply( - isThreaded = false, - senderName = "Alice", - eventId = EventId("$1234"), - attachmentThumbnailInfo = null, - defaultContent = "A message\n" + - "With several lines\n" + - "To preview larger textfields and long lines with overflow" - ), - enableTextFormatting = true, - enableVoiceMessages = true, - currentUserId = UserId("@alice:localhost") + PreviewColumn( + items = persistentListOf( + { + ATextComposer( + RichTextEditorState(""), + voiceMessageState = VoiceMessageState.Idle, + composerMode = MessageComposerMode.Reply( + isThreaded = false, + senderName = "Alice", + eventId = EventId("$1234"), + attachmentThumbnailInfo = null, + defaultContent = "A message\n" + + "With several lines\n" + + "To preview larger textfields and long lines with overflow" + ), + enableTextFormatting = true, + enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") + ) + }, + { + ATextComposer( + RichTextEditorState(""), + voiceMessageState = VoiceMessageState.Idle, + composerMode = MessageComposerMode.Reply( + isThreaded = true, + senderName = "Alice", + eventId = EventId("$1234"), + attachmentThumbnailInfo = null, + defaultContent = "A message\n" + + "With several lines\n" + + "To preview larger textfields and long lines with overflow" + ), + enableTextFormatting = true, + enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") + ) + }, + { + ATextComposer( + RichTextEditorState("A message"), + voiceMessageState = VoiceMessageState.Idle, + composerMode = MessageComposerMode.Reply( + isThreaded = true, + senderName = "Alice", + eventId = EventId("$1234"), + attachmentThumbnailInfo = AttachmentThumbnailInfo( + thumbnailSource = MediaSource("https://domain.com/image.jpg"), + textContent = "image.jpg", + type = AttachmentThumbnailType.Image, + blurHash = A_BLUR_HASH, + ), + defaultContent = "image.jpg" + ), + enableTextFormatting = true, + enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") + ) + }, + { + ATextComposer( + RichTextEditorState("A message"), + voiceMessageState = VoiceMessageState.Idle, + composerMode = MessageComposerMode.Reply( + isThreaded = false, + senderName = "Alice", + eventId = EventId("$1234"), + attachmentThumbnailInfo = AttachmentThumbnailInfo( + thumbnailSource = MediaSource("https://domain.com/video.mp4"), + textContent = "video.mp4", + type = AttachmentThumbnailType.Video, + blurHash = A_BLUR_HASH, + ), + defaultContent = "video.mp4" + ), + enableTextFormatting = true, + enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") + ) + }, + { + ATextComposer( + RichTextEditorState("A message"), + voiceMessageState = VoiceMessageState.Idle, + composerMode = MessageComposerMode.Reply( + isThreaded = false, + senderName = "Alice", + eventId = EventId("$1234"), + attachmentThumbnailInfo = AttachmentThumbnailInfo( + thumbnailSource = null, + textContent = "logs.txt", + type = AttachmentThumbnailType.File, + blurHash = null, + ), + defaultContent = "logs.txt" + ), + enableTextFormatting = true, + enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") + ) + }, + { + ATextComposer( + RichTextEditorState("A message", initialFocus = true), + voiceMessageState = VoiceMessageState.Idle, + composerMode = MessageComposerMode.Reply( + isThreaded = false, + senderName = "Alice", + eventId = EventId("$1234"), + attachmentThumbnailInfo = AttachmentThumbnailInfo( + thumbnailSource = null, + textContent = null, + type = AttachmentThumbnailType.Location, + blurHash = null, + ), + defaultContent = "Shared location" + ), + enableTextFormatting = true, + enableVoiceMessages = true, + currentUserId = UserId("@alice:localhost") + ) + } ) - }, - { - ATextComposer( - RichTextEditorState(""), - voiceMessageState = VoiceMessageState.Idle, - composerMode = MessageComposerMode.Reply( - isThreaded = true, - senderName = "Alice", - eventId = EventId("$1234"), - attachmentThumbnailInfo = null, - defaultContent = "A message\n" + - "With several lines\n" + - "To preview larger textfields and long lines with overflow" - ), - enableTextFormatting = true, - enableVoiceMessages = true, - currentUserId = UserId("@alice:localhost") - ) - }, { - ATextComposer( - RichTextEditorState("A message"), - voiceMessageState = VoiceMessageState.Idle, - composerMode = MessageComposerMode.Reply( - isThreaded = true, - senderName = "Alice", - eventId = EventId("$1234"), - attachmentThumbnailInfo = AttachmentThumbnailInfo( - thumbnailSource = MediaSource("https://domain.com/image.jpg"), - textContent = "image.jpg", - type = AttachmentThumbnailType.Image, - blurHash = A_BLUR_HASH, - ), - defaultContent = "image.jpg" - ), - enableTextFormatting = true, - enableVoiceMessages = true, - currentUserId = UserId("@alice:localhost") - ) - }, { - ATextComposer( - RichTextEditorState("A message"), - voiceMessageState = VoiceMessageState.Idle, - composerMode = MessageComposerMode.Reply( - isThreaded = false, - senderName = "Alice", - eventId = EventId("$1234"), - attachmentThumbnailInfo = AttachmentThumbnailInfo( - thumbnailSource = MediaSource("https://domain.com/video.mp4"), - textContent = "video.mp4", - type = AttachmentThumbnailType.Video, - blurHash = A_BLUR_HASH, - ), - defaultContent = "video.mp4" - ), - enableTextFormatting = true, - enableVoiceMessages = true, - currentUserId = UserId("@alice:localhost") - ) - }, { - ATextComposer( - RichTextEditorState("A message"), - voiceMessageState = VoiceMessageState.Idle, - composerMode = MessageComposerMode.Reply( - isThreaded = false, - senderName = "Alice", - eventId = EventId("$1234"), - attachmentThumbnailInfo = AttachmentThumbnailInfo( - thumbnailSource = null, - textContent = "logs.txt", - type = AttachmentThumbnailType.File, - blurHash = null, - ), - defaultContent = "logs.txt" - ), - enableTextFormatting = true, - enableVoiceMessages = true, - currentUserId = UserId("@alice:localhost") - ) - }, { - ATextComposer( - RichTextEditorState("A message", initialFocus = true), - voiceMessageState = VoiceMessageState.Idle, - composerMode = MessageComposerMode.Reply( - isThreaded = false, - senderName = "Alice", - eventId = EventId("$1234"), - attachmentThumbnailInfo = AttachmentThumbnailInfo( - thumbnailSource = null, - textContent = null, - type = AttachmentThumbnailType.Location, - blurHash = null, - ), - defaultContent = "Shared location" - ), - enableTextFormatting = true, - enableVoiceMessages = true, - currentUserId = UserId("@alice:localhost") - ) - }) ) } @@ -902,5 +919,6 @@ private fun ATextComposer( onDeleteVoiceMessage = {}, onError = {}, onSuggestionReceived = {}, + onRichContentSelected = null, ) } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposerLinkDialog.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposerLinkDialog.kt index 82b84889d3..b0078a40eb 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposerLinkDialog.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposerLinkDialog.kt @@ -24,13 +24,13 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.components.dialogs.ListDialog import io.element.android.libraries.designsystem.components.list.TextFieldListItem import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.wysiwyg.view.models.LinkAction @@ -234,4 +234,3 @@ internal fun TextComposerLinkDialogEditLinkPreview() = ElementPreview { onRemoveLinkRequest = {}, ) } - diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/ComposerOptionsButton.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/ComposerOptionsButton.kt index c833b46b23..e37c8ebc49 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/ComposerOptionsButton.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/ComposerOptionsButton.kt @@ -21,13 +21,13 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.textcomposer.R -import io.element.android.compound.theme.ElementTheme @Composable internal fun ComposerOptionsButton( @@ -41,7 +41,7 @@ internal fun ComposerOptionsButton( ) { Icon( modifier = Modifier.size(30.dp), - resourceId = CommonDrawables.ic_plus, + resourceId = CommonDrawables.ic_plus_composer, contentDescription = stringResource(R.string.rich_text_editor_a11y_add_attachment), tint = ElementTheme.colors.iconPrimary, ) @@ -53,4 +53,3 @@ internal fun ComposerOptionsButton( internal fun ComposerOptionsButtonPreview() = ElementPreview { ComposerOptionsButton(onClick = {}) } - diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/DismissTextFormattingButton.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/DismissTextFormattingButton.kt index e2cca41f1d..110ec776c6 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/DismissTextFormattingButton.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/DismissTextFormattingButton.kt @@ -21,12 +21,12 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.utils.CommonDrawables -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -53,4 +53,3 @@ internal fun DismissTextFormattingButton( internal fun DismissTextFormattingButtonPreview() = ElementPreview { DismissTextFormattingButton(onClick = {}) } - diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOption.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOption.kt index 850a0e3c38..ea768c4f3b 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOption.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOption.kt @@ -32,12 +32,12 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.iconSuccessPrimaryBackground import io.element.android.libraries.designsystem.utils.CommonDrawables -import io.element.android.compound.theme.ElementTheme @Composable internal fun FormattingOption( @@ -112,4 +112,3 @@ internal fun FormattingButtonPreview() = ElementPreview { ) } } - diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOptionState.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOptionState.kt index 386fa5a668..65b9204cc0 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOptionState.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOptionState.kt @@ -17,6 +17,7 @@ package io.element.android.libraries.textcomposer.components internal enum class FormattingOptionState { - Default, Selected, Disabled + Default, + Selected, + Disabled } - diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/LiveWaveformView.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/LiveWaveformView.kt index 03bf4554f6..b8bb6eb439 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/LiveWaveformView.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/LiveWaveformView.kt @@ -31,6 +31,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.graphicsLayer @@ -38,16 +39,17 @@ import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.components.media.drawWaveform import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.compound.theme.ElementTheme import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList import java.lang.Float.min private const val DEFAULT_GRAPHICS_LAYER_ALPHA: Float = 0.99F private val waveFormHeight = 26.dp + @Composable fun LiveWaveformView( levels: ImmutableList, @@ -66,8 +68,8 @@ fun LiveWaveformView( } } - - Box(contentAlignment = Alignment.CenterEnd, + Box( + contentAlignment = Alignment.CenterEnd, modifier = modifier .fillMaxWidth() .height(waveFormHeight) @@ -79,11 +81,12 @@ fun LiveWaveformView( .graphicsLayer(alpha = DEFAULT_GRAPHICS_LAYER_ALPHA) .then(modifier) ) { - canvasSize = DpSize(Dp(min(waveformWidth, parentWidth.toFloat())), size.height.toDp()) + val width = min(waveformWidth, parentWidth.toFloat()) + canvasSize = DpSize(width.dp, size.height.toDp()) val countThatFitsWidth = (parentWidth.toFloat() / (lineWidth.toPx() + linePadding.toPx())).toInt() drawWaveform( waveformData = levels.takeLast(countThatFitsWidth).toPersistentList(), - canvasSize = canvasSize, + canvasSizePx = Size(canvasSize.width.toPx(), size.height), brush = brush, lineWidth = lineWidth, linePadding = linePadding, @@ -96,7 +99,6 @@ fun LiveWaveformView( @Composable internal fun LiveWaveformViewPreview() = ElementPreview { Column { - LiveWaveformView( levels = List(100) { it.toFloat() / 100 }.toPersistentList(), modifier = Modifier.height(34.dp), diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/TextFormatting.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/TextFormatting.kt index 2df01c5f3a..81fd1ca81c 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/TextFormatting.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/TextFormatting.kt @@ -48,7 +48,6 @@ internal fun TextFormatting( state: RichTextEditorState, modifier: Modifier = Modifier, ) { - val scrollState = rememberScrollState() val coroutineScope = rememberCoroutineScope() diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt index 08415e9e10..db6a489321 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView import io.element.android.libraries.designsystem.components.media.createFakeWaveform import io.element.android.libraries.designsystem.preview.ElementPreview @@ -44,7 +45,6 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.CommonDrawables -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.utils.time.formatShort import kotlinx.collections.immutable.ImmutableList @@ -115,7 +115,8 @@ internal fun VoiceMessagePreview( } private enum class PlayerButtonType { - Play, Pause + Play, + Pause } @Composable diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt index 01403ee3d7..c3825cef1d 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt @@ -14,7 +14,6 @@ * limitations under the License. */ - package io.element.android.libraries.textcomposer.components import androidx.compose.foundation.background @@ -28,14 +27,14 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent -import io.element.android.compound.theme.ElementTheme -import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.ui.strings.CommonStrings @Composable diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecording.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecording.kt index 877e4832f4..f98fbd5b9e 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecording.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecording.kt @@ -38,10 +38,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.ui.utils.time.formatShort import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt index ff93bb754a..9788f1f6c3 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt @@ -21,6 +21,8 @@ import android.graphics.Paint import android.graphics.RectF import android.graphics.Typeface import android.text.style.ReplacementSpan +import io.element.android.libraries.core.extensions.orEmpty +import kotlin.math.min import kotlin.math.roundToInt class MentionSpan( @@ -31,52 +33,63 @@ class MentionSpan( val endPadding: Int, val typeface: Typeface = Typeface.DEFAULT, ) : ReplacementSpan() { + companion object { + private const val MAX_LENGTH = 20 + } + + private var actualText: CharSequence? = null + private var textWidth = 0 + private var cachedRect: RectF = RectF() + private val backgroundPaint = Paint().apply { + isAntiAlias = true + color = backgroundColor + } override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int { - val mentionText = getActualText(text, start) - var actualEnd = end - if (mentionText != text.toString()) { - actualEnd = end + 1 - } + val mentionText = getActualText(text, start, end) paint.typeface = typeface - return paint.measureText(mentionText, start, actualEnd).roundToInt() + startPadding + endPadding + textWidth = paint.measureText(mentionText, 0, mentionText.length).roundToInt() + return textWidth + startPadding + endPadding } override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) { - val mentionText = getActualText(text, start) - var actualEnd = end - if (mentionText != text.toString()) { - actualEnd = end + 1 - } - val textWidth = paint.measureText(mentionText, start, actualEnd) + val mentionText = getActualText(text, start, end) + // Extra vertical space to add below the baseline (y). This helps us center the span vertically val extraVerticalSpace = y + paint.ascent() + paint.descent() - top - val rect = RectF(x, top.toFloat(), x + textWidth + startPadding + endPadding, y.toFloat() + extraVerticalSpace) - paint.color = backgroundColor - canvas.drawRoundRect(rect, rect.height() / 2, rect.height() / 2, paint) + if (cachedRect.isEmpty) { + cachedRect = RectF(x, top.toFloat(), x + textWidth + startPadding + endPadding, y.toFloat() + extraVerticalSpace) + } + + val rect = cachedRect + val radius = rect.height() / 2 + canvas.drawRoundRect(rect, radius, radius, backgroundPaint) paint.color = textColor paint.typeface = typeface - canvas.drawText(mentionText, start, actualEnd, x + startPadding, y.toFloat(), paint) + canvas.drawText(mentionText, 0, mentionText.length, x + startPadding, y.toFloat(), paint) } - private fun getActualText(text: CharSequence?, start: Int): String { - return when (type) { - Type.USER -> { - val mentionText = text.toString() - if (start in mentionText.indices && mentionText[start] != '@') { - mentionText.replaceRange(start, start, "@") - } else { - mentionText + private fun getActualText(text: CharSequence?, start: Int, end: Int): CharSequence { + if (actualText != null) return actualText!! + return buildString { + val mentionText = text.orEmpty() + when (type) { + Type.USER -> { + if (start in mentionText.indices && mentionText[start] != '@') { + append("@") + } + } + Type.ROOM -> { + if (start in mentionText.indices && mentionText[start] != '#') { + append("#") + } } } - Type.ROOM -> { - val mentionText = text.toString() - if (start in mentionText.indices && mentionText[start] != '#') { - mentionText.replaceRange(start, start, "#") - } else { - mentionText - } + append(mentionText.substring(start, min(end, start + MAX_LENGTH))) + if (end - start > MAX_LENGTH) { + append("…") } + actualText = this } } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MessageComposerMode.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MessageComposerMode.kt index 9abf6bc52e..2e2869aec7 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MessageComposerMode.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MessageComposerMode.kt @@ -26,7 +26,7 @@ import kotlinx.parcelize.Parcelize @Immutable sealed interface MessageComposerMode : Parcelable { @Parcelize - data object Normal: MessageComposerMode + data object Normal : MessageComposerMode sealed class Special(open val eventId: EventId?, open val defaultContent: String) : MessageComposerMode diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/Suggestion.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/Suggestion.kt index e3174f74d0..015677c99c 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/Suggestion.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/Suggestion.kt @@ -25,7 +25,7 @@ data class Suggestion( val type: SuggestionType, val text: String, ) { - constructor(suggestion: SuggestionPattern): this( + constructor(suggestion: SuggestionPattern) : this( suggestion.start.toInt(), suggestion.end.toInt(), SuggestionType.fromPatternKey(suggestion.key), diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessagePlayerEvent.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessagePlayerEvent.kt index 193c72969b..1b35eca80b 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessagePlayerEvent.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessagePlayerEvent.kt @@ -17,10 +17,10 @@ package io.element.android.libraries.textcomposer.model sealed interface VoiceMessagePlayerEvent { - data object Play: VoiceMessagePlayerEvent - data object Pause: VoiceMessagePlayerEvent + data object Play : VoiceMessagePlayerEvent + data object Pause : VoiceMessagePlayerEvent data class Seek( val position: Float - ): VoiceMessagePlayerEvent + ) : VoiceMessagePlayerEvent } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageRecorderEvent.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageRecorderEvent.kt index 17091930c5..7536f96a68 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageRecorderEvent.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageRecorderEvent.kt @@ -17,7 +17,7 @@ package io.element.android.libraries.textcomposer.model sealed interface VoiceMessageRecorderEvent { - data object Start: VoiceMessageRecorderEvent - data object Stop: VoiceMessageRecorderEvent - data object Cancel: VoiceMessageRecorderEvent + data object Start : VoiceMessageRecorderEvent + data object Stop : VoiceMessageRecorderEvent + data object Cancel : VoiceMessageRecorderEvent } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageState.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageState.kt index 94a854a491..ec9118e330 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageState.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageState.kt @@ -22,7 +22,7 @@ import kotlin.time.Duration @Immutable sealed interface VoiceMessageState { - data object Idle: VoiceMessageState + data object Idle : VoiceMessageState data class Preview( val isSending: Boolean, @@ -31,10 +31,10 @@ sealed interface VoiceMessageState { val playbackProgress: Float, val time: Duration, val waveform: ImmutableList, - ): VoiceMessageState + ) : VoiceMessageState data class Recording( val duration: Duration, val levels: ImmutableList, - ): VoiceMessageState + ) : VoiceMessageState } diff --git a/libraries/textcomposer/impl/src/main/res/values-es/translations.xml b/libraries/textcomposer/impl/src/main/res/values-es/translations.xml index 606e3bde8e..fd38301104 100644 --- a/libraries/textcomposer/impl/src/main/res/values-es/translations.xml +++ b/libraries/textcomposer/impl/src/main/res/values-es/translations.xml @@ -1,8 +1,11 @@ "Lista de puntos" + "Cerrar opciones de formato" "Bloque de código" "Mensaje…" + "Crear un enlace" + "Editar enlace" "Aplicar formato negrita" "Aplicar formato cursiva" "Aplicar formato tachado" @@ -12,6 +15,11 @@ "Código" "Enlazar" "Lista numérica" + "Abrir opciones de formato" "Cita" + "Eliminar enlace" "Quitar sangría" + "Enlace" + "Adjuntar archivo" + "Mantén pulsado para grabar" diff --git a/libraries/textcomposer/impl/src/main/res/values-it/translations.xml b/libraries/textcomposer/impl/src/main/res/values-it/translations.xml index e3034e8dfe..18bd369d74 100644 --- a/libraries/textcomposer/impl/src/main/res/values-it/translations.xml +++ b/libraries/textcomposer/impl/src/main/res/values-it/translations.xml @@ -1,8 +1,11 @@ "Attiva/disattiva l\'elenco puntato" + "Chiudi le opzioni di formattazione" "Attiva/disattiva il blocco di codice" "Messaggio…" + "Crea un collegamento" + "Modifica collegamento" "Applica il formato in grassetto" "Applicare il formato corsivo" "Applica il formato barrato" @@ -12,6 +15,11 @@ "Applicare il formato del codice in linea" "Imposta collegamento" "Attiva/disattiva elenco numerato" + "Apri le opzioni di composizione" "Attiva/disattiva citazione" + "Rimuovi collegamento" "Rientro a sinistra" + "Collegamento" + "Aggiungi allegato" + "Tieni premuto per registrare" diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt index b615dc489a..c8281cca0e 100644 --- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt @@ -28,7 +28,6 @@ import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class MentionSpanProviderTest { - @JvmField @Rule val warmUpRule = WarmUpRule() diff --git a/libraries/textcomposer/lib/.gitignore b/libraries/textcomposer/lib/.gitignore new file mode 100644 index 0000000000..67f29a6964 --- /dev/null +++ b/libraries/textcomposer/lib/.gitignore @@ -0,0 +1,2 @@ +# Built application files +*.aar diff --git a/libraries/textcomposer/lib/build.gradle.kts b/libraries/textcomposer/lib/build.gradle.kts new file mode 100644 index 0000000000..3fbff24e20 --- /dev/null +++ b/libraries/textcomposer/lib/build.gradle.kts @@ -0,0 +1,3 @@ +configurations.maybeCreate("default") +artifacts.add("default", file("library.aar")) +artifacts.add("default", file("library-compose.aar")) diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index 085bb2c40a..1578326b9f 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -24,7 +24,7 @@ "Sprachnachricht aufnehmen." "Aufnahme beenden" "Akzeptieren" - "Zur Zeitleiste hinzufügen" + "Zum Nachrichtenverlauf hinzufügen" "Zurück" "Abbrechen" "Foto auswählen" @@ -50,8 +50,8 @@ "Passwort vergessen?" "Weiter" "Einladen" - "Freunde einladen" - "Freunde einladen %1$s" + "Personen einladen" + "Zu %1$s einladen" "Lade Personen in %1$s ein" "Einladungen" "Beitreten" @@ -62,8 +62,8 @@ "Geräte verwalten" "Weiter" "Nein" - "Nicht jetzt" - "OK" + "Später" + "Ok" "Einstellungen öffnen" "Öffnen mit" "Schnelle Antwort" @@ -90,10 +90,10 @@ "Chat starten" "Verifizierung starten" "Tippe, um die Karte zu laden" - "Foto machen" + "Foto aufnehmen" "Für Optionen tippen" "Erneut versuchen" - "Quelle anzeigen" + "Quellcode anzeigen" "Ja" "Mehr laden …" "Über" @@ -110,6 +110,7 @@ "Dunkel" "Dekodierungsfehler" "Entwickleroptionen" + "Direktnachricht" "(bearbeitet)" "Bearbeitung" "* %1$s %2$s" @@ -146,6 +147,7 @@ "Datenschutz­erklärung" "Reaktion" "Reaktionen" + "Wiederherstellungsschlüssel" "Wird erneuert…" "%1$s antworten" "Einen Fehler melden" diff --git a/libraries/ui-strings/src/main/res/values-es/translations.xml b/libraries/ui-strings/src/main/res/values-es/translations.xml index eaab1988d5..9239bcea4b 100644 --- a/libraries/ui-strings/src/main/res/values-es/translations.xml +++ b/libraries/ui-strings/src/main/res/values-es/translations.xml @@ -1,11 +1,33 @@ + "Borrar" "Ocultar contraseña" + "Ir al final" + "Sólo menciones" + "Silenciado" + "Página %1$d" + "Pausar" + "Campo PIN" + "Reproducir" + "Encuesta" + "Encuesta finalizada" + "Reacciona con %1$s" + "Reacciona con otros emojis" + "Leído por %1$s y %2$s" + "Leído por %1$s" + "Pulsa para mostrar todo" + "Elimina la reacción con %1$s" "Enviar archivos" "Mostrar contraseña" + "Iniciar llamada" "Menú de usuario" + "Grabar mensaje de voz" + "Detener grabación" + "Aceptar" + "Añadir a la cronología" "Atrás" "Cancelar" + "Elegir foto" "Borrar" "Cerrar" "Completar verificación" @@ -13,24 +35,43 @@ "Continuar" "Copiar" "Copiar enlace" + "Copiar enlace al mensaje" + "Crear" "Crear una sala" + "Rechazar" + "Eliminar encuesta" "Desactivar" "Hecho" "Editar" + "Editar encuesta" "Activar" + "Finalizar encuesta" + "Introducir PIN" + "¿Olvidaste tu contraseña?" + "Reenviar" "Invitar" - "Invitar amigos a %1$s" + "Invitar personas" + "Invita a alguien a %1$s" + "Invita a alguien a %1$s" + "Invitaciones" + "Unirse" "Más información" "Salir" "Salir de la sala" + "Gestionar cuenta" + "Administrar dispositivos" "Siguiente" "No" "Ahora no" "OK" + "Ajustes" + "Abrir con" "Respuesta rápida" "Citar" + "Reaccionar" "Eliminar" "Responder" + "Responder en el hilo" "Informar de un error" "Reportar Contenido" "Reintentar" @@ -38,73 +79,166 @@ "Guardar" "Buscar" "Enviar" + "Enviar mensaje" "Compartir" "Compartir enlace" + "Inicia sesión de nuevo" + "Cerrar sesión" + "Cerrar sesión de todos modos" "Saltar" "Comenzar" "Iniciar chat" "Iniciar la verificación" + "Pulsa para cargar el mapa" + "Hacer foto" + "Toca para ver opciones" + "Inténtalo de nuevo" "Ver Fuente" "Sí" + "Cargar más" "Acerca de" + "Política de uso aceptable" + "Ajustes avanzados" + "Estadísticas" + "Apariencia" "Sonido" "Burbujas" + "Copia de seguridad del chat" + "Derechos de autor" "Creando sala…" "Saliste de la sala" + "Oscuro" "Error de descifrado" "Opciones de desarrollador" + "Chat directo" "(editado)" "Edición" + "* %1$s %2$s" "Cifrado activado" + "Introduce tu PIN" "Error" + "Todos" "Archivo" + "Archivo guardado en Descargas" + "Reenviar mensaje" "GIF" "Imagen" + "En respuesta a %1$s" + "Instalar APK" + "No se encontró este ID de Matrix, por lo que es posible que no se reciba la invitación." + "Abandonando la sala" + "Claro" "Enlace copiado al portapapeles" "Cargando…" "Mensaje" - "Diseño del mensaje" + "Acciones del mensaje" + "Diseño de los mensajes" "Mensaje eliminado" "Moderno" + "Silenciar" "No hay resultados" "Sin conexión" "Contraseña" "Personas" "Enlace permanente" + "Permiso" + "Total de votos: %1$s" + "Los resultados se mostrarán una vez finalizada la encuesta" + "Política de privacidad" + "Reacción" "Reacciones" + "Clave de recuperación" + "Recargando…" "Respondiendo a %1$s" "Informar de un error" + "Informar de un problema" "Informe enviado" + "Editor de texto enriquecido" + "Sala" + "Nombre de la sala" + "p. ej., el nombre de tu proyecto" + "Bloqueo de pantalla" "Buscar a alguien" + "Buscar resultados" "Seguridad" + "Visto por" "Enviando…" + "Fallo al enviar" + "Enviado" "Servidor no compatible" "Dirección del servidor" "Ajustes" + "Ubicación compartida" + "Cerrando sesión" + "Iniciando chat…" "Sticker" "Terminado" "Sugerencias" + "Sincronizando" + "Sistema" + "Texto" + "Avisos de terceros" + "Hilo" "Tema" + "¿De qué trata esta sala?" "No se puede descifrar" + "Las invitaciones no se pudieron enviar a uno o más usuarios." + "No se pudo enviar la(s) invitación(es)" + "Desbloquear" + "Dejar de silenciar" "Evento no compatible" "Usuario" "Verificación cancelada" "Verificación completada" "Vídeo" + "Mensaje de voz" "Esperando…" + "Esperando este mensaje" + "¿Estás seguro de que quieres finalizar esta encuesta?" + "Encuesta: %1$s" + "Verificar dispositivo" "Confirmar" "Atención" "No se pudo crear el enlace permanente" + "%1$s no pudo cargar el mapa. Por favor vuelve a intentarlo más tarde." "Error al cargar mensajes" + "%1$s no ha podido acceder a tu ubicación. Por favor vuelve a intentarlo más tarde." + "No se pudo cargar tu mensaje de voz." + "%1$s no tiene permiso para acceder a tu ubicación. Puedes habilitar el acceso en Ajustes." + "%1$s no tiene permiso para acceder a tu ubicación. Habilita el acceso a continuación." + "%1$s no tiene permiso para acceder al micrófono. Habilita el acceso para grabar un mensaje de voz." "Algunos mensajes no se han enviado" "Lo siento, se ha producido un error" + "🔐️ Únete a mí en %1$s" "Hola, puedes hablar conmigo en %1$s: %2$s" "%1$s Android" + + "%1$d dígito introducido" + "%1$d dígitos introducidos" + + + "Leído por %1$s y %2$d otro" + "Leído por %1$s y %2$d otros" + "%1$d miembro" "%1$d miembros" + + "%d voto" + "%d votos" + "Agitar con fuerza para informar de un error" + "Error al seleccionar archivos multimedia, por favor inténtalo de nuevo." + "Error al procesar el contenido multimedia, por favor inténtalo de nuevo." + "Error al subir el contenido multimedia, por favor inténtalo de nuevo." + "Compartir ubicación" + "Compartir mi ubicación" + "Abrir en Apple Maps" + "Abrir en Google Maps" + "Abrir en OpenStreetMap" + "Compartir esta ubicación" + "Ubicación" "Versión: %1$s (%2$s)" "es" "Error" diff --git a/libraries/ui-strings/src/main/res/values-hu/translations.xml b/libraries/ui-strings/src/main/res/values-hu/translations.xml index f59c70a05a..530ab7125b 100644 --- a/libraries/ui-strings/src/main/res/values-hu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml @@ -47,7 +47,7 @@ "Engedélyezés" "Szavazás lezárása" "Adja meg a PIN-kódot" - "Elfelejtette a jelszót?" + "Elfelejtetted a jelszavadat?" "Tovább" "Meghívás" "Ismerősök meghívása" @@ -82,17 +82,17 @@ "Üzenet küldése" "Megosztás" "Hivatkozás megosztása" - "Jelentkezzen be újra" + "Jelentkezz be újra" "Kijelentkezés" "Kijelentkezés mindenképp" "Kihagyás" "Indítás" "Csevegés indítása" "Ellenőrzés elindítása" - "Koppintson a térkép betöltéséhez" + "Koppints a térkép betöltéséhez" "Fénykép készítése" "Koppintson a lehetőségekért" - "Próbálja újra" + "Próbáld újra" "Forrás megtekintése" "Igen" "Továbbiak betöltése" @@ -106,7 +106,7 @@ "Csevegés biztonsági mentése" "Szerzői jogok" "Szoba létrehozása…" - "Elhagyta a szobát" + "Elhagytad a szobát" "Sötét" "Visszafejtési hiba" "Fejlesztői beállítások" @@ -115,7 +115,7 @@ "Szerkesztés" "* %1$s %2$s" "Titkosítás engedélyezve" - "Adja meg a PIN-kódját" + "Add meg a PIN-kódodat" "Hiba" "Mindenki" "Fájl" @@ -156,7 +156,7 @@ "Formázott szöveges szerkesztő" "Szoba" "Szoba neve" - "például a projekt neve" + "például a projekted neve" "Képernyőzár" "Személy keresése" "Keresési találatok" @@ -202,9 +202,9 @@ "Nem sikerült létrehozni az állandó hivatkozást" "Az %1$s nem tudta betölteni a térképet. Próbálja meg újra később." "Nem sikerült betölteni az üzeneteket" - "Az %1$s nem tudta elérni a tartózkodási helyét. Próbálja meg újra később." + "A(z) %1$s nem tudta elérni a tartózkodási helyét. Próbáld meg újra később." "Nem sikerült feltölteni a hangüzenetét." - "Az %1$snek nincs engedélye, hogy hozzáférjen a tartózkodási helyéhez. Ezt a beállításokban engedélyezheti." + "Az %1$snek nincs engedélye, hogy hozzáférjen a tartózkodási helyedhez. Ezt a beállításokban engedélyezheted." "Az %1$snek nincs engedélye, hogy hozzáférjen a tartózkodási helyéhez. Engedélyezze alább az elérését." "Az %1$snek nincs engedélye, hogy hozzáférjen a mikrofonjához. Engedélyezze, hogy tudjon hangüzenetet felvenni." "Néhány üzenet nem került elküldésre" diff --git a/libraries/ui-strings/src/main/res/values-in/translations.xml b/libraries/ui-strings/src/main/res/values-in/translations.xml index f7f05f71f2..e19aa1005d 100644 --- a/libraries/ui-strings/src/main/res/values-in/translations.xml +++ b/libraries/ui-strings/src/main/res/values-in/translations.xml @@ -50,8 +50,8 @@ "Lupa kata sandi?" "Teruskan" "Undang" - "Undang teman" - "Undang teman ke %1$s" + "Undang orang-orang" + "Undang orang-orang ke %1$s" "Undang orang-orang ke %1$s" "Undangan" "Gabung" diff --git a/libraries/ui-strings/src/main/res/values-it/translations.xml b/libraries/ui-strings/src/main/res/values-it/translations.xml index cfd24d7c08..fc783fae31 100644 --- a/libraries/ui-strings/src/main/res/values-it/translations.xml +++ b/libraries/ui-strings/src/main/res/values-it/translations.xml @@ -1,11 +1,33 @@ + "Elimina" "Nascondi password" + "Vai alla fine" + "Solo menzioni" + "Silenziato" + "Pagina %1$d" + "Pausa" + "Campo del PIN" + "Riproduci" + "Sondaggio" + "Sondaggio terminato" + "Reagisci con %1$s" + "Reagisci con altri emoji" + "Letto da %1$s e %2$s" + "Letto da %1$s" + "Tocca per mostrare tutti" + "Rimuovi la reazione con %1$s" "Invia file" "Mostra password" + "Inizia una chiamata" "Menu utente" + "Registra un messaggio vocale." + "Ferma la registrazione" + "Accetta" + "Aggiungi alla timeline" "Indietro" "Annulla" + "Scegli foto" "Cancella" "Chiudi" "Completa verifica" @@ -13,24 +35,43 @@ "Continua" "Copia" "Copia collegamento" + "Copia link nel messaggio" + "Crea" "Crea una stanza" + "Rifiuta" + "Elimina sondaggio" "Disabilita" "Fine" "Modifica" + "Modifica sondaggio" "Attiva" + "Termina sondaggio" + "Inserisci PIN" + "Password dimenticata?" + "Inoltra" "Invita" - "Invita amici a %1$s" + "Invita persone" + "Invita persone su %1$s" + "Invita persone su %1$s" + "Inviti" + "Entra" "Ulteriori informazioni" "Esci" "Esci dalla stanza" + "Gestisci account" + "Gestisci dispositivi" "Avanti" "No" "Non ora" "OK" + "Impostazioni" + "Apri con" "Risposta rapida" "Citazione" + "Reagisci" "Rimuovi" "Rispondi" + "Rispondi nella conversazione" "Segnala un problema" "Segnala Contenuto" "Riprova" @@ -38,73 +79,166 @@ "Salva" "Ricerca" "Invia" + "Invia messaggio" "Condividi" "Condividi collegamento" + "Accedi di nuovo" + "Disconnetti" + "Disconnetti comunque" "Salta" "Inizia" "Avvia conversazione" "Avvia la verifica" + "Tocca per caricare la mappa" + "Scatta foto" + "Tocca per le opzioni" + "Riprova" "Vedi Sorgente" "Sì" + "Carica di più" "Informazioni" + "Regole sull\'utilizzo consentito" + "Impostazioni avanzate" + "Statistiche" + "Aspetto" "Audio" "Fumetti" + "Backup della chat" + "Copyright" "Creazione stanza…" "Hai lasciato la stanza" + "Scuro" "Errore di decrittazione" "Opzioni sviluppatore" + "Chat diretta" "(modificato)" "Modifica in corso" + "* %1$s %2$s" "Crittografia abilitata" + "Inserisci il PIN" "Errore" + "Tutti" "File" + "File salvato in Download" + "Inoltra messaggio" "GIF" "Immagine" + "In risposta a %1$s" + "Installa APK" + "Questo ID Matrix non può essere trovato, quindi l\'invito potrebbe non essere ricevuto." + "Lasciando la stanza" + "Chiaro" "Collegamento copiato negli appunti" "Caricamento…" "Messaggio" - "Layout del messaggio" + "Azioni del messaggio" + "Impaginazione del messaggio" "Messaggio rimosso" "Moderno" + "Silenzia" "Nessun risultato" "Non in linea" "Password" "Persone" "Collegamento permanente" + "Autorizzazione" + "Voti totali: %1$s" + "I risultati verranno mostrati al termine del sondaggio" + "Informativa sulla privacy" + "Reazione" "Reazioni" + "Chiave di recupero" + "Aggiornamento…" "Risposta a %1$s" "Segnala un problema" + "Segnala un problema" "Segnalazione inviata" + "Editor in rich text" + "Stanza" + "Nome stanza" + "ad es. il nome del tuo progetto" + "Blocco schermo" "Cerca qualcuno" + "Risultati di ricerca" "Sicurezza" + "Visto da" "Invio in corso…" + "Invio fallito" + "Inviato" "Server non supportato" "URL del server" "Impostazioni" + "Posizione condivisa" + "Disconnessione" + "Avvio della chat…" "Adesivo" "Operazione riuscita" "Suggerimenti" + "Sincronizzazione" + "Sistema" + "Testo" + "Comunicazioni di terze parti" + "Conversazione" "Oggetto" + "Di cosa parla questa stanza?" "Impossibile decrittografare" + "Non è stato possibile spedire inviti a uno o più utenti." + "Impossibile inviare inviti" + "Sblocca" + "Annulla silenzioso" "Evento non supportato" "Nome utente" "Verifica annullata" "Verifica completata" "Video" + "Messaggio vocale" "In attesa…" + "In attesa di questo messaggio" + "Vuoi davvero terminare questo sondaggio?" + "Sondaggio: %1$s" + "Verifica dispositivo" "Conferma" "Attenzione" "Impossibile creare il collegamento permanente" + "%1$s non è riuscito a caricare la mappa. Riprova più tardi." "Caricamento dei messaggi non riuscito" + "%1$s non è riuscito ad accedere alla tua posizione. Riprova più tardi." + "Invio del messaggio vocale fallito." + "%1$s non ha l\'autorizzazione di accedere alla tua posizione. Puoi attivare l\'accesso nelle impostazioni." + "%1$s non ha l\'autorizzazione per accedere alla tua posizione. Attiva l\'accesso di seguito." + "%1$s non ha l\'autorizzazione di accedere al microfono. Attiva l\'accesso per registrare un messaggio vocale." "Alcuni messaggi non sono stati inviati" "Siamo spiacenti, si è verificato un errore" + "🔐️ Unisciti a me su %1$s" "Ehi, parlami su %1$s: %2$s" "%1$s Android" + + "%1$d cifra inserita" + "%1$d cifre inserite" + + + "Letto da %1$s e %2$d altro" + "Letto da %1$s e altri %2$d" + "%1$d membro" "%1$d membri" + + "%d voto" + "%d voti" + "Scuoti per segnalare un problema" + "Selezione del file multimediale fallita, riprova." + "Elaborazione del file multimediale da caricare fallita, riprova." + "Caricamento del file multimediale fallito, riprova." + "Condividi posizione" + "Condividi la mia posizione" + "Apri in Apple Maps" + "Apri in Google Maps" + "Apri in OpenStreetMap" + "Condividi questa posizione" + "Posizione" "Versione: %1$s (%2$s)" "it" "Errore" diff --git a/libraries/ui-strings/src/main/res/values-ru/translations.xml b/libraries/ui-strings/src/main/res/values-ru/translations.xml index b6576d988c..4f035ce514 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -98,9 +98,9 @@ "Загрузить еще" "О приложении" "Политика допустимого использования" - "Дополнительные параметры" + "Дополнительные настройки" "Аналитика" - "Оформление" + "Внешний вид" "Аудио" "Пузыри" "Резервная копия чатов" @@ -139,7 +139,7 @@ "Ничего не найдено" "Не в сети" "Пароль" - "Пользователи" + "Люди" "Постоянная ссылка" "Разрешение" "Всего голосов: %1$s" @@ -173,7 +173,7 @@ "Начало чата…" "Стикер" "Успешно" - "Рекомендации" + "Предложения" "Синхронизация" "Системная" "Текст" @@ -232,7 +232,7 @@ "%d голоса" "%d голосов" - "Rageshake сообщит об ошибке" + "Встряхните устройство, чтобы сообщить об ошибке" "Не удалось выбрать носитель, попробуйте еще раз." "Не удалось обработать медиафайл для загрузки, попробуйте еще раз." "Не удалось загрузить медиафайлы, попробуйте еще раз." diff --git a/libraries/ui-strings/src/main/res/values-sk/translations.xml b/libraries/ui-strings/src/main/res/values-sk/translations.xml index 9b7db8c032..29ecb56649 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -50,8 +50,8 @@ "Zabudnuté heslo?" "Preposlať" "Pozvať" - "Pozvať priateľov" - "Pozvať priateľov do %1$s" + "Pozvať ľudí" + "Pozvať ľudí do %1$s" "Pozvať ľudí do %1$s" "Pozvánky" "Pripojiť sa" diff --git a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml index 2b3de76895..76cdca0b2b 100644 --- a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml +++ b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml @@ -2,15 +2,26 @@ "刪除" "隱藏密碼" + "跳至底部" "僅限提及" "已關閉通知" + "第 %1$d 頁" "暫停" + "PIN 碼欄位" "播放" "投票" "投票已結束" + "使用 %1$s 回應" + "用其他表情符號回應" + "%1$s 和 %2$s 已讀" + "%1$s 已讀" + "點擊以顯示全部" "傳送檔案" "顯示密碼" + "開始通話" "使用者選單" + "錄製語音訊息。" + "停止錄音" "接受" "新增至時間軸" "返回" @@ -26,12 +37,14 @@ "建立" "建立聊天室" "拒絕" + "刪除投票" "停用" "完成" "編輯" "編輯投票" "啟用" "結束投票" + "輸入 PIN 碼" "忘記密碼?" "轉寄" "邀請" @@ -67,29 +80,41 @@ "傳送訊息" "分享" "分享連結" + "再登入一次" + "登出" + "直接登出" "略過" "開始" "開始聊天" "開始驗證" "點擊以載入地圖" "拍照" + "再試一次" "檢視原始碼" "是" + "載入更多" "關於" "可接受使用政策" "進階設定" "分析" + "外觀" "音訊" + "泡泡" + "聊天室備份" "著作權" "正在建立聊天室…" "離開聊天室" + "深色" "解密錯誤" "開發者選項" + "私訊" "(已編輯)" "編輯中" "* %1$s %2$s" "已啟用加密" + "輸入您的 PIN 碼" "錯誤" + "所有人" "檔案" "檔案已儲存至 Downloads" "訊息轉寄" @@ -99,10 +124,11 @@ "安裝 APK" "找不到此 Matrix ID,因此可能沒有人會收到邀請。" "正在離開聊天室" + "淺色" "連結已複製到剪貼簿" "載入中…" "訊息" - "訊息布局" + "訊息佈局" "訊息已移除" "現代" "關閉通知" @@ -117,29 +143,38 @@ "隱私權政策" "回應" "回應" + "復原金鑰" "重新整理中…" "正在回覆%1$s" "回報程式錯誤" + "回報問題" "格式化文字編輯器" + "聊天室" "聊天室名稱" "範例:您的計畫名稱" "螢幕鎖定" "搜尋結果" "安全性" + "已讀" "傳送中…" + "傳送失敗" + "已傳送" "伺服器不支援" "伺服器 URL" "設定" + "正在登出" "貼圖" "成功" "建議" "同步中" + "系統" "文字" "討論串" "主題" "無法解密" "無法發送邀請給一或多個使用者。" "無法發送邀請" + "解鎖" "開啟通知" "不支援的事件" "使用者名稱" @@ -148,19 +183,28 @@ "影片" "語音訊息" "等待中…" + "等待此則訊息" "您確定要結束這項投票嗎?" "投票:%1$s" + "驗證裝置" "確認" "警告" "無法建立永久連結" "%1$s無法載入地圖。請稍後再試。" "無法載入訊息" "%1$s無法取得您的位置。請稍後再試。" + "無法上傳語音訊息。" "%1$s 沒有權限存取您的位置。您可以到設定中開啟權限。" "%1$s 沒有權限存取您的位置。請在下方開啟權限。" "有些訊息尚未傳送" "嘿,來 %1$s 和我聊天:%2$s" "%1$s Android" + + "已輸入 %1$d 個位數" + + + "%1$s 與其他 %2$d 個人已讀" + "%1$d 位成員" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 10d3bbd12e..26874d4a19 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -50,13 +50,14 @@ "Forgot password?" "Forward" "Invite" - "Invite friends" - "Invite friends to %1$s" + "Invite people" + "Invite people to %1$s" "Invite people to %1$s" "Invites" "Join" "Learn more" "Leave" + "Leave conversation" "Leave room" "Manage account" "Manage devices" diff --git a/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserListDataSource.kt b/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserListDataSource.kt index b204af447a..569717f511 100644 --- a/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserListDataSource.kt +++ b/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserListDataSource.kt @@ -20,7 +20,7 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser interface UserListDataSource { - //TODO should probably have a flow + // TODO should probably have a flow suspend fun search(query: String, count: Long): List suspend fun getProfile(userId: UserId): MatrixUser? } diff --git a/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserRepository.kt b/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserRepository.kt index 03e9952c92..76a466533f 100644 --- a/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserRepository.kt +++ b/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserRepository.kt @@ -19,6 +19,5 @@ package io.element.android.libraries.usersearch.api import kotlinx.coroutines.flow.Flow interface UserRepository { - - suspend fun search(query: String): Flow> + fun search(query: String): Flow } diff --git a/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserSearchResult.kt b/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserSearchResult.kt index e67a7af46f..2410b58d05 100644 --- a/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserSearchResult.kt +++ b/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserSearchResult.kt @@ -22,3 +22,8 @@ data class UserSearchResult( val matrixUser: MatrixUser, val isUnresolved: Boolean = false, ) + +data class UserSearchResultState( + val results: List, + val isSearching: Boolean, +) diff --git a/libraries/usersearch/impl/src/main/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepository.kt b/libraries/usersearch/impl/src/main/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepository.kt index 0714528836..5d7bca2fbb 100644 --- a/libraries/usersearch/impl/src/main/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepository.kt +++ b/libraries/usersearch/impl/src/main/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepository.kt @@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.usersearch.api.UserListDataSource import io.element.android.libraries.usersearch.api.UserRepository import io.element.android.libraries.usersearch.api.UserSearchResult +import io.element.android.libraries.usersearch.api.UserSearchResultState import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -35,37 +36,46 @@ class MatrixUserRepository @Inject constructor( private val client: MatrixClient, private val dataSource: UserListDataSource ) : UserRepository { - - override suspend fun search(query: String): Flow> = flow { - // If the search term is a MXID that's not ours, we'll show a 'fake' result for that user, then update it when we get search results. + override fun search(query: String): Flow = flow { val shouldQueryProfile = MatrixPatterns.isUserId(query) && !client.isMe(UserId(query)) - if (shouldQueryProfile) { - emit(listOf(UserSearchResult(MatrixUser(UserId(query))))) + val shouldFetchSearchResults = query.length >= MINIMUM_SEARCH_LENGTH + // If the search term is a MXID that's not ours, we'll show a 'fake' result for that user, then update it when we get search results. + val fakeSearchResult = if (shouldQueryProfile) { + UserSearchResult(MatrixUser(UserId(query))) + } else { + null } - - if (query.length >= MINIMUM_SEARCH_LENGTH) { - // Debounce - delay(DEBOUNCE_TIME_MILLIS) - - val results = dataSource - .search(query, MAXIMUM_SEARCH_RESULTS) - .filter { !client.isMe(it.userId) } - .map { UserSearchResult(it) } - .toMutableList() - - // If the query is another user's MXID and the result doesn't contain that user ID, query the profile information explicitly - if (shouldQueryProfile && results.none { it.matrixUser.userId.value == query }) { - results.add( - 0, - dataSource.getProfile(UserId(query)) - ?.let { UserSearchResult(it) } - ?: UserSearchResult(MatrixUser(UserId(query)), isUnresolved = true)) - } - + if (shouldQueryProfile || shouldFetchSearchResults) { + emit(UserSearchResultState(isSearching = shouldFetchSearchResults, results = listOfNotNull(fakeSearchResult))) + } + if (shouldFetchSearchResults) { + val results = fetchSearchResults(query, shouldQueryProfile) emit(results) } } + private suspend fun fetchSearchResults(query: String, shouldQueryProfile: Boolean): UserSearchResultState { + // Debounce + delay(DEBOUNCE_TIME_MILLIS) + val results = dataSource + .search(query, MAXIMUM_SEARCH_RESULTS) + .filter { !client.isMe(it.userId) } + .map { UserSearchResult(it) } + .toMutableList() + + // If the query is another user's MXID and the result doesn't contain that user ID, query the profile information explicitly + if (shouldQueryProfile && results.none { it.matrixUser.userId.value == query }) { + results.add( + 0, + dataSource.getProfile(UserId(query)) + ?.let { UserSearchResult(it) } + ?: UserSearchResult(MatrixUser(UserId(query)), isUnresolved = true) + ) + } + + return UserSearchResultState(results = results, isSearching = false) + } + companion object { private const val DEBOUNCE_TIME_MILLIS = 250L private const val MINIMUM_SEARCH_LENGTH = 3 diff --git a/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt b/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt index 6ec9f28658..54bf3599ec 100644 --- a/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt +++ b/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt @@ -30,7 +30,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test internal class MatrixUserListDataSourceTest { - @Test fun `search - returns users on success`() = runTest { val matrixClient = FakeMatrixClient() diff --git a/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepositoryTest.kt b/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepositoryTest.kt index 621274bef5..92fa186631 100644 --- a/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepositoryTest.kt +++ b/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepositoryTest.kt @@ -33,7 +33,6 @@ import org.junit.Test private val SESSION_ID = SessionId("@current-user:example.com") internal class MatrixUserRepositoryTest { - @Test fun `search - emits nothing if the search query is too short`() = runTest { val dataSource = FakeUserListDataSource() @@ -54,7 +53,14 @@ internal class MatrixUserRepositoryTest { val result = repository.search("some query") result.test { - assertThat(awaitItem()).isEmpty() + awaitItem().also { + assertThat(it.isSearching).isTrue() + assertThat(it.results).isEmpty() + } + awaitItem().also { + assertThat(it.isSearching).isFalse() + assertThat(it.results).isEmpty() + } awaitComplete() } } @@ -68,7 +74,14 @@ internal class MatrixUserRepositoryTest { val result = repository.search("some query") result.test { - assertThat(awaitItem()).isEqualTo(aMatrixUserList().toUserSearchResults()) + awaitItem().also { + assertThat(it.isSearching).isTrue() + assertThat(it.results).isEmpty() + } + awaitItem().also { + assertThat(it.isSearching).isFalse() + assertThat(it.results).isEqualTo(aMatrixUserList().toUserSearchResults()) + } awaitComplete() } } @@ -81,9 +94,11 @@ internal class MatrixUserRepositoryTest { val result = repository.search(A_USER_ID.value) result.test { - assertThat(awaitItem()).isEqualTo(listOf(placeholderResult())) - skipItems(1) - awaitComplete() + awaitItem().also { + assertThat(it.isSearching).isTrue() + assertThat(it.results).isEqualTo(listOf(placeholderResult())) + } + cancelAndConsumeRemainingEvents() } } @@ -95,8 +110,11 @@ internal class MatrixUserRepositoryTest { val result = repository.search(SESSION_ID.value) result.test { - assertThat(awaitItem()).isEmpty() - awaitComplete() + awaitItem().also { + assertThat(it.isSearching).isTrue() + assertThat(it.results).isEmpty() + } + cancelAndConsumeRemainingEvents() } } @@ -110,7 +128,8 @@ internal class MatrixUserRepositoryTest { val result = repository.search("some text") result.test { - assertThat(awaitItem()).isEqualTo(aMatrixUserList().toUserSearchResults()) + skipItems(1) + assertThat(awaitItem().results).isEqualTo(aMatrixUserList().toUserSearchResults()) awaitComplete() } } @@ -126,7 +145,7 @@ internal class MatrixUserRepositoryTest { result.test { skipItems(1) - assertThat(awaitItem()).isEqualTo(searchResults.toUserSearchResults()) + assertThat(awaitItem().results).isEqualTo(searchResults.toUserSearchResults()) awaitComplete() } } @@ -145,7 +164,7 @@ internal class MatrixUserRepositoryTest { result.test { skipItems(1) - assertThat(awaitItem()).isEqualTo((listOf(userProfile) + searchResults).toUserSearchResults()) + assertThat(awaitItem().results).isEqualTo((listOf(userProfile) + searchResults).toUserSearchResults()) awaitComplete() } } @@ -163,7 +182,8 @@ internal class MatrixUserRepositoryTest { val result = repository.search(SESSION_ID.value) result.test { - assertThat(awaitItem()).isEqualTo(searchResults.toUserSearchResults()) + skipItems(1) + assertThat(awaitItem().results).isEqualTo(searchResults.toUserSearchResults()) awaitComplete() } } @@ -181,7 +201,7 @@ internal class MatrixUserRepositoryTest { result.test { skipItems(1) - assertThat(awaitItem()).isEqualTo(listOf(placeholderResult(isUnresolved = true)) + searchResults.toUserSearchResults()) + assertThat(awaitItem().results).isEqualTo(listOf(placeholderResult(isUnresolved = true)) + searchResults.toUserSearchResults()) awaitComplete() } } @@ -191,5 +211,4 @@ internal class MatrixUserRepositoryTest { private fun List.toUserSearchResults() = map { UserSearchResult(it) } private fun placeholderResult(id: UserId = A_USER_ID, isUnresolved: Boolean = false) = UserSearchResult(MatrixUser(id), isUnresolved = isUnresolved) - } diff --git a/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserListDataSource.kt b/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserListDataSource.kt index 23935c1a6c..deda766411 100644 --- a/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserListDataSource.kt +++ b/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserListDataSource.kt @@ -21,7 +21,6 @@ import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.usersearch.api.UserListDataSource class FakeUserListDataSource : UserListDataSource { - private var searchResult: List = emptyList() private var profile: MatrixUser? = null diff --git a/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserRepository.kt b/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserRepository.kt index 0911d86b36..c243d04b20 100644 --- a/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserRepository.kt +++ b/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserRepository.kt @@ -17,24 +17,22 @@ package io.element.android.libraries.usersearch.test import io.element.android.libraries.usersearch.api.UserRepository -import io.element.android.libraries.usersearch.api.UserSearchResult +import io.element.android.libraries.usersearch.api.UserSearchResultState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow class FakeUserRepository : UserRepository { - var providedQuery: String? = null private set - private val flow = MutableSharedFlow>() + private val flow = MutableSharedFlow() - override suspend fun search(query: String): Flow> { + override fun search(query: String): Flow { providedQuery = query return flow } - suspend fun emitResult(result: List) { - flow.emit(result) + suspend fun emitState(state: UserSearchResultState) { + flow.emit(state) } - } diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImpl.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImpl.kt index 5a1cfe537b..3c4d7dd15f 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImpl.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImpl.kt @@ -104,7 +104,7 @@ class VoiceRecorderImpl @Inject constructor( is Audio.Data -> { val audioLevel = audioLevelCalculator.calculateAudioLevel(audio.buffer) - lock.withLock{ + lock.withLock { levels.add(audioLevel) _state.emit(VoiceRecorderState.Recording(elapsedTime, levels.toList())) } @@ -136,7 +136,6 @@ class VoiceRecorderImpl @Inject constructor( audioReader = null encoder.release() - lock.withLock { if (cancelled) { deleteRecording() diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AndroidAudioReader.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AndroidAudioReader.kt index a2342f3c2f..e4ca61b4a4 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AndroidAudioReader.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AndroidAudioReader.kt @@ -29,7 +29,8 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext class AndroidAudioReader -@RequiresPermission(Manifest.permission.RECORD_AUDIO) private constructor( +@RequiresPermission(Manifest.permission.RECORD_AUDIO) +private constructor( private val config: AudioConfig, private val dispatchers: CoroutineDispatchers, ) : AudioReader { @@ -92,7 +93,7 @@ class AndroidAudioReader private fun createOutputBuffer(sampleRate: SampleRate): ShortArray { val bufferSizeInShorts = AudioRecord.getMinBufferSize( - sampleRate.hz, + sampleRate.HZ, config.format.channelMask, config.format.encoding ) diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioReader.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioReader.kt index 230c9533fd..4031a5ef45 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioReader.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioReader.kt @@ -33,5 +33,4 @@ interface AudioReader { interface Factory { fun create(config: AudioConfig, dispatchers: CoroutineDispatchers): AudioReader } - } diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DefaultEncoder.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DefaultEncoder.kt index a888824fe5..814ed2f891 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DefaultEncoder.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DefaultEncoder.kt @@ -33,7 +33,7 @@ class DefaultEncoder @Inject constructor( config: AudioConfig, ) : Encoder { private val bitRate = config.bitRate -private val sampleRate = config.sampleRate.asEncoderModel() + private val sampleRate = config.sampleRate.asEncoderModel() private var encoder: OggOpusEncoder? = null override fun init( diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Encoder.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Encoder.kt index 67685635aa..d6686b092f 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Encoder.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Encoder.kt @@ -19,7 +19,6 @@ package io.element.android.libraries.voicerecorder.impl.audio import java.io.File interface Encoder { - fun init(file: File) fun encode(buffer: ShortArray, readSize: Int) diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/SampleRate.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/SampleRate.kt index b392b6e19f..d510e88d09 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/SampleRate.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/SampleRate.kt @@ -19,6 +19,6 @@ package io.element.android.libraries.voicerecorder.impl.audio import io.element.android.opusencoder.configuration.SampleRate as LibOpusOggSampleRate data object SampleRate { - const val hz = 48_000 + const val HZ = 48_000 fun asEncoderModel() = LibOpusOggSampleRate.Rate48kHz } diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/di/VoiceRecorderModule.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/di/VoiceRecorderModule.kt index 29071a9879..d51ab3e138 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/di/VoiceRecorderModule.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/di/VoiceRecorderModule.kt @@ -37,10 +37,11 @@ object VoiceRecorderModule { return AudioConfig( format = AudioFormat.Builder() .setEncoding(AudioFormat.ENCODING_PCM_16BIT) - .setSampleRate(sampleRate.hz) + .setSampleRate(sampleRate.HZ) .setChannelMask(AudioFormat.CHANNEL_IN_MONO) .build(), - bitRate = 24_000, // 24 kbps + // 24 kbps + bitRate = 24_000, sampleRate = sampleRate, source = MediaRecorder.AudioSource.MIC, ) diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/DefaultVoiceFileManager.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/DefaultVoiceFileManager.kt index 07ef54991f..d8b30e4c53 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/DefaultVoiceFileManager.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/DefaultVoiceFileManager.kt @@ -32,7 +32,6 @@ class DefaultVoiceFileManager @Inject constructor( private val config: VoiceFileConfig, room: MatrixRoom, ) : VoiceFileManager { - private val roomId: RoomId = room.roomId override fun createFile(): File { diff --git a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImplTest.kt b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImplTest.kt index 030545a3bc..9bb1ace7b1 100644 --- a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImplTest.kt +++ b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/VoiceRecorderImplTest.kt @@ -141,8 +141,9 @@ class VoiceRecorderImplTest { ), encoder = FakeEncoder(fakeFileSystem), config = AudioConfig( - format = AUDIO_FORMAT, - bitRate = 24_000, // 24 kbps + format = audioFormat, + // 24 kbps + bitRate = 24_000, sampleRate = SampleRate, source = MediaRecorder.AudioSource.MIC, ), @@ -155,8 +156,8 @@ class VoiceRecorderImplTest { companion object { const val FILE_ID: String = "recording" - const val FILE_PATH = "voice_recordings/${FILE_ID}.ogg" - private lateinit var AUDIO_FORMAT: AudioFormat + const val FILE_PATH = "voice_recordings/$FILE_ID.ogg" + private lateinit var audioFormat: AudioFormat // FakeEncoder doesn't actually encode, it just writes the data to the file private const val ENCODED_DATA = "[32767, 32767, 32767][32767, 32767, 32767]" @@ -170,7 +171,7 @@ class VoiceRecorderImplTest { @BeforeClass @JvmStatic fun initAudioFormat() { - AUDIO_FORMAT = mockk() + audioFormat = mockk() } } } diff --git a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DBovAudioLevelCalculatorTest.kt b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DBovAudioLevelCalculatorTest.kt index 9aa3432ef6..2ea462f162 100644 --- a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DBovAudioLevelCalculatorTest.kt +++ b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DBovAudioLevelCalculatorTest.kt @@ -20,7 +20,6 @@ import com.google.common.truth.Truth.assertThat import org.junit.Test class DBovAudioLevelCalculatorTest { - @Test fun `given max values, it returns 1`() { val calculator = DBovAudioLevelCalculator() diff --git a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioRecorderFactory.kt b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioRecorderFactory.kt index 01943f4d10..02d8b4742c 100644 --- a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioRecorderFactory.kt +++ b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioRecorderFactory.kt @@ -23,7 +23,7 @@ import io.element.android.libraries.voicerecorder.impl.audio.AudioReader class FakeAudioRecorderFactory( private val audio: List