Merge branch 'release/0.4.1' into main
This commit is contained in:
commit
8bc39ce1c4
1786 changed files with 7343 additions and 4262 deletions
|
|
@ -14,10 +14,19 @@ ij_smart_tabs = false
|
||||||
ij_visual_guides = none
|
ij_visual_guides = none
|
||||||
ij_wrap_on_typing = false
|
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_wrapping = disabled
|
||||||
ktlint_standard_trailing-comma-on-call-site = 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]
|
[*.java]
|
||||||
ij_java_align_consecutive_assignments = false
|
ij_java_align_consecutive_assignments = false
|
||||||
|
|
|
||||||
6
.github/renovate.json
vendored
6
.github/renovate.json
vendored
|
|
@ -18,12 +18,6 @@
|
||||||
],
|
],
|
||||||
"groupName" : "kotlin"
|
"groupName" : "kotlin"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"matchPackageNames" : [
|
|
||||||
"org.jetbrains.kotlinx.kover"
|
|
||||||
],
|
|
||||||
"enabled" : false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"matchPackagePatterns" : [
|
"matchPackagePatterns" : [
|
||||||
"^org.maplibre"
|
"^org.maplibre"
|
||||||
|
|
|
||||||
31
.github/workflows/fork-pr-notice.yml
vendored
Normal file
31
.github/workflows/fork-pr-notice.yml
vendored
Normal file
|
|
@ -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).`
|
||||||
|
})
|
||||||
6
.github/workflows/maestro.yml
vendored
6
.github/workflows/maestro.yml
vendored
|
|
@ -24,27 +24,31 @@ jobs:
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
steps:
|
steps:
|
||||||
- name: Remove Run-Maestro label
|
- 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
|
uses: actions-ecosystem/action-remove-labels@v1
|
||||||
with:
|
with:
|
||||||
labels: Run-Maestro
|
labels: Run-Maestro
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||||
with:
|
with:
|
||||||
# Ensure we are building the branch and not the branch after being merged on develop
|
# Ensure we are building the branch and not the branch after being merged on develop
|
||||||
# https://github.com/actions/checkout/issues/881
|
# https://github.com/actions/checkout/issues/881
|
||||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||||
- uses: actions/setup-java@v4
|
- uses: actions/setup-java@v4
|
||||||
name: Use JDK 17
|
name: Use JDK 17
|
||||||
|
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Assemble debug APK
|
- name: Assemble debug APK
|
||||||
run: ./gradlew :app:assembleDebug $CI_GRADLE_ARG_PROPERTIES
|
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:
|
env:
|
||||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||||
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
|
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
|
||||||
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
|
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
|
||||||
- uses: mobile-dev-inc/action-maestro-cloud@v1.8.0
|
- 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:
|
with:
|
||||||
api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }}
|
api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }}
|
||||||
# Doc says (https://github.com/mobile-dev-inc/action-maestro-cloud#android):
|
# Doc says (https://github.com/mobile-dev-inc/action-maestro-cloud#android):
|
||||||
|
|
|
||||||
4
.github/workflows/nightlyReports.yml
vendored
4
.github/workflows/nightlyReports.yml
vendored
|
|
@ -33,7 +33,7 @@ jobs:
|
||||||
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
- name: 📈 Generate kover report and verify coverage
|
- 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
|
- name: ✅ Upload kover report
|
||||||
if: always()
|
if: always()
|
||||||
|
|
@ -41,7 +41,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: kover-results
|
name: kover-results
|
||||||
path: |
|
path: |
|
||||||
**/build/reports/kover/merged
|
**/build/reports/kover
|
||||||
|
|
||||||
- name: 🔊 Publish results to Sonar
|
- name: 🔊 Publish results to Sonar
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
6
.github/workflows/recordScreenshots.yml
vendored
6
.github/workflows/recordScreenshots.yml
vendored
|
|
@ -26,7 +26,7 @@ jobs:
|
||||||
uses: nschloe/action-cached-lfs-checkout@v1.2.2
|
uses: nschloe/action-cached-lfs-checkout@v1.2.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
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)
|
- name: ⏬ Checkout with LFS (Branch)
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
uses: nschloe/action-cached-lfs-checkout@v1.2.2
|
uses: nschloe/action-cached-lfs-checkout@v1.2.2
|
||||||
|
|
@ -43,8 +43,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Record screenshots
|
- name: Record screenshots
|
||||||
|
id: record
|
||||||
run: ./.github/workflows/scripts/recordScreenshots.sh
|
run: ./.github/workflows/scripts/recordScreenshots.sh
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
GITHUB_REPOSITORY: ${{ secrets.GITHUB_REPOSITORY }}
|
GITHUB_REPOSITORY: ${{ secrets.GITHUB_REPOSITORY }}
|
||||||
|
|
||||||
|
|
|
||||||
22
.github/workflows/scripts/recordScreenshots.sh
vendored
22
.github/workflows/scripts/recordScreenshots.sh
vendored
|
|
@ -68,11 +68,27 @@ echo "Record screenshots"
|
||||||
./gradlew recordPaparazziDebug --stacktrace -PpreDexEnable=false --max-workers 4 --warn
|
./gradlew recordPaparazziDebug --stacktrace -PpreDexEnable=false --max-workers 4 --warn
|
||||||
|
|
||||||
echo "Committing changes"
|
echo "Committing changes"
|
||||||
git config user.name "ElementBot"
|
git config http.sslVerify false
|
||||||
git config user.email "benoitm+elementbot@element.io"
|
|
||||||
|
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 add -A
|
||||||
git commit -m "Update screenshots"
|
git commit -m "Update screenshots"
|
||||||
|
|
||||||
|
GITHUB_REPO="https://$GITHUB_ACTOR:$TOKEN@github.com/$REPO.git"
|
||||||
echo "Pushing changes"
|
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!"
|
echo "Done!"
|
||||||
|
|
|
||||||
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
|
|
@ -55,7 +55,7 @@ jobs:
|
||||||
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
- name: 📈Generate kover report and verify coverage
|
- 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
|
- name: 🚫 Upload kover failed coverage reports
|
||||||
if: failure()
|
if: failure()
|
||||||
|
|
@ -63,7 +63,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: kover-error-report
|
name: kover-error-report
|
||||||
path: |
|
path: |
|
||||||
**/kover/merged/verification/errors.txt
|
app/build/reports/kover/verify.err
|
||||||
|
|
||||||
- name: ✅ Upload kover report (disabled)
|
- name: ✅ Upload kover report (disabled)
|
||||||
if: always()
|
if: always()
|
||||||
|
|
@ -83,4 +83,4 @@ jobs:
|
||||||
if: always()
|
if: always()
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
# with:
|
# with:
|
||||||
# files: build/reports/kover/merged/xml/report.xml
|
# files: build/reports/kover/xml/report.xml
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,10 @@ appId: ${APP_ID}
|
||||||
- takeScreenshot: build/maestro/900-SignOutScreen
|
- takeScreenshot: build/maestro/900-SignOutScreen
|
||||||
- back
|
- back
|
||||||
- tapOn: "Sign out"
|
- tapOn: "Sign out"
|
||||||
- tapOn:
|
|
||||||
id: "sign-out-submit"
|
|
||||||
# Ensure cancel cancels
|
# Ensure cancel cancels
|
||||||
- tapOn: "Cancel"
|
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "sign-out-submit"
|
id: "dialog-negative"
|
||||||
|
- tapOn: "Sign out"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "dialog-positive"
|
id: "dialog-positive"
|
||||||
- runFlow: ../assertions/assertInitDisplayed.yaml
|
- runFlow: ../assertions/assertInitDisplayed.yaml
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,5 @@ appId: ${APP_ID}
|
||||||
index: 1
|
index: 1
|
||||||
- takeScreenshot: build/maestro/330-createAndDeleteDM
|
- takeScreenshot: build/maestro/330-createAndDeleteDM
|
||||||
- tapOn: "maestroelement2"
|
- tapOn: "maestroelement2"
|
||||||
- tapOn: "Leave room"
|
- tapOn: "Leave conversation"
|
||||||
- tapOn: "Leave"
|
- tapOn: "Leave"
|
||||||
|
|
|
||||||
36
CHANGES.md
36
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)
|
Changes in Element X v0.4.0 (2023-12-22)
|
||||||
========================================
|
========================================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ import java.io.File
|
||||||
*/
|
*/
|
||||||
@AutoService(CodeGenerator::class)
|
@AutoService(CodeGenerator::class)
|
||||||
class ContributesNodeCodeGenerator : CodeGenerator {
|
class ContributesNodeCodeGenerator : CodeGenerator {
|
||||||
|
|
||||||
override fun isApplicable(context: AnvilContext): Boolean = true
|
override fun isApplicable(context: AnvilContext): Boolean = true
|
||||||
|
|
||||||
override fun generateCode(codeGenDir: File, module: ModuleDescriptor, projectFiles: Collection<KtFile>): Collection<GeneratedFile> {
|
override fun generateCode(codeGenDir: File, module: ModuleDescriptor, projectFiles: Collection<KtFile>): Collection<GeneratedFile> {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import com.android.build.api.variant.FilterConfiguration.FilterType.ABI
|
||||||
import extension.allFeaturesImpl
|
import extension.allFeaturesImpl
|
||||||
import extension.allLibrariesImpl
|
import extension.allLibrariesImpl
|
||||||
import extension.allServicesImpl
|
import extension.allServicesImpl
|
||||||
|
import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-application")
|
id("io.element.android-compose-application")
|
||||||
|
|
@ -27,7 +28,8 @@ plugins {
|
||||||
alias(libs.plugins.anvil)
|
alias(libs.plugins.anvil)
|
||||||
alias(libs.plugins.ksp)
|
alias(libs.plugins.ksp)
|
||||||
alias(libs.plugins.kapt)
|
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)
|
alias(libs.plugins.knit)
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
// To be able to update the firebase.xml files, uncomment and build the project
|
// To be able to update the firebase.xml files, uncomment and build the project
|
||||||
|
|
@ -43,10 +45,6 @@ android {
|
||||||
versionCode = Versions.versionCode
|
versionCode = Versions.versionCode
|
||||||
versionName = Versions.versionName
|
versionName = Versions.versionName
|
||||||
|
|
||||||
vectorDrawables {
|
|
||||||
useSupportLibrary = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep abiFilter for the universalApk
|
// Keep abiFilter for the universalApk
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters += listOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64")
|
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 {
|
dependencies {
|
||||||
allLibrariesImpl()
|
allLibrariesImpl()
|
||||||
allServicesImpl()
|
allServicesImpl()
|
||||||
|
|
|
||||||
8
app/proguard-rules.pro
vendored
8
app/proguard-rules.pro
vendored
|
|
@ -10,6 +10,9 @@
|
||||||
-keep class com.sun.jna.** { *; }
|
-keep class com.sun.jna.** { *; }
|
||||||
-keep class * implements com.sun.jna.** { *; }
|
-keep class * implements com.sun.jna.** { *; }
|
||||||
|
|
||||||
|
# TagSoup, coming from the RTE library
|
||||||
|
-keep class org.ccil.cowan.tagsoup.** { *; }
|
||||||
|
|
||||||
# kotlinx.serialization
|
# kotlinx.serialization
|
||||||
|
|
||||||
# Kotlin serialization looks up the generated serializer classes through a function on companion
|
# Kotlin serialization looks up the generated serializer classes through a function on companion
|
||||||
|
|
@ -32,3 +35,8 @@
|
||||||
-dontwarn org.conscrypt.**
|
-dontwarn org.conscrypt.**
|
||||||
-dontwarn org.bouncycastle.**
|
-dontwarn org.bouncycastle.**
|
||||||
-dontwarn org.openjsse.**
|
-dontwarn org.openjsse.**
|
||||||
|
|
||||||
|
# Needed for Posthog
|
||||||
|
-keepclassmembers class android.view.JavaViewSpy {
|
||||||
|
static int windowAttachCount(android.view.View);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import io.element.android.x.initializer.CrashInitializer
|
||||||
import io.element.android.x.initializer.TracingInitializer
|
import io.element.android.x.initializer.TracingInitializer
|
||||||
|
|
||||||
class ElementXApplication : Application(), DaggerComponentOwner {
|
class ElementXApplication : Application(), DaggerComponentOwner {
|
||||||
|
|
||||||
override val daggerComponent: AppComponent = DaggerAppComponent.factory().create(this)
|
override val daggerComponent: AppComponent = DaggerAppComponent.factory().create(this)
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@ import timber.log.Timber
|
||||||
private val loggerTag = LoggerTag("MainActivity")
|
private val loggerTag = LoggerTag("MainActivity")
|
||||||
|
|
||||||
class MainActivity : NodeActivity() {
|
class MainActivity : NodeActivity() {
|
||||||
|
|
||||||
private lateinit var mainNode: MainNode
|
private lateinit var mainNode: MainNode
|
||||||
private lateinit var appBindings: AppBindings
|
private lateinit var appBindings: AppBindings
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ class MainNode(
|
||||||
plugins = plugins,
|
plugins = plugins,
|
||||||
),
|
),
|
||||||
DaggerComponentOwner {
|
DaggerComponentOwner {
|
||||||
|
|
||||||
override val daggerComponent = (context as DaggerComponentOwner).daggerComponent
|
override val daggerComponent = (context as DaggerComponentOwner).daggerComponent
|
||||||
|
|
||||||
override fun resolve(navTarget: RootNavTarget, buildContext: BuildContext): Node {
|
override fun resolve(navTarget: RootNavTarget, buildContext: BuildContext): Node {
|
||||||
|
|
@ -68,5 +67,4 @@ class MainNode(
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
object RootNavTarget : Parcelable
|
object RootNavTarget : Parcelable
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,12 @@ import io.element.android.libraries.matrix.api.tracing.TracingService
|
||||||
@ContributesTo(AppScope::class)
|
@ContributesTo(AppScope::class)
|
||||||
interface AppBindings {
|
interface AppBindings {
|
||||||
fun snackbarDispatcher(): SnackbarDispatcher
|
fun snackbarDispatcher(): SnackbarDispatcher
|
||||||
|
|
||||||
fun tracingService(): TracingService
|
fun tracingService(): TracingService
|
||||||
|
|
||||||
fun bugReporter(): BugReporter
|
fun bugReporter(): BugReporter
|
||||||
|
|
||||||
fun lockScreenService(): LockScreenService
|
fun lockScreenService(): LockScreenService
|
||||||
|
|
||||||
fun preferencesStore(): PreferencesStore
|
fun preferencesStore(): PreferencesStore
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,11 @@ import io.element.android.libraries.di.SingleIn
|
||||||
@SingleIn(AppScope::class)
|
@SingleIn(AppScope::class)
|
||||||
@MergeComponent(AppScope::class)
|
@MergeComponent(AppScope::class)
|
||||||
interface AppComponent : NodeFactoriesBindings {
|
interface AppComponent : NodeFactoriesBindings {
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(@ApplicationContext @BindsInstance context: Context): AppComponent
|
fun create(
|
||||||
|
@ApplicationContext @BindsInstance
|
||||||
|
context: Context
|
||||||
|
): AppComponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,6 @@ import java.io.File
|
||||||
@Module
|
@Module
|
||||||
@ContributesTo(AppScope::class)
|
@ContributesTo(AppScope::class)
|
||||||
object AppModule {
|
object AppModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun providesBaseDirectory(@ApplicationContext context: Context): File {
|
fun providesBaseDirectory(@ApplicationContext context: Context): File {
|
||||||
return File(context.filesDir, "sessions")
|
return File(context.filesDir, "sessions")
|
||||||
|
|
@ -82,14 +81,20 @@ object AppModule {
|
||||||
buildType = buildType,
|
buildType = buildType,
|
||||||
applicationName = context.getString(R.string.app_name),
|
applicationName = context.getString(R.string.app_name),
|
||||||
applicationId = BuildConfig.APPLICATION_ID,
|
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,
|
versionName = BuildConfig.VERSION_NAME,
|
||||||
versionCode = BuildConfig.VERSION_CODE,
|
versionCode = BuildConfig.VERSION_CODE,
|
||||||
gitRevision = "TODO", // BuildConfig.GIT_REVISION,
|
// BuildConfig.GIT_REVISION,
|
||||||
gitRevisionDate = "TODO", // BuildConfig.GIT_REVISION_DATE,
|
gitRevision = "TODO",
|
||||||
gitBranchName = "TODO", // BuildConfig.GIT_BRANCH_NAME,
|
// BuildConfig.GIT_REVISION_DATE,
|
||||||
flavorDescription = "TODO", // BuildConfig.FLAVOR_DESCRIPTION,
|
gitRevisionDate = "TODO",
|
||||||
flavorShortDescription = "TODO", // BuildConfig.SHORT_FLAVOR_DESCRIPTION,
|
// BuildConfig.GIT_BRANCH_NAME,
|
||||||
|
gitBranchName = "TODO",
|
||||||
|
// BuildConfig.FLAVOR_DESCRIPTION,
|
||||||
|
flavorDescription = "TODO",
|
||||||
|
// BuildConfig.SHORT_FLAVOR_DESCRIPTION,
|
||||||
|
flavorShortDescription = "TODO",
|
||||||
)
|
)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import javax.inject.Inject
|
||||||
class DefaultRoomComponentFactory @Inject constructor(
|
class DefaultRoomComponentFactory @Inject constructor(
|
||||||
private val roomComponentBuilder: RoomComponent.Builder
|
private val roomComponentBuilder: RoomComponent.Builder
|
||||||
) : RoomComponentFactory {
|
) : RoomComponentFactory {
|
||||||
|
|
||||||
override fun create(room: MatrixRoom): Any {
|
override fun create(room: MatrixRoom): Any {
|
||||||
return roomComponentBuilder.room(room).build()
|
return roomComponentBuilder.room(room).build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import javax.inject.Inject
|
||||||
class DefaultSessionComponentFactory @Inject constructor(
|
class DefaultSessionComponentFactory @Inject constructor(
|
||||||
private val sessionComponentBuilder: SessionComponent.Builder
|
private val sessionComponentBuilder: SessionComponent.Builder
|
||||||
) : SessionComponentFactory {
|
) : SessionComponentFactory {
|
||||||
|
|
||||||
override fun create(client: MatrixClient): Any {
|
override fun create(client: MatrixClient): Any {
|
||||||
return sessionComponentBuilder.client(client).build()
|
return sessionComponentBuilder.client(client).build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
@SingleIn(RoomScope::class)
|
@SingleIn(RoomScope::class)
|
||||||
@MergeSubcomponent(RoomScope::class)
|
@MergeSubcomponent(RoomScope::class)
|
||||||
interface RoomComponent : NodeFactoriesBindings {
|
interface RoomComponent : NodeFactoriesBindings {
|
||||||
|
|
||||||
@Subcomponent.Builder
|
@Subcomponent.Builder
|
||||||
interface Builder {
|
interface Builder {
|
||||||
@BindsInstance
|
@BindsInstance
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
@SingleIn(SessionScope::class)
|
@SingleIn(SessionScope::class)
|
||||||
@MergeSubcomponent(SessionScope::class)
|
@MergeSubcomponent(SessionScope::class)
|
||||||
interface SessionComponent : NodeFactoriesBindings {
|
interface SessionComponent : NodeFactoriesBindings {
|
||||||
|
|
||||||
@Subcomponent.Builder
|
@Subcomponent.Builder
|
||||||
interface Builder {
|
interface Builder {
|
||||||
@BindsInstance
|
@BindsInstance
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import androidx.startup.Initializer
|
||||||
import io.element.android.features.rageshake.impl.crash.VectorUncaughtExceptionHandler
|
import io.element.android.features.rageshake.impl.crash.VectorUncaughtExceptionHandler
|
||||||
|
|
||||||
class CrashInitializer : Initializer<Unit> {
|
class CrashInitializer : Initializer<Unit> {
|
||||||
|
|
||||||
override fun create(context: Context) {
|
override fun create(context: Context) {
|
||||||
VectorUncaughtExceptionHandler(context).activate()
|
VectorUncaughtExceptionHandler(context).activate()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ import io.element.android.x.di.AppBindings
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class TracingInitializer : Initializer<Unit> {
|
class TracingInitializer : Initializer<Unit> {
|
||||||
|
|
||||||
override fun create(context: Context) {
|
override fun create(context: Context) {
|
||||||
val appBindings = context.bindings<AppBindings>()
|
val appBindings = context.bindings<AppBindings>()
|
||||||
val tracingService = appBindings.tracingService()
|
val tracingService = appBindings.tracingService()
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ import org.robolectric.RuntimeEnvironment
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner::class)
|
@RunWith(RobolectricTestRunner::class)
|
||||||
class IntentProviderImplTest {
|
class IntentProviderImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test getViewRoomIntent with Session`() {
|
fun `test getViewRoomIntent with Session`() {
|
||||||
val sut = createIntentProviderImpl()
|
val sut = createIntentProviderImpl()
|
||||||
|
|
|
||||||
|
|
@ -25,48 +25,27 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration for the lock screen feature.
|
* 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(
|
data class LockScreenConfig(
|
||||||
/**
|
|
||||||
* Whether the PIN is mandatory or not.
|
|
||||||
*/
|
|
||||||
val isPinMandatory: Boolean,
|
val isPinMandatory: Boolean,
|
||||||
|
|
||||||
/**
|
|
||||||
* Some PINs are forbidden.
|
|
||||||
*/
|
|
||||||
val pinBlacklist: Set<String>,
|
val pinBlacklist: Set<String>,
|
||||||
|
|
||||||
/**
|
|
||||||
* The size of the PIN.
|
|
||||||
*/
|
|
||||||
val pinSize: Int,
|
val pinSize: Int,
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of attempts before the user is logged out.
|
|
||||||
*/
|
|
||||||
val maxPinCodeAttemptsBeforeLogout: Int,
|
val maxPinCodeAttemptsBeforeLogout: Int,
|
||||||
|
|
||||||
/**
|
|
||||||
* Time period before locking the app once backgrounded.
|
|
||||||
*/
|
|
||||||
val gracePeriod: Duration,
|
val gracePeriod: Duration,
|
||||||
|
|
||||||
/**
|
|
||||||
* Authentication with strong methods (fingerprint, some face/iris unlock implementations) is supported.
|
|
||||||
*/
|
|
||||||
val isStrongBiometricsEnabled: Boolean,
|
val isStrongBiometricsEnabled: Boolean,
|
||||||
|
|
||||||
/**
|
|
||||||
* Authentication with weak methods (most face/iris unlock implementations) is supported.
|
|
||||||
*/
|
|
||||||
val isWeakBiometricsEnabled: Boolean,
|
val isWeakBiometricsEnabled: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ContributesTo(AppScope::class)
|
@ContributesTo(AppScope::class)
|
||||||
@Module
|
@Module
|
||||||
object LockScreenConfigModule {
|
object LockScreenConfigModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun providesLockScreenConfig(): LockScreenConfig = LockScreenConfig(
|
fun providesLockScreenConfig(): LockScreenConfig = LockScreenConfig(
|
||||||
isPinMandatory = false,
|
isPinMandatory = false,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,6 @@
|
||||||
package io.element.android.appconfig
|
package io.element.android.appconfig
|
||||||
|
|
||||||
object MatrixConfiguration {
|
object MatrixConfiguration {
|
||||||
const val matrixToPermalinkBaseUrl: String = "https://matrix.to/#/"
|
const val MATRIX_TO_PERMALINK_BASE_URL: String = "https://matrix.to/#/"
|
||||||
val clientPermalinkBaseUrl: String? = null
|
val clientPermalinkBaseUrl: String? = null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ package io.element.android.appconfig
|
||||||
|
|
||||||
object NotificationConfig {
|
object NotificationConfig {
|
||||||
// TODO EAx Implement and set to true at some point
|
// 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
|
// TODO EAx Implement and set to true at some point
|
||||||
const val supportQuickReplyAction = false
|
const val SUPPORT_QUICK_REPLY_ACTION = false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,5 @@ object PushConfig {
|
||||||
/**
|
/**
|
||||||
* Note: pusher_app_id cannot exceed 64 chars.
|
* 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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@
|
||||||
package io.element.android.appconfig
|
package io.element.android.appconfig
|
||||||
|
|
||||||
object RoomListConfig {
|
object RoomListConfig {
|
||||||
const val showInviteMenuItem = false
|
const val SHOW_INVITE_MENU_ITEM = false
|
||||||
const val showReportProblemMenuItem = 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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,5 @@
|
||||||
package io.element.android.appconfig
|
package io.element.android.appconfig
|
||||||
|
|
||||||
object SecureBackupConfig {
|
object SecureBackupConfig {
|
||||||
const val LearnMoreUrl: String = "https://element.io/help#encryption5"
|
const val LEARN_MORE_URL: String = "https://element.io/help#encryption5"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,5 @@
|
||||||
package io.element.android.appconfig
|
package io.element.android.appconfig
|
||||||
|
|
||||||
object TimelineConfig {
|
object TimelineConfig {
|
||||||
const val maxReadReceiptToDisplay = 3
|
const val MAX_READ_RECEIPT_TO_DISPLAY = 3
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,4 +40,3 @@ fun <T : Any> BackStack<T>.removeLast(element: T) {
|
||||||
} ?: return
|
} ?: return
|
||||||
accept(Remove(lastExpectedNavElement.key))
|
accept(Remove(lastExpectedNavElement.key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ class LoggedInEventProcessor @Inject constructor(
|
||||||
roomMembershipObserver: RoomMembershipObserver,
|
roomMembershipObserver: RoomMembershipObserver,
|
||||||
sessionVerificationService: SessionVerificationService,
|
sessionVerificationService: SessionVerificationService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var observingJob: Job? = null
|
private var observingJob: Job? = null
|
||||||
|
|
||||||
private val displayLeftRoomMessage = roomMembershipObserver.updates
|
private val displayLeftRoomMessage = roomMembershipObserver.updates
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,6 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
plugins = plugins
|
plugins = plugins
|
||||||
) {
|
) {
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onOpenBugReport()
|
fun onOpenBugReport()
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +137,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
},
|
},
|
||||||
onStop = {
|
onStop = {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
//Counterpart startSync is done in observeSyncStateAndNetworkStatus method.
|
// Counterpart startSync is done in observeSyncStateAndNetworkStatus method.
|
||||||
syncService.stopSync()
|
syncService.stopSync()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,6 @@ class RootFlowNode @AssistedInject constructor(
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
plugins = plugins
|
plugins = plugins
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override fun onBuilt() {
|
override fun onBuilt() {
|
||||||
matrixClientsHolder.restoreWithSavedState(buildContext.savedStateMap)
|
matrixClientsHolder.restoreWithSavedState(buildContext.savedStateMap)
|
||||||
super.onBuilt()
|
super.onBuilt()
|
||||||
|
|
@ -268,10 +267,9 @@ class RootFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun attachSession(sessionId: SessionId): LoggedInAppScopeFlowNode {
|
private suspend fun attachSession(sessionId: SessionId): LoggedInAppScopeFlowNode {
|
||||||
//TODO handle multi-session
|
// TODO handle multi-session
|
||||||
return waitForChildAttached { navTarget ->
|
return waitForChildAttached { navTarget ->
|
||||||
navTarget is NavTarget.LoggedInFlow && navTarget.sessionId == sessionId
|
navTarget is NavTarget.LoggedInFlow && navTarget.sessionId == sessionId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ private const val SAVE_INSTANCE_KEY = "io.element.android.x.di.MatrixClientsHold
|
||||||
@SingleIn(AppScope::class)
|
@SingleIn(AppScope::class)
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class MatrixClientsHolder @Inject constructor(private val authenticationService: MatrixAuthenticationService) : MatrixClientProvider {
|
class MatrixClientsHolder @Inject constructor(private val authenticationService: MatrixAuthenticationService) : MatrixClientProvider {
|
||||||
|
|
||||||
private val sessionIdsToMatrixClient = ConcurrentHashMap<SessionId, MatrixClient>()
|
private val sessionIdsToMatrixClient = ConcurrentHashMap<SessionId, MatrixClient>()
|
||||||
private val restoreMutex = Mutex()
|
private val restoreMutex = Mutex()
|
||||||
|
|
||||||
|
|
@ -65,8 +64,9 @@ class MatrixClientsHolder @Inject constructor(private val authenticationService:
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun restoreWithSavedState(state: SavedStateMap?) {
|
fun restoreWithSavedState(state: SavedStateMap?) {
|
||||||
Timber.d("Restore state")
|
Timber.d("Restore state")
|
||||||
if (state == null || sessionIdsToMatrixClient.isNotEmpty()) return Unit.also {
|
if (state == null || sessionIdsToMatrixClient.isNotEmpty()) {
|
||||||
Timber.w("Restore with non-empty map")
|
Timber.w("Restore with non-empty map")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
val sessionIds = state[SAVE_INSTANCE_KEY] as? Array<SessionId>
|
val sessionIds = state[SAVE_INSTANCE_KEY] as? Array<SessionId>
|
||||||
Timber.d("Restore matrix session keys = ${sessionIds?.map { it.value }}")
|
Timber.d("Restore matrix session keys = ${sessionIds?.map { it.value }}")
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ class LoggedInNode @AssistedInject constructor(
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
plugins = plugins
|
plugins = plugins
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val loggedInState = loggedInPresenter.present()
|
val loggedInState = loggedInPresenter.present()
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ class LoggedInPresenter @Inject constructor(
|
||||||
private val networkMonitor: NetworkMonitor,
|
private val networkMonitor: NetworkMonitor,
|
||||||
private val pushService: PushService,
|
private val pushService: PushService,
|
||||||
) : Presenter<LoggedInState> {
|
) : Presenter<LoggedInState> {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): LoggedInState {
|
override fun present(): LoggedInState {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
|
|
||||||
|
|
@ -59,15 +59,15 @@ fun SyncStateView(
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(color = ElementTheme.colors.bgSubtleSecondary)
|
.background(color = ElementTheme.colors.bgSubtleSecondary)
|
||||||
.padding(horizontal = 24.dp, vertical = 10.dp),
|
.padding(horizontal = 24.dp, vertical = 10.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.progressSemantics()
|
.progressSemantics()
|
||||||
.size(12.dp),
|
.size(12.dp),
|
||||||
color = ElementTheme.colors.textPrimary,
|
color = ElementTheme.colors.textPrimary,
|
||||||
strokeWidth = 1.5.dp,
|
strokeWidth = 1.5.dp,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package io.element.android.appnav.room
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
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.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoadingRoomNodeView(
|
fun LoadingRoomNodeView(
|
||||||
state: LoadingRoomState,
|
state: LoadingRoomState,
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@ open class LoadingRoomStateProvider : PreviewParameterProvider<LoadingRoomState>
|
||||||
|
|
||||||
@SingleIn(SessionScope::class)
|
@SingleIn(SessionScope::class)
|
||||||
class LoadingRoomStateFlowFactory @Inject constructor(private val matrixClient: MatrixClient) {
|
class LoadingRoomStateFlowFactory @Inject constructor(private val matrixClient: MatrixClient) {
|
||||||
|
|
||||||
fun create(lifecycleScope: CoroutineScope, roomId: RoomId): StateFlow<LoadingRoomState> =
|
fun create(lifecycleScope: CoroutineScope, roomId: RoomId): StateFlow<LoadingRoomState> =
|
||||||
getRoomFlow(roomId)
|
getRoomFlow(roomId)
|
||||||
.map { room ->
|
.map { room ->
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
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.node.node
|
import com.bumble.appyx.core.node.node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
|
|
@ -65,7 +66,6 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
plugins = plugins
|
plugins = plugins
|
||||||
) {
|
) {
|
||||||
|
|
||||||
data class Inputs(
|
data class Inputs(
|
||||||
val roomId: RoomId,
|
val roomId: RoomId,
|
||||||
val initialElement: RoomLoadedFlowNode.NavTarget = RoomLoadedFlowNode.NavTarget.Messages,
|
val initialElement: RoomLoadedFlowNode.NavTarget = RoomLoadedFlowNode.NavTarget.Messages,
|
||||||
|
|
@ -130,7 +130,8 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
BackstackView()
|
BackstackView(
|
||||||
|
transitionHandler = JumpToEndTransitionHandler(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,6 @@ class RoomLoadedFlowNode @AssistedInject constructor(
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
plugins = plugins,
|
plugins = plugins,
|
||||||
), DaggerComponentOwner {
|
), DaggerComponentOwner {
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onOpenRoom(roomId: RoomId)
|
fun onOpenRoom(roomId: RoomId)
|
||||||
fun onForwardedToSingleRoom(roomId: RoomId)
|
fun onForwardedToSingleRoom(roomId: RoomId)
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ class RootNavStateFlowFactory @Inject constructor(
|
||||||
private val matrixClientsHolder: MatrixClientsHolder,
|
private val matrixClientsHolder: MatrixClientsHolder,
|
||||||
private val loginUserStory: LoginUserStory,
|
private val loginUserStory: LoginUserStory,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var currentCacheIndex = 0
|
private var currentCacheIndex = 0
|
||||||
|
|
||||||
fun create(savedStateMap: SavedStateMap?): Flow<RootNavState> {
|
fun create(savedStateMap: SavedStateMap?): Flow<RootNavState> {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ class RootPresenter @Inject constructor(
|
||||||
private val rageshakeDetectionPresenter: RageshakeDetectionPresenter,
|
private val rageshakeDetectionPresenter: RageshakeDetectionPresenter,
|
||||||
private val appErrorStateService: AppErrorStateService,
|
private val appErrorStateService: AppErrorStateService,
|
||||||
) : Presenter<RootState> {
|
) : Presenter<RootState> {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): RootState {
|
override fun present(): RootState {
|
||||||
val rageshakeDetectionState = rageshakeDetectionPresenter.present()
|
val rageshakeDetectionState = rageshakeDetectionPresenter.present()
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class RoomFlowNodeTest {
|
class RoomFlowNodeTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||||
|
|
||||||
|
|
@ -49,7 +48,6 @@ class RoomFlowNodeTest {
|
||||||
val mainDispatcherRule = MainDispatcherRule()
|
val mainDispatcherRule = MainDispatcherRule()
|
||||||
|
|
||||||
private class FakeMessagesEntryPoint : MessagesEntryPoint {
|
private class FakeMessagesEntryPoint : MessagesEntryPoint {
|
||||||
|
|
||||||
var nodeId: String? = null
|
var nodeId: String? = null
|
||||||
var callback: MessagesEntryPoint.Callback? = null
|
var callback: MessagesEntryPoint.Callback? = null
|
||||||
|
|
||||||
|
|
@ -68,12 +66,10 @@ class RoomFlowNodeTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FakeRoomDetailsEntryPoint : RoomDetailsEntryPoint {
|
private class FakeRoomDetailsEntryPoint : RoomDetailsEntryPoint {
|
||||||
|
|
||||||
var nodeId: String? = null
|
var nodeId: String? = null
|
||||||
|
|
||||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomDetailsEntryPoint.NodeBuilder {
|
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomDetailsEntryPoint.NodeBuilder {
|
||||||
return object : RoomDetailsEntryPoint.NodeBuilder {
|
return object : RoomDetailsEntryPoint.NodeBuilder {
|
||||||
|
|
||||||
override fun params(params: RoomDetailsEntryPoint.Params): RoomDetailsEntryPoint.NodeBuilder {
|
override fun params(params: RoomDetailsEntryPoint.Params): RoomDetailsEntryPoint.NodeBuilder {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class LoggedInPresenterTest {
|
class LoggedInPresenterTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,25 +28,24 @@ import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class LoadingRoomStateFlowFactoryTest {
|
class LoadingRoomStateFlowFactoryTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `flow should emit Loading and then Loaded when there is a room in cache`() = runTest {
|
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 {
|
val matrixClient = FakeMatrixClient(A_SESSION_ID).apply {
|
||||||
givenGetRoomResult(A_ROOM_ID, room)
|
givenGetRoomResult(A_ROOM_ID, room)
|
||||||
}
|
}
|
||||||
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
||||||
flowFactory
|
flowFactory
|
||||||
.create(this, A_ROOM_ID)
|
.create(this, A_ROOM_ID)
|
||||||
.test {
|
.test {
|
||||||
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
||||||
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room))
|
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `flow should emit Loading and then Loaded when there is a room in cache after SS is loaded`() = runTest {
|
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 roomListService = FakeRoomListService()
|
||||||
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService)
|
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService)
|
||||||
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
||||||
|
|
|
||||||
203
build.gradle.kts
203
build.gradle.kts
|
|
@ -1,7 +1,5 @@
|
||||||
import com.google.devtools.ksp.gradle.KspTask
|
import com.google.devtools.ksp.gradle.KspTask
|
||||||
import kotlinx.kover.api.KoverTaskExtension
|
|
||||||
import org.apache.tools.ant.taskdefs.optional.ReplaceRegExp
|
import org.apache.tools.ant.taskdefs.optional.ReplaceRegExp
|
||||||
import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
@ -28,6 +26,7 @@ buildscript {
|
||||||
|
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
|
id("io.element.android-root")
|
||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
alias(libs.plugins.android.library) apply false
|
alias(libs.plugins.android.library) apply false
|
||||||
alias(libs.plugins.kotlin.android) apply false
|
alias(libs.plugins.kotlin.android) apply false
|
||||||
|
|
@ -41,7 +40,6 @@ plugins {
|
||||||
alias(libs.plugins.ktlint)
|
alias(libs.plugins.ktlint)
|
||||||
alias(libs.plugins.dependencygraph)
|
alias(libs.plugins.dependencygraph)
|
||||||
alias(libs.plugins.sonarqube)
|
alias(libs.plugins.sonarqube)
|
||||||
alias(libs.plugins.kover)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Delete>("clean").configure {
|
tasks.register<Delete>("clean").configure {
|
||||||
|
|
@ -62,7 +60,7 @@ allprojects {
|
||||||
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
detektPlugins("io.nlopez.compose.rules:detekt:0.3.8")
|
detektPlugins("io.nlopez.compose.rules:detekt:0.3.9")
|
||||||
}
|
}
|
||||||
|
|
||||||
// KtLint
|
// KtLint
|
||||||
|
|
@ -74,7 +72,7 @@ allprojects {
|
||||||
configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
|
configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
|
||||||
// See https://github.com/pinterest/ktlint/releases/
|
// See https://github.com/pinterest/ktlint/releases/
|
||||||
// TODO Regularly check for new version here ^
|
// TODO Regularly check for new version here ^
|
||||||
version.set("0.48.2")
|
version.set("1.1.1")
|
||||||
android.set(true)
|
android.set(true)
|
||||||
ignoreFailures.set(false)
|
ignoreFailures.set(false)
|
||||||
enableExperimentalRules.set(true)
|
enableExperimentalRules.set(true)
|
||||||
|
|
@ -100,11 +98,16 @@ allprojects {
|
||||||
// You can override by passing `-PallWarningsAsErrors=true` in the command line
|
// You can override by passing `-PallWarningsAsErrors=true` in the command line
|
||||||
// Or add a line with "allWarningsAsErrors=true" in your ~/.gradle/gradle.properties file
|
// Or add a line with "allWarningsAsErrors=true" in your ~/.gradle/gradle.properties file
|
||||||
kotlinOptions.allWarningsAsErrors = project.properties["allWarningsAsErrors"] == "true"
|
kotlinOptions.allWarningsAsErrors = project.properties["allWarningsAsErrors"] == "true"
|
||||||
}
|
|
||||||
|
|
||||||
// Detect unused dependencies
|
kotlinOptions {
|
||||||
apply {
|
/*
|
||||||
plugin("com.autonomousapps.dependency-analysis")
|
// 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<Test>().configureEach {
|
|
||||||
extensions.configure<KoverTaskExtension> {
|
|
||||||
val enabled = name.contains("debug", ignoreCase = true)
|
|
||||||
isDisabled.set(!enabled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register quality check tasks.
|
// Register quality check tasks.
|
||||||
tasks.register("runQualityChecks") {
|
tasks.register("runQualityChecks") {
|
||||||
project.subprojects {
|
project.subprojects {
|
||||||
|
|
@ -361,7 +190,7 @@ subprojects {
|
||||||
|
|
||||||
// Workaround for https://github.com/airbnb/Showkase/issues/335
|
// Workaround for https://github.com/airbnb/Showkase/issues/335
|
||||||
subprojects {
|
subprojects {
|
||||||
tasks.withType<KspTask>() {
|
tasks.withType<KspTask> {
|
||||||
doLast {
|
doLast {
|
||||||
fileTree(layout.buildDirectory).apply { include("**/*ShowkaseExtension*.kt") }.files.forEach { file ->
|
fileTree(layout.buildDirectory).apply { include("**/*ShowkaseExtension*.kt") }.files.forEach { file ->
|
||||||
ReplaceRegExp().apply {
|
ReplaceRegExp().apply {
|
||||||
|
|
@ -383,13 +212,15 @@ subprojects {
|
||||||
if (project.findProperty("composeCompilerReports") == "true") {
|
if (project.findProperty("composeCompilerReports") == "true") {
|
||||||
freeCompilerArgs += listOf(
|
freeCompilerArgs += listOf(
|
||||||
"-P",
|
"-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") {
|
if (project.findProperty("composeCompilerMetrics") == "true") {
|
||||||
freeCompilerArgs += listOf(
|
freeCompilerArgs += listOf(
|
||||||
"-P",
|
"-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"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
[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.
|
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:
|
To compute the code coverage, run:
|
||||||
|
|
||||||
```bash
|
```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
|
To ensure that the code coverage threshold are OK, you can run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./gradlew koverMergedVerify
|
./gradlew :app:koverVerify
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the CI performs this check on every pull requests.
|
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
|
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
|
### Other points
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
* [Stop Synapse](#stop-synapse)
|
* [Stop Synapse](#stop-synapse)
|
||||||
* [Troubleshoot](#troubleshoot)
|
* [Troubleshoot](#troubleshoot)
|
||||||
* [Android Emulator does cannot reach the homeserver](#android-emulator-does-cannot-reach-the-homeserver)
|
* [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)
|
* [virtualenv command fails](#virtualenv-command-fails)
|
||||||
|
|
||||||
<!--- END -->
|
<!--- END -->
|
||||||
|
|
|
||||||
2
fastlane/metadata/android/en-US/changelogs/40004010.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40004010.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: Mainly bug fixes.
|
||||||
|
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||||
|
|
@ -25,8 +25,8 @@ import io.element.android.features.analytics.api.AnalyticsOptInEvents
|
||||||
import io.element.android.features.analytics.api.R
|
import io.element.android.features.analytics.api.R
|
||||||
import io.element.android.libraries.designsystem.components.LINK_TAG
|
import io.element.android.libraries.designsystem.components.LINK_TAG
|
||||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
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.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart
|
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.ListItem
|
||||||
import io.element.android.libraries.designsystem.theme.components.ListSupportingText
|
import io.element.android.libraries.designsystem.theme.components.ListSupportingText
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_analytics_settings_share_data">"Compartir datos analíticos"</string>
|
||||||
|
<string name="screen_analytics_settings_help_us_improve">"Compartir datos de uso anónimos para ayudarnos a identificar problemas."</string>
|
||||||
|
<string name="screen_analytics_settings_read_terms">"Puedes leer todos nuestros términos %1$s."</string>
|
||||||
|
<string name="screen_analytics_settings_read_terms_content_link">"aquí"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_analytics_settings_share_data">"Condividi dati statistici"</string>
|
||||||
|
<string name="screen_analytics_settings_help_us_improve">"Condividi dati di utilizzo anonimi per aiutarci a identificare problemi."</string>
|
||||||
|
<string name="screen_analytics_settings_read_terms">"Puoi leggere tutti i nostri termini %1$s."</string>
|
||||||
|
<string name="screen_analytics_settings_read_terms_content_link">"qui"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_analytics_settings_share_data">"分享分析數據"</string>
|
<string name="screen_analytics_settings_share_data">"提供分析數據"</string>
|
||||||
<string name="screen_analytics_settings_help_us_improve">"分享匿名的使用數據以協助我們釐清問題。"</string>
|
<string name="screen_analytics_settings_help_us_improve">"提供匿名的使用數據以協助我們釐清問題。"</string>
|
||||||
<string name="screen_analytics_settings_read_terms">"您可以到%1$s閱讀我們的條款。"</string>
|
<string name="screen_analytics_settings_read_terms">"您可以到%1$s閱讀我們的條款。"</string>
|
||||||
<string name="screen_analytics_settings_read_terms_content_link">"這裡"</string>
|
<string name="screen_analytics_settings_read_terms_content_link">"這裡"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ class AnalyticsOptInNode @AssistedInject constructor(
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val presenter: AnalyticsOptInPresenter,
|
private val presenter: AnalyticsOptInPresenter,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
private fun onClickTerms(activity: Activity, darkTheme: Boolean) {
|
private fun onClickTerms(activity: Activity, darkTheme: Boolean) {
|
||||||
activity.openUrlInChromeCustomTab(null, darkTheme, AnalyticsConfig.POLICY_LINK)
|
activity.openUrlInChromeCustomTab(null, darkTheme, AnalyticsConfig.POLICY_LINK)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ class AnalyticsOptInPresenter @Inject constructor(
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
) : Presenter<AnalyticsOptInState> {
|
) : Presenter<AnalyticsOptInState> {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): AnalyticsOptInState {
|
override fun present(): AnalyticsOptInState {
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,7 @@ package io.element.android.features.analytics.impl
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
open class AnalyticsOptInStateProvider @Inject constructor(
|
open class AnalyticsOptInStateProvider @Inject constructor() : PreviewParameterProvider<AnalyticsOptInState> {
|
||||||
) : PreviewParameterProvider<AnalyticsOptInState> {
|
|
||||||
override val values: Sequence<AnalyticsOptInState>
|
override val values: Sequence<AnalyticsOptInState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aAnalyticsOptInState(),
|
aAnalyticsOptInState(),
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ class DefaultAnalyticsPreferencesPresenter @Inject constructor(
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
) : AnalyticsPreferencesPresenter {
|
) : AnalyticsPreferencesPresenter {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): AnalyticsPreferencesState {
|
override fun present(): AnalyticsPreferencesState {
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_analytics_prompt_data_usage">"No registraremos o perfilaremos ningún dato personal"</string>
|
||||||
|
<string name="screen_analytics_prompt_help_us_improve">"Compartir datos de uso anónimos para ayudarnos a identificar problemas."</string>
|
||||||
|
<string name="screen_analytics_prompt_read_terms">"Puedes leer todos nuestros términos %1$s."</string>
|
||||||
|
<string name="screen_analytics_prompt_read_terms_content_link">"aquí"</string>
|
||||||
|
<string name="screen_analytics_prompt_settings">"Puedes desactivarlo en cualquier momento"</string>
|
||||||
|
<string name="screen_analytics_prompt_third_party_sharing">"No compartiremos tus datos con terceros"</string>
|
||||||
|
<string name="screen_analytics_prompt_title">"Ayuda a mejorar %1$s"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_analytics_prompt_data_usage">"Non registreremo né profileremo alcun dato personale"</string>
|
||||||
|
<string name="screen_analytics_prompt_help_us_improve">"Condividi dati di utilizzo anonimi per aiutarci a identificare problemi."</string>
|
||||||
|
<string name="screen_analytics_prompt_read_terms">"Puoi leggere tutti i nostri termini %1$s."</string>
|
||||||
|
<string name="screen_analytics_prompt_read_terms_content_link">"qui"</string>
|
||||||
|
<string name="screen_analytics_prompt_settings">"Puoi disattivarlo in qualsiasi momento"</string>
|
||||||
|
<string name="screen_analytics_prompt_third_party_sharing">"Non condivideremo i tuoi dati con terze parti"</string>
|
||||||
|
<string name="screen_analytics_prompt_title">"Aiutaci a migliorare %1$s"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_analytics_prompt_data_usage">"我們不會紀錄或剖繪您的個人資料"</string>
|
<string name="screen_analytics_prompt_data_usage">"我們不會紀錄或剖繪您的個人資料"</string>
|
||||||
<string name="screen_analytics_prompt_help_us_improve">"分享匿名的使用數據以協助我們釐清問題。"</string>
|
<string name="screen_analytics_prompt_help_us_improve">"提供匿名的使用數據以協助我們釐清問題。"</string>
|
||||||
<string name="screen_analytics_prompt_read_terms">"您可以到%1$s閱讀我們的條款。"</string>
|
<string name="screen_analytics_prompt_read_terms">"您可以到%1$s閱讀我們的條款。"</string>
|
||||||
<string name="screen_analytics_prompt_read_terms_content_link">"這裡"</string>
|
<string name="screen_analytics_prompt_read_terms_content_link">"這裡"</string>
|
||||||
<string name="screen_analytics_prompt_settings">"您可以在任何時候關閉它"</string>
|
<string name="screen_analytics_prompt_settings">"您可以在任何時候關閉它"</string>
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class AnalyticsOptInPresenterTest {
|
class AnalyticsOptInPresenterTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
|
|
@ -70,4 +69,3 @@ class AnalyticsOptInPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class AnalyticsPreferencesPresenterTest {
|
class AnalyticsPreferencesPresenterTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
|
|
@ -82,4 +81,3 @@ class AnalyticsPreferencesPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import io.element.android.features.call.ui.ElementCallActivity
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
|
|
||||||
class CallForegroundService : Service() {
|
class CallForegroundService : Service() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun start(context: Context) {
|
fun start(context: Context) {
|
||||||
val intent = Intent(context, CallForegroundService::class.java)
|
val intent = Intent(context, CallForegroundService::class.java)
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,11 @@ data class WidgetMessage(
|
||||||
@SerialName("action") val action: Action,
|
@SerialName("action") val action: Action,
|
||||||
@SerialName("data") val data: JsonElement? = null,
|
@SerialName("data") val data: JsonElement? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
enum class Direction {
|
enum class Direction {
|
||||||
@SerialName("fromWidget")
|
@SerialName("fromWidget")
|
||||||
FromWidget,
|
FromWidget,
|
||||||
|
|
||||||
@SerialName("toWidget")
|
@SerialName("toWidget")
|
||||||
ToWidget
|
ToWidget
|
||||||
}
|
}
|
||||||
|
|
@ -41,6 +41,7 @@ data class WidgetMessage(
|
||||||
enum class Action {
|
enum class Action {
|
||||||
@SerialName("im.vector.hangup")
|
@SerialName("im.vector.hangup")
|
||||||
HangUp,
|
HangUp,
|
||||||
|
|
||||||
@SerialName("send_event")
|
@SerialName("send_event")
|
||||||
SendEvent,
|
SendEvent,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.CallWidgetProvider
|
||||||
import io.element.android.features.call.utils.WidgetMessageInterceptor
|
import io.element.android.features.call.utils.WidgetMessageInterceptor
|
||||||
import io.element.android.features.call.utils.WidgetMessageSerializer
|
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.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
|
|
@ -63,7 +63,6 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||||
private val matrixClientsProvider: MatrixClientProvider,
|
private val matrixClientsProvider: MatrixClientProvider,
|
||||||
private val appCoroutineScope: CoroutineScope,
|
private val appCoroutineScope: CoroutineScope,
|
||||||
) : Presenter<CallScreenState> {
|
) : Presenter<CallScreenState> {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(callType: CallType, navigator: CallScreenNavigator): CallScreenPresenter
|
fun create(callType: CallType, navigator: CallScreenNavigator): CallScreenPresenter
|
||||||
|
|
@ -75,7 +74,7 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): CallScreenState {
|
override fun present(): CallScreenState {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val urlState = remember { mutableStateOf<Async<String>>(Async.Uninitialized) }
|
val urlState = remember { mutableStateOf<AsyncData<String>>(AsyncData.Uninitialized) }
|
||||||
val callWidgetDriver = remember { mutableStateOf<MatrixWidgetDriver?>(null) }
|
val callWidgetDriver = remember { mutableStateOf<MatrixWidgetDriver?>(null) }
|
||||||
val messageInterceptor = remember { mutableStateOf<WidgetMessageInterceptor?>(null) }
|
val messageInterceptor = remember { mutableStateOf<WidgetMessageInterceptor?>(null) }
|
||||||
var isJoinedCall by rememberSaveable { mutableStateOf(false) }
|
var isJoinedCall by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
@ -154,7 +153,7 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||||
|
|
||||||
private fun CoroutineScope.loadUrl(
|
private fun CoroutineScope.loadUrl(
|
||||||
inputs: CallType,
|
inputs: CallType,
|
||||||
urlState: MutableState<Async<String>>,
|
urlState: MutableState<AsyncData<String>>,
|
||||||
callWidgetDriver: MutableState<MatrixWidgetDriver?>,
|
callWidgetDriver: MutableState<MatrixWidgetDriver?>,
|
||||||
) = launch {
|
) = launch {
|
||||||
urlState.runCatchingUpdatingState {
|
urlState.runCatchingUpdatingState {
|
||||||
|
|
@ -224,6 +223,4 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||||
navigator.close()
|
navigator.close()
|
||||||
widgetDriver?.close()
|
widgetDriver?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,10 @@
|
||||||
|
|
||||||
package io.element.android.features.call.ui
|
package io.element.android.features.call.ui
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.Async
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
||||||
data class CallScreenState(
|
data class CallScreenState(
|
||||||
val urlState: Async<String>,
|
val urlState: AsyncData<String>,
|
||||||
val userAgent: String,
|
val userAgent: String,
|
||||||
val isInWidgetMode: Boolean,
|
val isInWidgetMode: Boolean,
|
||||||
val eventSink: (CallScreenEvents) -> Unit,
|
val eventSink: (CallScreenEvents) -> Unit,
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.call.R
|
import io.element.android.features.call.R
|
||||||
import io.element.android.features.call.utils.WebViewWidgetMessageInterceptor
|
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.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
|
@ -96,7 +96,7 @@ internal fun CallScreenView(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CallWebView(
|
private fun CallWebView(
|
||||||
url: Async<String>,
|
url: AsyncData<String>,
|
||||||
userAgent: String,
|
userAgent: String,
|
||||||
onPermissionsRequested: (PermissionRequest) -> Unit,
|
onPermissionsRequested: (PermissionRequest) -> Unit,
|
||||||
onWebViewCreated: (WebView) -> Unit,
|
onWebViewCreated: (WebView) -> Unit,
|
||||||
|
|
@ -116,7 +116,7 @@ private fun CallWebView(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
update = { webView ->
|
update = { webView ->
|
||||||
if (url is Async.Success && webView.url != url.data) {
|
if (url is AsyncData.Success && webView.url != url.data) {
|
||||||
webView.loadUrl(url.data)
|
webView.loadUrl(url.data)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -161,7 +161,7 @@ internal fun CallScreenViewPreview() {
|
||||||
ElementPreview {
|
ElementPreview {
|
||||||
CallScreenView(
|
CallScreenView(
|
||||||
state = CallScreenState(
|
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,
|
isInWidgetMode = false,
|
||||||
userAgent = "",
|
userAgent = "",
|
||||||
eventSink = {},
|
eventSink = {},
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import android.net.Uri
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CallIntentDataParser @Inject constructor() {
|
class CallIntentDataParser @Inject constructor() {
|
||||||
|
|
||||||
private val validHttpSchemes = sequenceOf("https")
|
private val validHttpSchemes = sequenceOf("https")
|
||||||
|
|
||||||
fun parse(data: String?): String? {
|
fun parse(data: String?): String? {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
class WebViewWidgetMessageInterceptor(
|
class WebViewWidgetMessageInterceptor(
|
||||||
private val webView: WebView,
|
private val webView: WebView,
|
||||||
) : WidgetMessageInterceptor {
|
) : WidgetMessageInterceptor {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// We call both the WebMessageListener and the JavascriptInterface objects in JS with this
|
// 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
|
// '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") {
|
|| !message.data.response && message.data.api == "fromWidget") {
|
||||||
let json = JSON.stringify(event.data)
|
let json = JSON.stringify(event.data)
|
||||||
${"console.log('message sent: ' + json);".takeIf { BuildConfig.DEBUG } }
|
${"console.log('message sent: ' + json);".takeIf { BuildConfig.DEBUG } }
|
||||||
${LISTENER_NAME}.postMessage(json);
|
$LISTENER_NAME.postMessage(json);
|
||||||
} else {
|
} else {
|
||||||
${"console.log('message received (ignored): ' + JSON.stringify(event.data));".takeIf { BuildConfig.DEBUG } }
|
${"console.log('message received (ignored): ' + JSON.stringify(event.data));".takeIf { BuildConfig.DEBUG } }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import io.element.android.features.call.data.WidgetMessage
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
object WidgetMessageSerializer {
|
object WidgetMessageSerializer {
|
||||||
|
|
||||||
private val coder = Json { ignoreUnknownKeys = true }
|
private val coder = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
fun deserialize(message: String): Result<WidgetMessage> {
|
fun deserialize(message: String): Result<WidgetMessage> {
|
||||||
|
|
|
||||||
6
features/call/src/main/res/values-es/translations.xml
Normal file
6
features/call/src/main/res/values-es/translations.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="call_foreground_service_channel_title_android">"Llamada en curso"</string>
|
||||||
|
<string name="call_foreground_service_message_android">"Pulsa para regresar a la llamada"</string>
|
||||||
|
<string name="call_foreground_service_title_android">"☎️ Llamada en curso"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="call_foreground_service_channel_title_android">"Folyamatban lévő hívás"</string>
|
<string name="call_foreground_service_channel_title_android">"Folyamatban lévő hívás"</string>
|
||||||
<string name="call_foreground_service_message_android">"Koppintson a híváshoz való visszatéréshez"</string>
|
<string name="call_foreground_service_message_android">"Koppints a híváshoz való visszatéréshez"</string>
|
||||||
<string name="call_foreground_service_title_android">"☎️ Hívás folyamatban"</string>
|
<string name="call_foreground_service_title_android">"☎️ Hívás folyamatban"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
6
features/call/src/main/res/values-it/translations.xml
Normal file
6
features/call/src/main/res/values-it/translations.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="call_foreground_service_channel_title_android">"Chiamata in corso"</string>
|
||||||
|
<string name="call_foreground_service_message_android">"Tocca per tornare alla chiamata"</string>
|
||||||
|
<string name="call_foreground_service_title_android">"☎️ Chiamata in corso"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -2,5 +2,5 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="call_foreground_service_channel_title_android">"Текущий вызов"</string>
|
<string name="call_foreground_service_channel_title_android">"Текущий вызов"</string>
|
||||||
<string name="call_foreground_service_message_android">"Коснитесь, чтобы вернуться к вызову"</string>
|
<string name="call_foreground_service_message_android">"Коснитесь, чтобы вернуться к вызову"</string>
|
||||||
<string name="call_foreground_service_title_android">"Идёт вызов"</string>
|
<string name="call_foreground_service_title_android">"☎️ Идёт вызов"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import io.element.android.features.call.ui.mapWebkitPermissions
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class MapWebkitPermissionsTest {
|
class MapWebkitPermissionsTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given Webkit's RESOURCE_AUDIO_CAPTURE returns Android's RECORD_AUDIO permission`() {
|
fun `given Webkit's RESOURCE_AUDIO_CAPTURE returns Android's RECORD_AUDIO permission`() {
|
||||||
val permission = mapWebkitPermissions(arrayOf(PermissionRequest.RESOURCE_AUDIO_CAPTURE))
|
val permission = mapWebkitPermissions(arrayOf(PermissionRequest.RESOURCE_AUDIO_CAPTURE))
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.call.CallType
|
import io.element.android.features.call.CallType
|
||||||
import io.element.android.features.call.utils.FakeCallWidgetProvider
|
import io.element.android.features.call.utils.FakeCallWidgetProvider
|
||||||
import io.element.android.features.call.utils.FakeWidgetMessageInterceptor
|
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.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
|
|
@ -48,7 +48,6 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class CallScreenPresenterTest {
|
class CallScreenPresenterTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
|
|
@ -62,7 +61,7 @@ class CallScreenPresenterTest {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
|
|
||||||
val initialState = awaitItem()
|
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()
|
assertThat(initialState.isInWidgetMode).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +82,7 @@ class CallScreenPresenterTest {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.urlState).isInstanceOf(Async.Success::class.java)
|
assertThat(initialState.urlState).isInstanceOf(AsyncData.Success::class.java)
|
||||||
assertThat(initialState.isInWidgetMode).isTrue()
|
assertThat(initialState.isInWidgetMode).isTrue()
|
||||||
assertThat(widgetProvider.getWidgetCalled).isTrue()
|
assertThat(widgetProvider.getWidgetCalled).isTrue()
|
||||||
assertThat(widgetDriver.runCalledCount).isEqualTo(1)
|
assertThat(widgetDriver.runCalledCount).isEqualTo(1)
|
||||||
|
|
@ -191,7 +190,7 @@ class CallScreenPresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
consumeItemsUntilTimeout()
|
consumeItemsUntilTimeout()
|
||||||
|
|
||||||
assertThat(matrixClient.syncService().syncState.value).isEqualTo(SyncState.Running)
|
assertThat(matrixClient.syncService().syncState.value).isEqualTo(SyncState.Running)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,10 @@
|
||||||
package io.element.android.features.call.ui
|
package io.element.android.features.call.ui
|
||||||
|
|
||||||
class FakeCallScreenNavigator : CallScreenNavigator {
|
class FakeCallScreenNavigator : CallScreenNavigator {
|
||||||
var closeCalled = false
|
var closeCalled = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
closeCalled = true
|
closeCalled = true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import java.net.URLEncoder
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner::class)
|
@RunWith(RobolectricTestRunner::class)
|
||||||
class CallIntentDataParserTest {
|
class CallIntentDataParserTest {
|
||||||
|
|
||||||
private val callIntentDataParser = CallIntentDataParser()
|
private val callIntentDataParser = CallIntentDataParser()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -117,7 +116,7 @@ class CallIntentDataParserTest {
|
||||||
@Test
|
@Test
|
||||||
fun `Element Call url with url extra param appPrompt gets url extracted`() {
|
fun `Element Call url with url extra param appPrompt gets url extracted`() {
|
||||||
doTest(
|
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"
|
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +124,7 @@ class CallIntentDataParserTest {
|
||||||
@Test
|
@Test
|
||||||
fun `Element Call url with url extra param in fragment appPrompt gets url extracted`() {
|
fun `Element Call url with url extra param in fragment appPrompt gets url extracted`() {
|
||||||
doTest(
|
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"
|
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=false&confineToRoom=true"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -133,7 +132,7 @@ class CallIntentDataParserTest {
|
||||||
@Test
|
@Test
|
||||||
fun `Element Call url with url extra param in fragment appPrompt and other gets url extracted`() {
|
fun `Element Call url with url extra param in fragment appPrompt and other gets url extracted`() {
|
||||||
doTest(
|
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"
|
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=false&otherParam=maybe&confineToRoom=true"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +140,7 @@ class CallIntentDataParserTest {
|
||||||
@Test
|
@Test
|
||||||
fun `Element Call url with url extra param confineToRoom gets url extracted`() {
|
fun `Element Call url with url extra param confineToRoom gets url extracted`() {
|
||||||
doTest(
|
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"
|
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -149,7 +148,7 @@ class CallIntentDataParserTest {
|
||||||
@Test
|
@Test
|
||||||
fun `Element Call url with url extra param in fragment confineToRoom gets url extracted`() {
|
fun `Element Call url with url extra param in fragment confineToRoom gets url extracted`() {
|
||||||
doTest(
|
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"
|
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=true&appPrompt=false"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -157,7 +156,7 @@ class CallIntentDataParserTest {
|
||||||
@Test
|
@Test
|
||||||
fun `Element Call url with url extra param in fragment confineToRoom and more gets url extracted`() {
|
fun `Element Call url with url extra param in fragment confineToRoom and more gets url extracted`() {
|
||||||
doTest(
|
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"
|
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=true&otherParam=maybe&appPrompt=false"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +164,7 @@ class CallIntentDataParserTest {
|
||||||
@Test
|
@Test
|
||||||
fun `Element Call url with url fragment gets url extracted`() {
|
fun `Element Call url with url fragment gets url extracted`() {
|
||||||
doTest(
|
doTest(
|
||||||
url = "${VALID_CALL_URL_WITH_PARAM}#fragment",
|
url = "$VALID_CALL_URL_WITH_PARAM#fragment",
|
||||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#fragment?$EXTRA_PARAMS"
|
expectedResult = "$VALID_CALL_URL_WITH_PARAM#fragment?$EXTRA_PARAMS"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -173,7 +172,7 @@ class CallIntentDataParserTest {
|
||||||
@Test
|
@Test
|
||||||
fun `Element Call url with url fragment with params gets url extracted`() {
|
fun `Element Call url with url fragment with params gets url extracted`() {
|
||||||
doTest(
|
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"
|
expectedResult = "$VALID_CALL_URL_WITH_PARAM#fragment?otherParam=maybe&$EXTRA_PARAMS"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -181,7 +180,7 @@ class CallIntentDataParserTest {
|
||||||
@Test
|
@Test
|
||||||
fun `Element Call url with url fragment with other params gets url extracted`() {
|
fun `Element Call url with url fragment with other params gets url extracted`() {
|
||||||
doTest(
|
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"
|
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?otherParam=maybe&$EXTRA_PARAMS"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -189,7 +188,7 @@ class CallIntentDataParserTest {
|
||||||
@Test
|
@Test
|
||||||
fun `Element Call url with empty fragment`() {
|
fun `Element Call url with empty fragment`() {
|
||||||
doTest(
|
doTest(
|
||||||
url = "${VALID_CALL_URL_WITH_PARAM}#",
|
url = "$VALID_CALL_URL_WITH_PARAM#",
|
||||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
|
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +196,7 @@ class CallIntentDataParserTest {
|
||||||
@Test
|
@Test
|
||||||
fun `Element Call url with empty fragment query`() {
|
fun `Element Call url with empty fragment query`() {
|
||||||
doTest(
|
doTest(
|
||||||
url = "${VALID_CALL_URL_WITH_PARAM}#?",
|
url = "$VALID_CALL_URL_WITH_PARAM#?",
|
||||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
|
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class DefaultCallWidgetProviderTest {
|
class DefaultCallWidgetProviderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getWidget - fails if the session does not exist`() = runTest {
|
fun `getWidget - fails if the session does not exist`() = runTest {
|
||||||
val provider = createProvider(matrixClientProvider = FakeMatrixClientProvider { Result.failure(Exception("Session not found")) })
|
val provider = createProvider(matrixClientProvider = FakeMatrixClientProvider { Result.failure(Exception("Session not found")) })
|
||||||
|
|
|
||||||
|
|
@ -24,19 +24,18 @@ import io.element.android.libraries.matrix.test.widget.FakeWidgetDriver
|
||||||
class FakeCallWidgetProvider(
|
class FakeCallWidgetProvider(
|
||||||
private val widgetDriver: FakeWidgetDriver = FakeWidgetDriver(),
|
private val widgetDriver: FakeWidgetDriver = FakeWidgetDriver(),
|
||||||
private val url: String = "https://call.element.io",
|
private val url: String = "https://call.element.io",
|
||||||
) : CallWidgetProvider {
|
) : CallWidgetProvider {
|
||||||
|
var getWidgetCalled = false
|
||||||
|
private set
|
||||||
|
|
||||||
var getWidgetCalled = false
|
override suspend fun getWidget(
|
||||||
private set
|
sessionId: SessionId,
|
||||||
|
roomId: RoomId,
|
||||||
override suspend fun getWidget(
|
clientId: String,
|
||||||
sessionId: SessionId,
|
languageTag: String?,
|
||||||
roomId: RoomId,
|
theme: String?
|
||||||
clientId: String,
|
): Result<Pair<MatrixWidgetDriver, String>> {
|
||||||
languageTag: String?,
|
getWidgetCalled = true
|
||||||
theme: String?
|
return Result.success(widgetDriver to url)
|
||||||
): Result<Pair<MatrixWidgetDriver, String>> {
|
|
||||||
getWidgetCalled = true
|
|
||||||
return Result.success(widgetDriver to url)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,15 @@ package io.element.android.features.call.utils
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
|
||||||
class FakeWidgetMessageInterceptor : WidgetMessageInterceptor {
|
class FakeWidgetMessageInterceptor : WidgetMessageInterceptor {
|
||||||
val sentMessages = mutableListOf<String>()
|
val sentMessages = mutableListOf<String>()
|
||||||
|
|
||||||
override val interceptedMessages = MutableSharedFlow<String>(extraBufferCapacity = 1)
|
override val interceptedMessages = MutableSharedFlow<String>(extraBufferCapacity = 1)
|
||||||
|
|
||||||
override fun sendMessage(message: String) {
|
override fun sendMessage(message: String) {
|
||||||
sentMessages += message
|
sentMessages += message
|
||||||
}
|
|
||||||
|
|
||||||
fun givenInterceptedMessage(message: String) {
|
|
||||||
interceptedMessages.tryEmit(message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenInterceptedMessage(message: String) {
|
||||||
|
interceptedMessages.tryEmit(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
interface CreateRoomEntryPoint : FeatureEntryPoint {
|
interface CreateRoomEntryPoint : FeatureEntryPoint {
|
||||||
|
|
||||||
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
||||||
interface NodeBuilder {
|
interface NodeBuilder {
|
||||||
fun callback(callback: Callback): NodeBuilder
|
fun callback(callback: Callback): NodeBuilder
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
package io.element.android.features.createroom.api
|
package io.element.android.features.createroom.api
|
||||||
|
|
||||||
import androidx.compose.runtime.MutableState
|
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.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
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 userId The user to start a DM with.
|
||||||
* @param actionState The state to update with the result of the action.
|
* @param actionState The state to update with the result of the action.
|
||||||
*/
|
*/
|
||||||
suspend fun execute(userId: UserId, actionState: MutableState<Async<RoomId>>)
|
suspend fun execute(userId: UserId, actionState: MutableState<AsyncAction<RoomId>>)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ class ConfigureRoomFlowNode @AssistedInject constructor(
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
plugins = plugins
|
plugins = plugins
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val component by lazy {
|
private val component by lazy {
|
||||||
parent!!.bindings<CreateRoomComponent.ParentBindings>().createRoomComponentBuilder().build()
|
parent!!.bindings<CreateRoomComponent.ParentBindings>().createRoomComponentBuilder().build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ import javax.inject.Inject
|
||||||
class CreateRoomDataStore @Inject constructor(
|
class CreateRoomDataStore @Inject constructor(
|
||||||
val selectedUserListDataStore: UserListDataStore,
|
val selectedUserListDataStore: UserListDataStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val createRoomConfigFlow: MutableStateFlow<CreateRoomConfig> = MutableStateFlow(CreateRoomConfig())
|
private val createRoomConfigFlow: MutableStateFlow<CreateRoomConfig> = MutableStateFlow(CreateRoomConfig())
|
||||||
private var cachedAvatarUri: Uri? = null
|
private var cachedAvatarUri: Uri? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ class CreateRoomFlowNode @AssistedInject constructor(
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
plugins = plugins
|
plugins = plugins
|
||||||
) {
|
) {
|
||||||
|
|
||||||
sealed interface NavTarget : Parcelable {
|
sealed interface NavTarget : Parcelable {
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object Root : NavTarget
|
data object Root : NavTarget
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,10 @@ import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultCreateRoomEntryPoint @Inject constructor() : CreateRoomEntryPoint {
|
class DefaultCreateRoomEntryPoint @Inject constructor() : CreateRoomEntryPoint {
|
||||||
|
|
||||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): CreateRoomEntryPoint.NodeBuilder {
|
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): CreateRoomEntryPoint.NodeBuilder {
|
||||||
|
|
||||||
val plugins = ArrayList<Plugin>()
|
val plugins = ArrayList<Plugin>()
|
||||||
|
|
||||||
return object : CreateRoomEntryPoint.NodeBuilder {
|
return object : CreateRoomEntryPoint.NodeBuilder {
|
||||||
|
|
||||||
override fun callback(callback: CreateRoomEntryPoint.Callback): CreateRoomEntryPoint.NodeBuilder {
|
override fun callback(callback: CreateRoomEntryPoint.Callback): CreateRoomEntryPoint.NodeBuilder {
|
||||||
plugins += callback
|
plugins += callback
|
||||||
return this
|
return this
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import androidx.compose.runtime.MutableState
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import im.vector.app.features.analytics.plan.CreatedRoom
|
import im.vector.app.features.analytics.plan.CreatedRoom
|
||||||
import io.element.android.features.createroom.api.StartDMAction
|
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.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -35,18 +35,17 @@ class DefaultStartDMAction @Inject constructor(
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
) : StartDMAction {
|
) : StartDMAction {
|
||||||
|
override suspend fun execute(userId: UserId, actionState: MutableState<AsyncAction<RoomId>>) {
|
||||||
override suspend fun execute(userId: UserId, actionState: MutableState<Async<RoomId>>) {
|
actionState.value = AsyncAction.Loading
|
||||||
actionState.value = Async.Loading()
|
|
||||||
when (val result = matrixClient.startDM(userId)) {
|
when (val result = matrixClient.startDM(userId)) {
|
||||||
is StartDMResult.Success -> {
|
is StartDMResult.Success -> {
|
||||||
if (result.isNew) {
|
if (result.isNew) {
|
||||||
analyticsService.capture(CreatedRoom(isDM = true))
|
analyticsService.capture(CreatedRoom(isDM = true))
|
||||||
}
|
}
|
||||||
actionState.value = Async.Success(result.roomId)
|
actionState.value = AsyncAction.Success(result.roomId)
|
||||||
}
|
}
|
||||||
is StartDMResult.Failure -> {
|
is StartDMResult.Failure -> {
|
||||||
actionState.value = Async.Failure(result.throwable)
|
actionState.value = AsyncAction.Failure(result.throwable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ class AddPeopleNode @AssistedInject constructor(
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val presenter: AddPeoplePresenter,
|
private val presenter: AddPeoplePresenter,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onContinue()
|
fun onContinue()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ class AddPeoplePresenter @Inject constructor(
|
||||||
userRepository: UserRepository,
|
userRepository: UserRepository,
|
||||||
dataStore: CreateRoomDataStore,
|
dataStore: CreateRoomDataStore,
|
||||||
) : Presenter<UserListState> {
|
) : Presenter<UserListState> {
|
||||||
|
|
||||||
private val userListPresenter = userListPresenterFactory.create(
|
private val userListPresenter = userListPresenterFactory.create(
|
||||||
UserListPresenterArgs(
|
UserListPresenterArgs(
|
||||||
selectionMode = SelectionMode.Multiple,
|
selectionMode = SelectionMode.Multiple,
|
||||||
|
|
@ -45,4 +44,3 @@ class AddPeoplePresenter @Inject constructor(
|
||||||
return userListPresenter.present()
|
return userListPresenter.present()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,13 @@ open class AddPeopleUserListStateProvider : PreviewParameterProvider<UserListSta
|
||||||
selectionMode = SelectionMode.Multiple,
|
selectionMode = SelectionMode.Multiple,
|
||||||
),
|
),
|
||||||
aUserListState().copy(
|
aUserListState().copy(
|
||||||
searchResults = SearchBarResultState.Results(aMatrixUserList()
|
searchResults = SearchBarResultState.Results(
|
||||||
.mapIndexed { index, matrixUser ->
|
aMatrixUserList()
|
||||||
UserSearchResult(matrixUser, index % 2 == 0)
|
.mapIndexed { index, matrixUser ->
|
||||||
}
|
UserSearchResult(matrixUser, index % 2 == 0)
|
||||||
.toImmutableList()),
|
}
|
||||||
|
.toImmutableList()
|
||||||
|
),
|
||||||
selectedUsers = aListOfSelectedUsers(),
|
selectedUsers = aListOfSelectedUsers(),
|
||||||
isSearchActive = true,
|
isSearchActive = true,
|
||||||
selectionMode = SelectionMode.Multiple,
|
selectionMode = SelectionMode.Multiple,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package io.element.android.features.createroom.impl.addpeople
|
package io.element.android.features.createroom.impl.addpeople
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
|
@ -27,6 +26,7 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
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.R
|
||||||
import io.element.android.features.createroom.impl.components.UserListView
|
import io.element.android.features.createroom.impl.components.UserListView
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListEvents
|
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.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.compound.theme.ElementTheme
|
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AddPeopleView(
|
fun AddPeopleView(
|
||||||
state: UserListState,
|
state: UserListState,
|
||||||
|
|
|
||||||
|
|
@ -29,14 +29,14 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.unit.dp
|
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.RoomPrivacyItem
|
||||||
import io.element.android.features.createroom.impl.configureroom.roomPrivacyItems
|
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.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.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.RadioButton
|
import io.element.android.libraries.designsystem.theme.components.RadioButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.compound.theme.ElementTheme
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RoomPrivacyOption(
|
fun RoomPrivacyOption(
|
||||||
|
|
@ -85,7 +85,8 @@ fun RoomPrivacyOption(
|
||||||
.align(Alignment.CenterVertically)
|
.align(Alignment.CenterVertically)
|
||||||
.size(48.dp),
|
.size(48.dp),
|
||||||
selected = isSelected,
|
selected = isSelected,
|
||||||
onClick = null // null recommended for accessibility with screenreaders
|
// null recommended for accessibility with screenreaders
|
||||||
|
onClick = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,10 +57,7 @@ fun SearchMultipleUsersResultItem(
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
internal fun SearchMultipleUsersResultItemPreview() = ElementThemedPreview { ContentToPreview() }
|
internal fun SearchMultipleUsersResultItemPreview() = ElementThemedPreview {
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ContentToPreview() {
|
|
||||||
Column {
|
Column {
|
||||||
SearchMultipleUsersResultItem(
|
SearchMultipleUsersResultItem(
|
||||||
searchResult = UserSearchResult(
|
searchResult = UserSearchResult(
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue