Merge branch 'release/0.4.1' into main
This commit is contained in:
@@ -14,10 +14,19 @@ ij_smart_tabs = false
|
||||
ij_visual_guides = none
|
||||
ij_wrap_on_typing = false
|
||||
|
||||
# Ktlint rule, for more information see https://pinterest.github.io/ktlint/faq/#why-is-editorconfig-property-disabled_rules-deprecated-and-how-do-i-resolve-this
|
||||
# Ktlint rule, for more information see https://pinterest.github.io/ktlint/1.1.1/faq/#how-do-i-enable-or-disable-a-rule
|
||||
ktlint_standard_wrapping = disabled
|
||||
ktlint_standard_trailing-comma-on-call-site = disabled
|
||||
|
||||
ktlint_standard_trailing-comma-on-declaration-site = disabled
|
||||
ktlint_standard_multiline-expression-wrapping = disabled
|
||||
ktlint_standard_string-template-indent = disabled
|
||||
ktlint_standard_spacing-between-declarations-with-annotations = disabled
|
||||
ktlint_standard_function-signature = disabled
|
||||
ktlint_standard_annotation = disabled
|
||||
ktlint_standard_parameter-list-wrapping = disabled
|
||||
ktlint_standard_indent = disabled
|
||||
ktlint_standard_blank-line-before-declaration = disabled
|
||||
ktlint_function_naming_ignore_when_annotated_with=Composable
|
||||
|
||||
[*.java]
|
||||
ij_java_align_consecutive_assignments = false
|
||||
|
||||
6
.github/renovate.json
vendored
6
.github/renovate.json
vendored
@@ -18,12 +18,6 @@
|
||||
],
|
||||
"groupName" : "kotlin"
|
||||
},
|
||||
{
|
||||
"matchPackageNames" : [
|
||||
"org.jetbrains.kotlinx.kover"
|
||||
],
|
||||
"enabled" : false
|
||||
},
|
||||
{
|
||||
"matchPackagePatterns" : [
|
||||
"^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
|
||||
steps:
|
||||
- name: Remove Run-Maestro label
|
||||
if: ${{ github.event.label.name == 'Run-Maestro' }}
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.label.name == 'Run-Maestro' }}
|
||||
uses: actions-ecosystem/action-remove-labels@v1
|
||||
with:
|
||||
labels: Run-Maestro
|
||||
- uses: actions/checkout@v4
|
||||
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||
- uses: actions/setup-java@v4
|
||||
name: Use JDK 17
|
||||
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
- name: Assemble debug APK
|
||||
run: ./gradlew :app:assembleDebug $CI_GRADLE_ARG_PROPERTIES
|
||||
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
|
||||
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
|
||||
- uses: mobile-dev-inc/action-maestro-cloud@v1.8.0
|
||||
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||
with:
|
||||
api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }}
|
||||
# Doc says (https://github.com/mobile-dev-inc/action-maestro-cloud#android):
|
||||
|
||||
4
.github/workflows/nightlyReports.yml
vendored
4
.github/workflows/nightlyReports.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
||||
|
||||
- name: 📈 Generate kover report and verify coverage
|
||||
run: ./gradlew koverMergedReport koverMergedVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true
|
||||
run: ./gradlew :app:koverHtmlReport :app:koverXmlReport :app:koverVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true
|
||||
|
||||
- name: ✅ Upload kover report
|
||||
if: always()
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
with:
|
||||
name: kover-results
|
||||
path: |
|
||||
**/build/reports/kover/merged
|
||||
**/build/reports/kover
|
||||
|
||||
- name: 🔊 Publish results to Sonar
|
||||
env:
|
||||
|
||||
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
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
|
||||
- name: ⏬ Checkout with LFS (Branch)
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: nschloe/action-cached-lfs-checkout@v1.2.2
|
||||
@@ -43,8 +43,8 @@ jobs:
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Record screenshots
|
||||
id: record
|
||||
run: ./.github/workflows/scripts/recordScreenshots.sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ secrets.GITHUB_REPOSITORY }}
|
||||
|
||||
|
||||
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
|
||||
|
||||
echo "Committing changes"
|
||||
git config user.name "ElementBot"
|
||||
git config user.email "benoitm+elementbot@element.io"
|
||||
git config http.sslVerify false
|
||||
|
||||
if [[ -z ${INPUT_AUTHOR_NAME} ]]; then
|
||||
git config user.name "ElementBot"
|
||||
else
|
||||
git config --local user.name "${INPUT_AUTHOR_NAME}"
|
||||
fi
|
||||
|
||||
if [[ -z ${INPUT_AUTHOR_EMAIL} ]]; then
|
||||
git config user.email "benoitm+elementbot@element.io"
|
||||
else
|
||||
git config --local user.name "${INPUT_AUTHOR_EMAIL}"
|
||||
fi
|
||||
git add -A
|
||||
git commit -m "Update screenshots"
|
||||
|
||||
GITHUB_REPO="https://$GITHUB_ACTOR:$TOKEN@github.com/$REPO.git"
|
||||
echo "Pushing changes"
|
||||
git push "https://$TOKEN@github.com/$REPO.git" $BRANCH
|
||||
if [[ -z ${GITHUB_ACTOR} ]]; then
|
||||
echo "No GITHUB_ACTOR env var"
|
||||
GITHUB_REPO="https://$TOKEN@github.com/$REPO.git"
|
||||
fi
|
||||
git push $GITHUB_REPO "$BRANCH"
|
||||
echo "Done!"
|
||||
|
||||
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
||||
|
||||
- name: 📈Generate kover report and verify coverage
|
||||
run: ./gradlew koverMergedReport koverMergedVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true
|
||||
run: ./gradlew :app:koverHtmlReport :app:koverXmlReport :app:koverVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true
|
||||
|
||||
- name: 🚫 Upload kover failed coverage reports
|
||||
if: failure()
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
with:
|
||||
name: kover-error-report
|
||||
path: |
|
||||
**/kover/merged/verification/errors.txt
|
||||
app/build/reports/kover/verify.err
|
||||
|
||||
- name: ✅ Upload kover report (disabled)
|
||||
if: always()
|
||||
@@ -83,4 +83,4 @@ jobs:
|
||||
if: always()
|
||||
uses: codecov/codecov-action@v3
|
||||
# with:
|
||||
# files: build/reports/kover/merged/xml/report.xml
|
||||
# files: build/reports/kover/xml/report.xml
|
||||
|
||||
@@ -6,12 +6,10 @@ appId: ${APP_ID}
|
||||
- takeScreenshot: build/maestro/900-SignOutScreen
|
||||
- back
|
||||
- tapOn: "Sign out"
|
||||
- tapOn:
|
||||
id: "sign-out-submit"
|
||||
# Ensure cancel cancels
|
||||
- tapOn: "Cancel"
|
||||
- tapOn:
|
||||
id: "sign-out-submit"
|
||||
id: "dialog-negative"
|
||||
- tapOn: "Sign out"
|
||||
- tapOn:
|
||||
id: "dialog-positive"
|
||||
- runFlow: ../assertions/assertInitDisplayed.yaml
|
||||
|
||||
@@ -9,5 +9,5 @@ appId: ${APP_ID}
|
||||
index: 1
|
||||
- takeScreenshot: build/maestro/330-createAndDeleteDM
|
||||
- tapOn: "maestroelement2"
|
||||
- tapOn: "Leave room"
|
||||
- tapOn: "Leave conversation"
|
||||
- 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)
|
||||
========================================
|
||||
|
||||
|
||||
@@ -58,7 +58,6 @@ import java.io.File
|
||||
*/
|
||||
@AutoService(CodeGenerator::class)
|
||||
class ContributesNodeCodeGenerator : CodeGenerator {
|
||||
|
||||
override fun isApplicable(context: AnvilContext): Boolean = true
|
||||
|
||||
override fun generateCode(codeGenDir: File, module: ModuleDescriptor, projectFiles: Collection<KtFile>): Collection<GeneratedFile> {
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.android.build.api.variant.FilterConfiguration.FilterType.ABI
|
||||
import extension.allFeaturesImpl
|
||||
import extension.allLibrariesImpl
|
||||
import extension.allServicesImpl
|
||||
import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-application")
|
||||
@@ -27,7 +28,8 @@ plugins {
|
||||
alias(libs.plugins.anvil)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.kapt)
|
||||
alias(libs.plugins.firebaseAppDistribution)
|
||||
// When using precompiled plugins, we need to apply the firebase plugin like this
|
||||
id(libs.plugins.firebaseAppDistribution.get().pluginId)
|
||||
alias(libs.plugins.knit)
|
||||
id("kotlin-parcelize")
|
||||
// To be able to update the firebase.xml files, uncomment and build the project
|
||||
@@ -43,10 +45,6 @@ android {
|
||||
versionCode = Versions.versionCode
|
||||
versionName = Versions.versionName
|
||||
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
|
||||
// Keep abiFilter for the universalApk
|
||||
ndk {
|
||||
abiFilters += listOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64")
|
||||
@@ -194,6 +192,26 @@ knit {
|
||||
}
|
||||
}
|
||||
|
||||
val ciBuildProperty = "ci-build"
|
||||
val isCiBuild = if (project.hasProperty(ciBuildProperty)) {
|
||||
val raw = project.property(ciBuildProperty) as? String
|
||||
raw?.toBooleanLenient() == true || raw?.toIntOrNull() == 1
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
kover {
|
||||
// When running on the CI, run only debug test variants
|
||||
if (isCiBuild) {
|
||||
excludeTests {
|
||||
// Disable instrumentation for debug test tasks
|
||||
tasks(
|
||||
"testDebugUnitTest",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
allLibrariesImpl()
|
||||
allServicesImpl()
|
||||
|
||||
8
app/proguard-rules.pro
vendored
8
app/proguard-rules.pro
vendored
@@ -10,6 +10,9 @@
|
||||
-keep class com.sun.jna.** { *; }
|
||||
-keep class * implements com.sun.jna.** { *; }
|
||||
|
||||
# TagSoup, coming from the RTE library
|
||||
-keep class org.ccil.cowan.tagsoup.** { *; }
|
||||
|
||||
# kotlinx.serialization
|
||||
|
||||
# Kotlin serialization looks up the generated serializer classes through a function on companion
|
||||
@@ -32,3 +35,8 @@
|
||||
-dontwarn org.conscrypt.**
|
||||
-dontwarn org.bouncycastle.**
|
||||
-dontwarn org.openjsse.**
|
||||
|
||||
# Needed for Posthog
|
||||
-keepclassmembers class android.view.JavaViewSpy {
|
||||
static int windowAttachCount(android.view.View);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import io.element.android.x.initializer.CrashInitializer
|
||||
import io.element.android.x.initializer.TracingInitializer
|
||||
|
||||
class ElementXApplication : Application(), DaggerComponentOwner {
|
||||
|
||||
override val daggerComponent: AppComponent = DaggerAppComponent.factory().create(this)
|
||||
|
||||
override fun onCreate() {
|
||||
|
||||
@@ -51,7 +51,6 @@ import timber.log.Timber
|
||||
private val loggerTag = LoggerTag("MainActivity")
|
||||
|
||||
class MainActivity : NodeActivity() {
|
||||
|
||||
private lateinit var mainNode: MainNode
|
||||
private lateinit var appBindings: AppBindings
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ class MainNode(
|
||||
plugins = plugins,
|
||||
),
|
||||
DaggerComponentOwner {
|
||||
|
||||
override val daggerComponent = (context as DaggerComponentOwner).daggerComponent
|
||||
|
||||
override fun resolve(navTarget: RootNavTarget, buildContext: BuildContext): Node {
|
||||
@@ -68,5 +67,4 @@ class MainNode(
|
||||
|
||||
@Parcelize
|
||||
object RootNavTarget : Parcelable
|
||||
|
||||
}
|
||||
|
||||
@@ -27,8 +27,12 @@ import io.element.android.libraries.matrix.api.tracing.TracingService
|
||||
@ContributesTo(AppScope::class)
|
||||
interface AppBindings {
|
||||
fun snackbarDispatcher(): SnackbarDispatcher
|
||||
|
||||
fun tracingService(): TracingService
|
||||
|
||||
fun bugReporter(): BugReporter
|
||||
|
||||
fun lockScreenService(): LockScreenService
|
||||
|
||||
fun preferencesStore(): PreferencesStore
|
||||
}
|
||||
|
||||
@@ -28,9 +28,11 @@ import io.element.android.libraries.di.SingleIn
|
||||
@SingleIn(AppScope::class)
|
||||
@MergeComponent(AppScope::class)
|
||||
interface AppComponent : NodeFactoriesBindings {
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(@ApplicationContext @BindsInstance context: Context): AppComponent
|
||||
fun create(
|
||||
@ApplicationContext @BindsInstance
|
||||
context: Context
|
||||
): AppComponent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ import java.io.File
|
||||
@Module
|
||||
@ContributesTo(AppScope::class)
|
||||
object AppModule {
|
||||
|
||||
@Provides
|
||||
fun providesBaseDirectory(@ApplicationContext context: Context): File {
|
||||
return File(context.filesDir, "sessions")
|
||||
@@ -82,14 +81,20 @@ object AppModule {
|
||||
buildType = buildType,
|
||||
applicationName = context.getString(R.string.app_name),
|
||||
applicationId = BuildConfig.APPLICATION_ID,
|
||||
lowPrivacyLoggingEnabled = false, // TODO EAx Config.LOW_PRIVACY_LOG_ENABLE,
|
||||
// TODO EAx Config.LOW_PRIVACY_LOG_ENABLE,
|
||||
lowPrivacyLoggingEnabled = false,
|
||||
versionName = BuildConfig.VERSION_NAME,
|
||||
versionCode = BuildConfig.VERSION_CODE,
|
||||
gitRevision = "TODO", // BuildConfig.GIT_REVISION,
|
||||
gitRevisionDate = "TODO", // BuildConfig.GIT_REVISION_DATE,
|
||||
gitBranchName = "TODO", // BuildConfig.GIT_BRANCH_NAME,
|
||||
flavorDescription = "TODO", // BuildConfig.FLAVOR_DESCRIPTION,
|
||||
flavorShortDescription = "TODO", // BuildConfig.SHORT_FLAVOR_DESCRIPTION,
|
||||
// BuildConfig.GIT_REVISION,
|
||||
gitRevision = "TODO",
|
||||
// BuildConfig.GIT_REVISION_DATE,
|
||||
gitRevisionDate = "TODO",
|
||||
// BuildConfig.GIT_BRANCH_NAME,
|
||||
gitBranchName = "TODO",
|
||||
// BuildConfig.FLAVOR_DESCRIPTION,
|
||||
flavorDescription = "TODO",
|
||||
// BuildConfig.SHORT_FLAVOR_DESCRIPTION,
|
||||
flavorShortDescription = "TODO",
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -26,7 +26,6 @@ import javax.inject.Inject
|
||||
class DefaultRoomComponentFactory @Inject constructor(
|
||||
private val roomComponentBuilder: RoomComponent.Builder
|
||||
) : RoomComponentFactory {
|
||||
|
||||
override fun create(room: MatrixRoom): Any {
|
||||
return roomComponentBuilder.room(room).build()
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import javax.inject.Inject
|
||||
class DefaultSessionComponentFactory @Inject constructor(
|
||||
private val sessionComponentBuilder: SessionComponent.Builder
|
||||
) : SessionComponentFactory {
|
||||
|
||||
override fun create(client: MatrixClient): Any {
|
||||
return sessionComponentBuilder.client(client).build()
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
@SingleIn(RoomScope::class)
|
||||
@MergeSubcomponent(RoomScope::class)
|
||||
interface RoomComponent : NodeFactoriesBindings {
|
||||
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
@BindsInstance
|
||||
|
||||
@@ -29,7 +29,6 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||
@SingleIn(SessionScope::class)
|
||||
@MergeSubcomponent(SessionScope::class)
|
||||
interface SessionComponent : NodeFactoriesBindings {
|
||||
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
@BindsInstance
|
||||
|
||||
@@ -21,7 +21,6 @@ import androidx.startup.Initializer
|
||||
import io.element.android.features.rageshake.impl.crash.VectorUncaughtExceptionHandler
|
||||
|
||||
class CrashInitializer : Initializer<Unit> {
|
||||
|
||||
override fun create(context: Context) {
|
||||
VectorUncaughtExceptionHandler(context).activate()
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import io.element.android.x.di.AppBindings
|
||||
import timber.log.Timber
|
||||
|
||||
class TracingInitializer : Initializer<Unit> {
|
||||
|
||||
override fun create(context: Context) {
|
||||
val appBindings = context.bindings<AppBindings>()
|
||||
val tracingService = appBindings.tracingService()
|
||||
|
||||
@@ -31,7 +31,6 @@ import org.robolectric.RuntimeEnvironment
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class IntentProviderImplTest {
|
||||
|
||||
@Test
|
||||
fun `test getViewRoomIntent with Session`() {
|
||||
val sut = createIntentProviderImpl()
|
||||
|
||||
@@ -25,48 +25,27 @@ import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
* Configuration for the lock screen feature.
|
||||
* @property isPinMandatory Whether the PIN is mandatory or not.
|
||||
* @property pinBlacklist Some PINs are forbidden.
|
||||
* @property pinSize The size of the PIN.
|
||||
* @property maxPinCodeAttemptsBeforeLogout Number of attempts before the user is logged out.
|
||||
* @property gracePeriod Time period before locking the app once backgrounded.
|
||||
* @property isStrongBiometricsEnabled Authentication with strong methods (fingerprint, some face/iris unlock implementations) is supported.
|
||||
* @property isWeakBiometricsEnabled Authentication with weak methods (most face/iris unlock implementations) is supported.
|
||||
*/
|
||||
data class LockScreenConfig(
|
||||
/**
|
||||
* Whether the PIN is mandatory or not.
|
||||
*/
|
||||
val isPinMandatory: Boolean,
|
||||
|
||||
/**
|
||||
* Some PINs are forbidden.
|
||||
*/
|
||||
val pinBlacklist: Set<String>,
|
||||
|
||||
/**
|
||||
* The size of the PIN.
|
||||
*/
|
||||
val pinSize: Int,
|
||||
|
||||
/**
|
||||
* Number of attempts before the user is logged out.
|
||||
*/
|
||||
val maxPinCodeAttemptsBeforeLogout: Int,
|
||||
|
||||
/**
|
||||
* Time period before locking the app once backgrounded.
|
||||
*/
|
||||
val gracePeriod: Duration,
|
||||
|
||||
/**
|
||||
* Authentication with strong methods (fingerprint, some face/iris unlock implementations) is supported.
|
||||
*/
|
||||
val isStrongBiometricsEnabled: Boolean,
|
||||
|
||||
/**
|
||||
* Authentication with weak methods (most face/iris unlock implementations) is supported.
|
||||
*/
|
||||
val isWeakBiometricsEnabled: Boolean,
|
||||
)
|
||||
|
||||
@ContributesTo(AppScope::class)
|
||||
@Module
|
||||
object LockScreenConfigModule {
|
||||
|
||||
@Provides
|
||||
fun providesLockScreenConfig(): LockScreenConfig = LockScreenConfig(
|
||||
isPinMandatory = false,
|
||||
|
||||
@@ -17,6 +17,6 @@
|
||||
package io.element.android.appconfig
|
||||
|
||||
object MatrixConfiguration {
|
||||
const val matrixToPermalinkBaseUrl: String = "https://matrix.to/#/"
|
||||
const val MATRIX_TO_PERMALINK_BASE_URL: String = "https://matrix.to/#/"
|
||||
val clientPermalinkBaseUrl: String? = null
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ package io.element.android.appconfig
|
||||
|
||||
object NotificationConfig {
|
||||
// TODO EAx Implement and set to true at some point
|
||||
const val supportMarkAsReadAction = false
|
||||
const val SUPPORT_MARK_AS_READ_ACTION = false
|
||||
|
||||
// TODO EAx Implement and set to true at some point
|
||||
const val supportQuickReplyAction = false
|
||||
const val SUPPORT_QUICK_REPLY_ACTION = false
|
||||
}
|
||||
|
||||
@@ -20,5 +20,5 @@ object PushConfig {
|
||||
/**
|
||||
* Note: pusher_app_id cannot exceed 64 chars.
|
||||
*/
|
||||
const val pusher_app_id: String = "im.vector.app.android"
|
||||
const val PUSHER_APP_ID: String = "im.vector.app.android"
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
package io.element.android.appconfig
|
||||
|
||||
object RoomListConfig {
|
||||
const val showInviteMenuItem = false
|
||||
const val showReportProblemMenuItem = false
|
||||
const val SHOW_INVITE_MENU_ITEM = false
|
||||
const val SHOW_REPORT_PROBLEM_MENU_ITEM = false
|
||||
|
||||
const val hasDropdownMenu = showInviteMenuItem || showReportProblemMenuItem
|
||||
const val HAS_DROP_DOWN_MENU = SHOW_INVITE_MENU_ITEM || SHOW_REPORT_PROBLEM_MENU_ITEM
|
||||
}
|
||||
|
||||
@@ -17,5 +17,5 @@
|
||||
package io.element.android.appconfig
|
||||
|
||||
object SecureBackupConfig {
|
||||
const val LearnMoreUrl: String = "https://element.io/help#encryption5"
|
||||
const val LEARN_MORE_URL: String = "https://element.io/help#encryption5"
|
||||
}
|
||||
|
||||
@@ -17,5 +17,5 @@
|
||||
package io.element.android.appconfig
|
||||
|
||||
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
|
||||
accept(Remove(lastExpectedNavElement.key))
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ class LoggedInEventProcessor @Inject constructor(
|
||||
roomMembershipObserver: RoomMembershipObserver,
|
||||
sessionVerificationService: SessionVerificationService,
|
||||
) {
|
||||
|
||||
private var observingJob: Job? = null
|
||||
|
||||
private val displayLeftRoomMessage = roomMembershipObserver.updates
|
||||
|
||||
@@ -111,7 +111,6 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins
|
||||
) {
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun onOpenBugReport()
|
||||
}
|
||||
@@ -138,7 +137,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||
},
|
||||
onStop = {
|
||||
coroutineScope.launch {
|
||||
//Counterpart startSync is done in observeSyncStateAndNetworkStatus method.
|
||||
// Counterpart startSync is done in observeSyncStateAndNetworkStatus method.
|
||||
syncService.stopSync()
|
||||
}
|
||||
},
|
||||
|
||||
@@ -81,7 +81,6 @@ class RootFlowNode @AssistedInject constructor(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins
|
||||
) {
|
||||
|
||||
override fun onBuilt() {
|
||||
matrixClientsHolder.restoreWithSavedState(buildContext.savedStateMap)
|
||||
super.onBuilt()
|
||||
@@ -268,10 +267,9 @@ class RootFlowNode @AssistedInject constructor(
|
||||
}
|
||||
|
||||
private suspend fun attachSession(sessionId: SessionId): LoggedInAppScopeFlowNode {
|
||||
//TODO handle multi-session
|
||||
// TODO handle multi-session
|
||||
return waitForChildAttached { navTarget ->
|
||||
navTarget is NavTarget.LoggedInFlow && navTarget.sessionId == sessionId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ private const val SAVE_INSTANCE_KEY = "io.element.android.x.di.MatrixClientsHold
|
||||
@SingleIn(AppScope::class)
|
||||
@ContributesBinding(AppScope::class)
|
||||
class MatrixClientsHolder @Inject constructor(private val authenticationService: MatrixAuthenticationService) : MatrixClientProvider {
|
||||
|
||||
private val sessionIdsToMatrixClient = ConcurrentHashMap<SessionId, MatrixClient>()
|
||||
private val restoreMutex = Mutex()
|
||||
|
||||
@@ -65,8 +64,9 @@ class MatrixClientsHolder @Inject constructor(private val authenticationService:
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun restoreWithSavedState(state: SavedStateMap?) {
|
||||
Timber.d("Restore state")
|
||||
if (state == null || sessionIdsToMatrixClient.isNotEmpty()) return Unit.also {
|
||||
if (state == null || sessionIdsToMatrixClient.isNotEmpty()) {
|
||||
Timber.w("Restore with non-empty map")
|
||||
return
|
||||
}
|
||||
val sessionIds = state[SAVE_INSTANCE_KEY] as? Array<SessionId>
|
||||
Timber.d("Restore matrix session keys = ${sessionIds?.map { it.value }}")
|
||||
|
||||
@@ -35,7 +35,6 @@ class LoggedInNode @AssistedInject constructor(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins
|
||||
) {
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val loggedInState = loggedInPresenter.present()
|
||||
|
||||
@@ -35,7 +35,6 @@ class LoggedInPresenter @Inject constructor(
|
||||
private val networkMonitor: NetworkMonitor,
|
||||
private val pushService: PushService,
|
||||
) : Presenter<LoggedInState> {
|
||||
|
||||
@Composable
|
||||
override fun present(): LoggedInState {
|
||||
LaunchedEffect(Unit) {
|
||||
|
||||
@@ -59,15 +59,15 @@ fun SyncStateView(
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(color = ElementTheme.colors.bgSubtleSecondary)
|
||||
.padding(horizontal = 24.dp, vertical = 10.dp),
|
||||
.background(color = ElementTheme.colors.bgSubtleSecondary)
|
||||
.padding(horizontal = 24.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.progressSemantics()
|
||||
.size(12.dp),
|
||||
.progressSemantics()
|
||||
.size(12.dp),
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
strokeWidth = 1.5.dp,
|
||||
)
|
||||
|
||||
@@ -18,7 +18,6 @@ package io.element.android.appnav.room
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -43,7 +42,6 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun LoadingRoomNodeView(
|
||||
state: LoadingRoomState,
|
||||
|
||||
@@ -47,7 +47,6 @@ open class LoadingRoomStateProvider : PreviewParameterProvider<LoadingRoomState>
|
||||
|
||||
@SingleIn(SessionScope::class)
|
||||
class LoadingRoomStateFlowFactory @Inject constructor(private val matrixClient: MatrixClient) {
|
||||
|
||||
fun create(lifecycleScope: CoroutineScope, roomId: RoomId): StateFlow<LoadingRoomState> =
|
||||
getRoomFlow(roomId)
|
||||
.map { room ->
|
||||
|
||||
@@ -26,6 +26,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.navigation.transition.JumpToEndTransitionHandler
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.node.node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
@@ -65,7 +66,6 @@ class RoomFlowNode @AssistedInject constructor(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins
|
||||
) {
|
||||
|
||||
data class Inputs(
|
||||
val roomId: RoomId,
|
||||
val initialElement: RoomLoadedFlowNode.NavTarget = RoomLoadedFlowNode.NavTarget.Messages,
|
||||
@@ -130,7 +130,8 @@ class RoomFlowNode @AssistedInject constructor(
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
BackstackView()
|
||||
BackstackView(
|
||||
transitionHandler = JumpToEndTransitionHandler(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,6 @@ class RoomLoadedFlowNode @AssistedInject constructor(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins,
|
||||
), DaggerComponentOwner {
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun onOpenRoom(roomId: RoomId)
|
||||
fun onForwardedToSingleRoom(roomId: RoomId)
|
||||
|
||||
@@ -41,7 +41,6 @@ class RootNavStateFlowFactory @Inject constructor(
|
||||
private val matrixClientsHolder: MatrixClientsHolder,
|
||||
private val loginUserStory: LoginUserStory,
|
||||
) {
|
||||
|
||||
private var currentCacheIndex = 0
|
||||
|
||||
fun create(savedStateMap: SavedStateMap?): Flow<RootNavState> {
|
||||
|
||||
@@ -30,7 +30,6 @@ class RootPresenter @Inject constructor(
|
||||
private val rageshakeDetectionPresenter: RageshakeDetectionPresenter,
|
||||
private val appErrorStateService: AppErrorStateService,
|
||||
) : Presenter<RootState> {
|
||||
|
||||
@Composable
|
||||
override fun present(): RootState {
|
||||
val rageshakeDetectionState = rageshakeDetectionPresenter.present()
|
||||
|
||||
@@ -41,7 +41,6 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class RoomFlowNodeTest {
|
||||
|
||||
@get:Rule
|
||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
@@ -49,7 +48,6 @@ class RoomFlowNodeTest {
|
||||
val mainDispatcherRule = MainDispatcherRule()
|
||||
|
||||
private class FakeMessagesEntryPoint : MessagesEntryPoint {
|
||||
|
||||
var nodeId: String? = null
|
||||
var callback: MessagesEntryPoint.Callback? = null
|
||||
|
||||
@@ -68,12 +66,10 @@ class RoomFlowNodeTest {
|
||||
}
|
||||
|
||||
private class FakeRoomDetailsEntryPoint : RoomDetailsEntryPoint {
|
||||
|
||||
var nodeId: String? = null
|
||||
|
||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomDetailsEntryPoint.NodeBuilder {
|
||||
return object : RoomDetailsEntryPoint.NodeBuilder {
|
||||
|
||||
override fun params(params: RoomDetailsEntryPoint.Params): RoomDetailsEntryPoint.NodeBuilder {
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class LoggedInPresenterTest {
|
||||
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
|
||||
@@ -28,25 +28,24 @@ import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class LoadingRoomStateFlowFactoryTest {
|
||||
|
||||
@Test
|
||||
fun `flow should emit Loading and then Loaded when there is a room in cache`() = runTest {
|
||||
val room = FakeMatrixRoom(sessionId= A_SESSION_ID, roomId = A_ROOM_ID)
|
||||
val room = FakeMatrixRoom(sessionId = A_SESSION_ID, roomId = A_ROOM_ID)
|
||||
val matrixClient = FakeMatrixClient(A_SESSION_ID).apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
||||
flowFactory
|
||||
.create(this, A_ROOM_ID)
|
||||
.test {
|
||||
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
||||
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room))
|
||||
}
|
||||
.test {
|
||||
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
|
||||
assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow should emit Loading and then Loaded when there is a room in cache after SS is loaded`() = runTest {
|
||||
val room = FakeMatrixRoom(sessionId= A_SESSION_ID, roomId = A_ROOM_ID)
|
||||
val room = FakeMatrixRoom(sessionId = A_SESSION_ID, roomId = A_ROOM_ID)
|
||||
val roomListService = FakeRoomListService()
|
||||
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService)
|
||||
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
||||
|
||||
203
build.gradle.kts
203
build.gradle.kts
@@ -1,7 +1,5 @@
|
||||
import com.google.devtools.ksp.gradle.KspTask
|
||||
import kotlinx.kover.api.KoverTaskExtension
|
||||
import org.apache.tools.ant.taskdefs.optional.ReplaceRegExp
|
||||
import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
@@ -28,6 +26,7 @@ buildscript {
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("io.element.android-root")
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.android.library) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
@@ -41,7 +40,6 @@ plugins {
|
||||
alias(libs.plugins.ktlint)
|
||||
alias(libs.plugins.dependencygraph)
|
||||
alias(libs.plugins.sonarqube)
|
||||
alias(libs.plugins.kover)
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean").configure {
|
||||
@@ -62,7 +60,7 @@ allprojects {
|
||||
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
||||
}
|
||||
dependencies {
|
||||
detektPlugins("io.nlopez.compose.rules:detekt:0.3.8")
|
||||
detektPlugins("io.nlopez.compose.rules:detekt:0.3.9")
|
||||
}
|
||||
|
||||
// KtLint
|
||||
@@ -74,7 +72,7 @@ allprojects {
|
||||
configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
|
||||
// See https://github.com/pinterest/ktlint/releases/
|
||||
// TODO Regularly check for new version here ^
|
||||
version.set("0.48.2")
|
||||
version.set("1.1.1")
|
||||
android.set(true)
|
||||
ignoreFailures.set(false)
|
||||
enableExperimentalRules.set(true)
|
||||
@@ -100,11 +98,16 @@ allprojects {
|
||||
// You can override by passing `-PallWarningsAsErrors=true` in the command line
|
||||
// Or add a line with "allWarningsAsErrors=true" in your ~/.gradle/gradle.properties file
|
||||
kotlinOptions.allWarningsAsErrors = project.properties["allWarningsAsErrors"] == "true"
|
||||
}
|
||||
|
||||
// Detect unused dependencies
|
||||
apply {
|
||||
plugin("com.autonomousapps.dependency-analysis")
|
||||
kotlinOptions {
|
||||
/*
|
||||
// Uncomment to suppress Compose Kotlin compiler compatibility warning
|
||||
freeCompilerArgs += listOf(
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"
|
||||
)
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,180 +161,6 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
apply(plugin = "kover")
|
||||
}
|
||||
|
||||
// https://kotlin.github.io/kotlinx-kover/
|
||||
// Run `./gradlew koverMergedHtmlReport` to get report at ./build/reports/kover
|
||||
// Run `./gradlew koverMergedReport` to also get XML report
|
||||
koverMerged {
|
||||
enable()
|
||||
|
||||
filters {
|
||||
classes {
|
||||
excludes.addAll(
|
||||
listOf(
|
||||
// Exclude generated classes.
|
||||
"*_ModuleKt",
|
||||
"anvil.hint.binding.io.element.*",
|
||||
"anvil.hint.merge.*",
|
||||
"anvil.hint.multibinding.io.element.*",
|
||||
"anvil.module.*",
|
||||
"com.airbnb.android.showkase*",
|
||||
"io.element.android.libraries.designsystem.showkase.*",
|
||||
"io.element.android.x.di.DaggerAppComponent*",
|
||||
"*_Factory",
|
||||
"*_Factory_Impl",
|
||||
"*_Factory$*",
|
||||
"*_Module",
|
||||
"*_Module$*",
|
||||
"*Module_Provides*",
|
||||
"Dagger*Component*",
|
||||
"*ComposableSingletons$*",
|
||||
"*_AssistedFactory_Impl*",
|
||||
"*BuildConfig",
|
||||
// Generated by Showkase
|
||||
"*Ioelementandroid*PreviewKt$*",
|
||||
"*Ioelementandroid*PreviewKt",
|
||||
// Other
|
||||
// We do not cover Nodes (normally covered by maestro, but code coverage is not computed with maestro)
|
||||
"*Node",
|
||||
"*Node$*",
|
||||
// Exclude `:libraries:matrix:impl` module, it contains only wrappers to access the Rust Matrix SDK api, so it is not really relevant to unit test it: there is no logic to test.
|
||||
"io.element.android.libraries.matrix.impl.*",
|
||||
"*Presenter\$present\$*",
|
||||
// Forked from compose
|
||||
"io.element.android.libraries.designsystem.theme.components.bottomsheet.*",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
annotations {
|
||||
excludes.addAll(
|
||||
listOf(
|
||||
"*Preview",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
projects {
|
||||
excludes.addAll(
|
||||
listOf(
|
||||
":anvilannotations",
|
||||
":anvilcodegen",
|
||||
":samples:minimal",
|
||||
":tests:testutils",
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Run ./gradlew koverMergedVerify to check the rules.
|
||||
verify {
|
||||
// Does not seems to work, so also run the task manually on the workflow.
|
||||
onCheck.set(true)
|
||||
// General rule: minimum code coverage.
|
||||
rule {
|
||||
name = "Global minimum code coverage."
|
||||
target = kotlinx.kover.api.VerificationTarget.ALL
|
||||
bound {
|
||||
minValue = 65
|
||||
// Setting a max value, so that if coverage is bigger, it means that we have to change minValue.
|
||||
// For instance if we have minValue = 20 and maxValue = 30, and current code coverage is now 31.32%, update
|
||||
// minValue to 25 and maxValue to 35.
|
||||
maxValue = 75
|
||||
counter = kotlinx.kover.api.CounterType.INSTRUCTION
|
||||
valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE
|
||||
}
|
||||
}
|
||||
// Rule to ensure that coverage of Presenters is sufficient.
|
||||
rule {
|
||||
name = "Check code coverage of presenters"
|
||||
target = kotlinx.kover.api.VerificationTarget.CLASS
|
||||
overrideClassFilter {
|
||||
includes += "*Presenter"
|
||||
excludes += "*Fake*Presenter"
|
||||
excludes += "io.element.android.appnav.loggedin.LoggedInPresenter$*"
|
||||
// Some options can't be tested at the moment
|
||||
excludes += "io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*"
|
||||
excludes += "*Presenter\$present\$*"
|
||||
}
|
||||
bound {
|
||||
minValue = 85
|
||||
counter = kotlinx.kover.api.CounterType.INSTRUCTION
|
||||
valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE
|
||||
}
|
||||
}
|
||||
// Rule to ensure that coverage of States is sufficient.
|
||||
rule {
|
||||
name = "Check code coverage of states"
|
||||
target = kotlinx.kover.api.VerificationTarget.CLASS
|
||||
overrideClassFilter {
|
||||
includes += "^*State$"
|
||||
excludes += "io.element.android.appnav.root.RootNavState*"
|
||||
excludes += "io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*"
|
||||
excludes += "io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*"
|
||||
excludes += "io.element.android.libraries.matrix.api.room.RoomMembershipState*"
|
||||
excludes += "io.element.android.libraries.matrix.api.room.MatrixRoomMembersState*"
|
||||
excludes += "io.element.android.libraries.push.impl.notifications.NotificationState*"
|
||||
excludes += "io.element.android.features.messages.impl.media.local.pdf.PdfViewerState"
|
||||
excludes += "io.element.android.features.messages.impl.media.local.LocalMediaViewState"
|
||||
excludes += "io.element.android.features.location.impl.map.MapState*"
|
||||
excludes += "io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*"
|
||||
excludes += "io.element.android.libraries.designsystem.swipe.SwipeableActionsState*"
|
||||
excludes += "io.element.android.features.messages.impl.timeline.components.ExpandableState*"
|
||||
excludes += "io.element.android.features.messages.impl.timeline.model.bubble.BubbleState*"
|
||||
excludes += "io.element.android.libraries.maplibre.compose.CameraPositionState*"
|
||||
excludes += "io.element.android.libraries.maplibre.compose.SaveableCameraPositionState"
|
||||
excludes += "io.element.android.libraries.maplibre.compose.SymbolState*"
|
||||
excludes += "io.element.android.features.ftue.api.state.*"
|
||||
excludes += "io.element.android.features.ftue.impl.welcome.state.*"
|
||||
}
|
||||
bound {
|
||||
minValue = 90
|
||||
counter = kotlinx.kover.api.CounterType.INSTRUCTION
|
||||
valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE
|
||||
}
|
||||
}
|
||||
// Rule to ensure that coverage of Views is sufficient (deactivated for now).
|
||||
rule {
|
||||
name = "Check code coverage of views"
|
||||
target = kotlinx.kover.api.VerificationTarget.CLASS
|
||||
overrideClassFilter {
|
||||
includes += "*ViewKt"
|
||||
}
|
||||
bound {
|
||||
// TODO Update this value, for now there are too many missing tests.
|
||||
minValue = 0
|
||||
counter = kotlinx.kover.api.CounterType.INSTRUCTION
|
||||
valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When running on the CI, run only debug test variants
|
||||
val ciBuildProperty = "ci-build"
|
||||
val isCiBuild = if (project.hasProperty(ciBuildProperty)) {
|
||||
val raw = project.property(ciBuildProperty) as? String
|
||||
raw?.toBooleanLenient() == true || raw?.toIntOrNull() == 1
|
||||
} else {
|
||||
false
|
||||
}
|
||||
if (isCiBuild) {
|
||||
allprojects {
|
||||
afterEvaluate {
|
||||
tasks.withType<Test>().configureEach {
|
||||
extensions.configure<KoverTaskExtension> {
|
||||
val enabled = name.contains("debug", ignoreCase = true)
|
||||
isDisabled.set(!enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register quality check tasks.
|
||||
tasks.register("runQualityChecks") {
|
||||
project.subprojects {
|
||||
@@ -361,7 +190,7 @@ subprojects {
|
||||
|
||||
// Workaround for https://github.com/airbnb/Showkase/issues/335
|
||||
subprojects {
|
||||
tasks.withType<KspTask>() {
|
||||
tasks.withType<KspTask> {
|
||||
doLast {
|
||||
fileTree(layout.buildDirectory).apply { include("**/*ShowkaseExtension*.kt") }.files.forEach { file ->
|
||||
ReplaceRegExp().apply {
|
||||
@@ -383,13 +212,15 @@ subprojects {
|
||||
if (project.findProperty("composeCompilerReports") == "true") {
|
||||
freeCompilerArgs += listOf(
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${project.layout.buildDirectory.asFile.get().absolutePath}/compose_compiler"
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
||||
"${project.layout.buildDirectory.asFile.get().absolutePath}/compose_compiler"
|
||||
)
|
||||
}
|
||||
if (project.findProperty("composeCompilerMetrics") == "true") {
|
||||
freeCompilerArgs += listOf(
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=${project.layout.buildDirectory.asFile.get().absolutePath}/compose_compiler"
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
||||
"${project.layout.buildDirectory.asFile.get().absolutePath}/compose_compiler"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,26 +344,26 @@ implementation of our interfaces. Mocking can be used to mock Android framework
|
||||
[kover](https://github.com/Kotlin/kotlinx-kover) is used to compute code coverage. Only have unit tests can produce code coverage result. Running Maestro does
|
||||
not participate to the code coverage results.
|
||||
|
||||
Kover configuration is defined in the main [build.gradle.kts](../build.gradle.kts) file.
|
||||
Kover configuration is defined in the app [build.gradle.kts](../app/build.gradle.kts) file.
|
||||
|
||||
To compute the code coverage, run:
|
||||
|
||||
```bash
|
||||
./gradlew koverMergedReport
|
||||
./gradlew :app:koverHtmlReport
|
||||
```
|
||||
|
||||
and open the Html report: [../build/reports/kover/merged/html/index.html](../build/reports/kover/merged/html/index.html)
|
||||
and open the Html report: [../app/build/reports/kover/html/index.html](../app/build/reports/kover/html/index.html)
|
||||
|
||||
To ensure that the code coverage threshold are OK, you can run
|
||||
|
||||
```bash
|
||||
./gradlew koverMergedVerify
|
||||
./gradlew :app:koverVerify
|
||||
```
|
||||
|
||||
Note that the CI performs this check on every pull requests.
|
||||
|
||||
Also, if the rule `Global minimum code coverage.` is in error because code coverage is `> maxValue`, `minValue` and `maxValue` can be updated for this rule in
|
||||
the file [build.gradle.kts](../build.gradle.kts) (you will see further instructions there).
|
||||
the file [build.gradle.kts](../app/build.gradle.kts) (you will see further instructions there).
|
||||
|
||||
### Other points
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* [Stop Synapse](#stop-synapse)
|
||||
* [Troubleshoot](#troubleshoot)
|
||||
* [Android Emulator does cannot reach the homeserver](#android-emulator-does-cannot-reach-the-homeserver)
|
||||
* [Tests partially run but some fail with "Unable to contact localhost:8080"](#tests-partially-run-but-some-fail-with-"unable-to-contact-localhost:8080")
|
||||
* [Tests partially run but some fail with "Unable to contact localhost:8080"](#tests-partially-run-but-some-fail-with-"unable-to-contact-localhost8080")
|
||||
* [virtualenv command fails](#virtualenv-command-fails)
|
||||
|
||||
<!--- 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.libraries.designsystem.components.LINK_TAG
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.ListSupportingText
|
||||
|
||||
@@ -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"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_settings_share_data">"分享分析數據"</string>
|
||||
<string name="screen_analytics_settings_help_us_improve">"分享匿名的使用數據以協助我們釐清問題。"</string>
|
||||
<string name="screen_analytics_settings_share_data">"提供分析數據"</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_content_link">"這裡"</string>
|
||||
</resources>
|
||||
|
||||
@@ -37,7 +37,6 @@ class AnalyticsOptInNode @AssistedInject constructor(
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: AnalyticsOptInPresenter,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
|
||||
private fun onClickTerms(activity: Activity, darkTheme: Boolean) {
|
||||
activity.openUrlInChromeCustomTab(null, darkTheme, AnalyticsConfig.POLICY_LINK)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ class AnalyticsOptInPresenter @Inject constructor(
|
||||
private val buildMeta: BuildMeta,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : Presenter<AnalyticsOptInState> {
|
||||
|
||||
@Composable
|
||||
override fun present(): AnalyticsOptInState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
|
||||
@@ -19,8 +19,7 @@ package io.element.android.features.analytics.impl
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
open class AnalyticsOptInStateProvider @Inject constructor(
|
||||
) : PreviewParameterProvider<AnalyticsOptInState> {
|
||||
open class AnalyticsOptInStateProvider @Inject constructor() : PreviewParameterProvider<AnalyticsOptInState> {
|
||||
override val values: Sequence<AnalyticsOptInState>
|
||||
get() = sequenceOf(
|
||||
aAnalyticsOptInState(),
|
||||
|
||||
@@ -36,7 +36,6 @@ class DefaultAnalyticsPreferencesPresenter @Inject constructor(
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val buildMeta: BuildMeta,
|
||||
) : AnalyticsPreferencesPresenter {
|
||||
|
||||
@Composable
|
||||
override fun present(): AnalyticsPreferencesState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
|
||||
@@ -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"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<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_content_link">"這裡"</string>
|
||||
<string name="screen_analytics_prompt_settings">"您可以在任何時候關閉它"</string>
|
||||
|
||||
@@ -30,7 +30,6 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class AnalyticsOptInPresenterTest {
|
||||
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@@ -70,4 +69,3 @@ class AnalyticsOptInPresenterTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class AnalyticsPreferencesPresenterTest {
|
||||
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@@ -82,4 +81,3 @@ class AnalyticsPreferencesPresenterTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ import io.element.android.features.call.ui.ElementCallActivity
|
||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||
|
||||
class CallForegroundService : Service() {
|
||||
|
||||
companion object {
|
||||
fun start(context: Context) {
|
||||
val intent = Intent(context, CallForegroundService::class.java)
|
||||
|
||||
@@ -28,11 +28,11 @@ data class WidgetMessage(
|
||||
@SerialName("action") val action: Action,
|
||||
@SerialName("data") val data: JsonElement? = null,
|
||||
) {
|
||||
|
||||
@Serializable
|
||||
enum class Direction {
|
||||
@SerialName("fromWidget")
|
||||
FromWidget,
|
||||
|
||||
@SerialName("toWidget")
|
||||
ToWidget
|
||||
}
|
||||
@@ -41,6 +41,7 @@ data class WidgetMessage(
|
||||
enum class Action {
|
||||
@SerialName("im.vector.hangup")
|
||||
HangUp,
|
||||
|
||||
@SerialName("send_event")
|
||||
SendEvent,
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import io.element.android.features.call.data.WidgetMessage
|
||||
import io.element.android.features.call.utils.CallWidgetProvider
|
||||
import io.element.android.features.call.utils.WidgetMessageInterceptor
|
||||
import io.element.android.features.call.utils.WidgetMessageSerializer
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
@@ -63,7 +63,6 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||
private val matrixClientsProvider: MatrixClientProvider,
|
||||
private val appCoroutineScope: CoroutineScope,
|
||||
) : Presenter<CallScreenState> {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(callType: CallType, navigator: CallScreenNavigator): CallScreenPresenter
|
||||
@@ -75,7 +74,7 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||
@Composable
|
||||
override fun present(): CallScreenState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val urlState = remember { mutableStateOf<Async<String>>(Async.Uninitialized) }
|
||||
val urlState = remember { mutableStateOf<AsyncData<String>>(AsyncData.Uninitialized) }
|
||||
val callWidgetDriver = remember { mutableStateOf<MatrixWidgetDriver?>(null) }
|
||||
val messageInterceptor = remember { mutableStateOf<WidgetMessageInterceptor?>(null) }
|
||||
var isJoinedCall by rememberSaveable { mutableStateOf(false) }
|
||||
@@ -154,7 +153,7 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||
|
||||
private fun CoroutineScope.loadUrl(
|
||||
inputs: CallType,
|
||||
urlState: MutableState<Async<String>>,
|
||||
urlState: MutableState<AsyncData<String>>,
|
||||
callWidgetDriver: MutableState<MatrixWidgetDriver?>,
|
||||
) = launch {
|
||||
urlState.runCatchingUpdatingState {
|
||||
@@ -224,6 +223,4 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||
navigator.close()
|
||||
widgetDriver?.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
package io.element.android.features.call.ui
|
||||
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
|
||||
data class CallScreenState(
|
||||
val urlState: Async<String>,
|
||||
val urlState: AsyncData<String>,
|
||||
val userAgent: String,
|
||||
val isInWidgetMode: Boolean,
|
||||
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.features.call.R
|
||||
import io.element.android.features.call.utils.WebViewWidgetMessageInterceptor
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
@@ -96,7 +96,7 @@ internal fun CallScreenView(
|
||||
|
||||
@Composable
|
||||
private fun CallWebView(
|
||||
url: Async<String>,
|
||||
url: AsyncData<String>,
|
||||
userAgent: String,
|
||||
onPermissionsRequested: (PermissionRequest) -> Unit,
|
||||
onWebViewCreated: (WebView) -> Unit,
|
||||
@@ -116,7 +116,7 @@ private fun CallWebView(
|
||||
}
|
||||
},
|
||||
update = { webView ->
|
||||
if (url is Async.Success && webView.url != url.data) {
|
||||
if (url is AsyncData.Success && webView.url != url.data) {
|
||||
webView.loadUrl(url.data)
|
||||
}
|
||||
},
|
||||
@@ -161,7 +161,7 @@ internal fun CallScreenViewPreview() {
|
||||
ElementPreview {
|
||||
CallScreenView(
|
||||
state = CallScreenState(
|
||||
urlState = Async.Success("https://call.element.io/some-actual-call?with=parameters"),
|
||||
urlState = AsyncData.Success("https://call.element.io/some-actual-call?with=parameters"),
|
||||
isInWidgetMode = false,
|
||||
userAgent = "",
|
||||
eventSink = {},
|
||||
|
||||
@@ -20,7 +20,6 @@ import android.net.Uri
|
||||
import javax.inject.Inject
|
||||
|
||||
class CallIntentDataParser @Inject constructor() {
|
||||
|
||||
private val validHttpSchemes = sequenceOf("https")
|
||||
|
||||
fun parse(data: String?): String? {
|
||||
|
||||
@@ -28,7 +28,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
class WebViewWidgetMessageInterceptor(
|
||||
private val webView: WebView,
|
||||
) : WidgetMessageInterceptor {
|
||||
|
||||
companion object {
|
||||
// We call both the WebMessageListener and the JavascriptInterface objects in JS with this
|
||||
// 'listenerName' so they can both receive the data from the WebView when
|
||||
@@ -56,7 +55,7 @@ class WebViewWidgetMessageInterceptor(
|
||||
|| !message.data.response && message.data.api == "fromWidget") {
|
||||
let json = JSON.stringify(event.data)
|
||||
${"console.log('message sent: ' + json);".takeIf { BuildConfig.DEBUG } }
|
||||
${LISTENER_NAME}.postMessage(json);
|
||||
$LISTENER_NAME.postMessage(json);
|
||||
} else {
|
||||
${"console.log('message received (ignored): ' + JSON.stringify(event.data));".takeIf { BuildConfig.DEBUG } }
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import io.element.android.features.call.data.WidgetMessage
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
object WidgetMessageSerializer {
|
||||
|
||||
private val coder = Json { ignoreUnknownKeys = true }
|
||||
|
||||
fun deserialize(message: String): Result<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"?>
|
||||
<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_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>
|
||||
</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">
|
||||
<string name="call_foreground_service_channel_title_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>
|
||||
|
||||
@@ -23,7 +23,6 @@ import io.element.android.features.call.ui.mapWebkitPermissions
|
||||
import org.junit.Test
|
||||
|
||||
class MapWebkitPermissionsTest {
|
||||
|
||||
@Test
|
||||
fun `given Webkit's RESOURCE_AUDIO_CAPTURE returns Android's RECORD_AUDIO permission`() {
|
||||
val permission = mapWebkitPermissions(arrayOf(PermissionRequest.RESOURCE_AUDIO_CAPTURE))
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.call.CallType
|
||||
import io.element.android.features.call.utils.FakeCallWidgetProvider
|
||||
import io.element.android.features.call.utils.FakeWidgetMessageInterceptor
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
@@ -48,7 +48,6 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class CallScreenPresenterTest {
|
||||
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@@ -62,7 +61,7 @@ class CallScreenPresenterTest {
|
||||
skipItems(1)
|
||||
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.urlState).isEqualTo(Async.Success("https://call.element.io"))
|
||||
assertThat(initialState.urlState).isEqualTo(AsyncData.Success("https://call.element.io"))
|
||||
assertThat(initialState.isInWidgetMode).isFalse()
|
||||
}
|
||||
}
|
||||
@@ -83,7 +82,7 @@ class CallScreenPresenterTest {
|
||||
skipItems(1)
|
||||
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.urlState).isInstanceOf(Async.Success::class.java)
|
||||
assertThat(initialState.urlState).isInstanceOf(AsyncData.Success::class.java)
|
||||
assertThat(initialState.isInWidgetMode).isTrue()
|
||||
assertThat(widgetProvider.getWidgetCalled).isTrue()
|
||||
assertThat(widgetDriver.runCalledCount).isEqualTo(1)
|
||||
@@ -191,7 +190,7 @@ class CallScreenPresenterTest {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
consumeItemsUntilTimeout()
|
||||
consumeItemsUntilTimeout()
|
||||
|
||||
assertThat(matrixClient.syncService().syncState.value).isEqualTo(SyncState.Running)
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
package io.element.android.features.call.ui
|
||||
|
||||
class FakeCallScreenNavigator : CallScreenNavigator {
|
||||
var closeCalled = false
|
||||
private set
|
||||
var closeCalled = false
|
||||
private set
|
||||
|
||||
override fun close() {
|
||||
closeCalled = true
|
||||
}
|
||||
override fun close() {
|
||||
closeCalled = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import java.net.URLEncoder
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class CallIntentDataParserTest {
|
||||
|
||||
private val callIntentDataParser = CallIntentDataParser()
|
||||
|
||||
@Test
|
||||
@@ -117,7 +116,7 @@ class CallIntentDataParserTest {
|
||||
@Test
|
||||
fun `Element Call url with url extra param appPrompt gets url extracted`() {
|
||||
doTest(
|
||||
url = "${VALID_CALL_URL_WITH_PARAM}&appPrompt=true",
|
||||
url = "$VALID_CALL_URL_WITH_PARAM&appPrompt=true",
|
||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
|
||||
)
|
||||
}
|
||||
@@ -125,7 +124,7 @@ class CallIntentDataParserTest {
|
||||
@Test
|
||||
fun `Element Call url with url extra param in fragment appPrompt gets url extracted`() {
|
||||
doTest(
|
||||
url = "${VALID_CALL_URL_WITH_PARAM}#?appPrompt=true",
|
||||
url = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=true",
|
||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=false&confineToRoom=true"
|
||||
)
|
||||
}
|
||||
@@ -133,7 +132,7 @@ class CallIntentDataParserTest {
|
||||
@Test
|
||||
fun `Element Call url with url extra param in fragment appPrompt and other gets url extracted`() {
|
||||
doTest(
|
||||
url = "${VALID_CALL_URL_WITH_PARAM}#?appPrompt=true&otherParam=maybe",
|
||||
url = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=true&otherParam=maybe",
|
||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=false&otherParam=maybe&confineToRoom=true"
|
||||
)
|
||||
}
|
||||
@@ -141,7 +140,7 @@ class CallIntentDataParserTest {
|
||||
@Test
|
||||
fun `Element Call url with url extra param confineToRoom gets url extracted`() {
|
||||
doTest(
|
||||
url = "${VALID_CALL_URL_WITH_PARAM}&confineToRoom=false",
|
||||
url = "$VALID_CALL_URL_WITH_PARAM&confineToRoom=false",
|
||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
|
||||
)
|
||||
}
|
||||
@@ -149,7 +148,7 @@ class CallIntentDataParserTest {
|
||||
@Test
|
||||
fun `Element Call url with url extra param in fragment confineToRoom gets url extracted`() {
|
||||
doTest(
|
||||
url = "${VALID_CALL_URL_WITH_PARAM}#?confineToRoom=false",
|
||||
url = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=false",
|
||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=true&appPrompt=false"
|
||||
)
|
||||
}
|
||||
@@ -157,7 +156,7 @@ class CallIntentDataParserTest {
|
||||
@Test
|
||||
fun `Element Call url with url extra param in fragment confineToRoom and more gets url extracted`() {
|
||||
doTest(
|
||||
url = "${VALID_CALL_URL_WITH_PARAM}#?confineToRoom=false&otherParam=maybe",
|
||||
url = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=false&otherParam=maybe",
|
||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=true&otherParam=maybe&appPrompt=false"
|
||||
)
|
||||
}
|
||||
@@ -165,7 +164,7 @@ class CallIntentDataParserTest {
|
||||
@Test
|
||||
fun `Element Call url with url fragment gets url extracted`() {
|
||||
doTest(
|
||||
url = "${VALID_CALL_URL_WITH_PARAM}#fragment",
|
||||
url = "$VALID_CALL_URL_WITH_PARAM#fragment",
|
||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#fragment?$EXTRA_PARAMS"
|
||||
)
|
||||
}
|
||||
@@ -173,7 +172,7 @@ class CallIntentDataParserTest {
|
||||
@Test
|
||||
fun `Element Call url with url fragment with params gets url extracted`() {
|
||||
doTest(
|
||||
url = "${VALID_CALL_URL_WITH_PARAM}#fragment?otherParam=maybe",
|
||||
url = "$VALID_CALL_URL_WITH_PARAM#fragment?otherParam=maybe",
|
||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#fragment?otherParam=maybe&$EXTRA_PARAMS"
|
||||
)
|
||||
}
|
||||
@@ -181,7 +180,7 @@ class CallIntentDataParserTest {
|
||||
@Test
|
||||
fun `Element Call url with url fragment with other params gets url extracted`() {
|
||||
doTest(
|
||||
url = "${VALID_CALL_URL_WITH_PARAM}#?otherParam=maybe",
|
||||
url = "$VALID_CALL_URL_WITH_PARAM#?otherParam=maybe",
|
||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?otherParam=maybe&$EXTRA_PARAMS"
|
||||
)
|
||||
}
|
||||
@@ -189,7 +188,7 @@ class CallIntentDataParserTest {
|
||||
@Test
|
||||
fun `Element Call url with empty fragment`() {
|
||||
doTest(
|
||||
url = "${VALID_CALL_URL_WITH_PARAM}#",
|
||||
url = "$VALID_CALL_URL_WITH_PARAM#",
|
||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
|
||||
)
|
||||
}
|
||||
@@ -197,7 +196,7 @@ class CallIntentDataParserTest {
|
||||
@Test
|
||||
fun `Element Call url with empty fragment query`() {
|
||||
doTest(
|
||||
url = "${VALID_CALL_URL_WITH_PARAM}#?",
|
||||
url = "$VALID_CALL_URL_WITH_PARAM#?",
|
||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultCallWidgetProviderTest {
|
||||
|
||||
@Test
|
||||
fun `getWidget - fails if the session does not exist`() = runTest {
|
||||
val provider = createProvider(matrixClientProvider = FakeMatrixClientProvider { Result.failure(Exception("Session not found")) })
|
||||
|
||||
@@ -24,19 +24,18 @@ import io.element.android.libraries.matrix.test.widget.FakeWidgetDriver
|
||||
class FakeCallWidgetProvider(
|
||||
private val widgetDriver: FakeWidgetDriver = FakeWidgetDriver(),
|
||||
private val url: String = "https://call.element.io",
|
||||
) : CallWidgetProvider {
|
||||
) : CallWidgetProvider {
|
||||
var getWidgetCalled = false
|
||||
private set
|
||||
|
||||
var getWidgetCalled = false
|
||||
private set
|
||||
|
||||
override suspend fun getWidget(
|
||||
sessionId: SessionId,
|
||||
roomId: RoomId,
|
||||
clientId: String,
|
||||
languageTag: String?,
|
||||
theme: String?
|
||||
): Result<Pair<MatrixWidgetDriver, String>> {
|
||||
getWidgetCalled = true
|
||||
return Result.success(widgetDriver to url)
|
||||
}
|
||||
override suspend fun getWidget(
|
||||
sessionId: SessionId,
|
||||
roomId: RoomId,
|
||||
clientId: String,
|
||||
languageTag: String?,
|
||||
theme: String?
|
||||
): 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
|
||||
|
||||
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) {
|
||||
sentMessages += message
|
||||
}
|
||||
|
||||
fun givenInterceptedMessage(message: String) {
|
||||
interceptedMessages.tryEmit(message)
|
||||
}
|
||||
override fun sendMessage(message: String) {
|
||||
sentMessages += 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
|
||||
|
||||
interface CreateRoomEntryPoint : FeatureEntryPoint {
|
||||
|
||||
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
||||
interface NodeBuilder {
|
||||
fun callback(callback: Callback): NodeBuilder
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package io.element.android.features.createroom.api
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
@@ -27,5 +27,5 @@ interface StartDMAction {
|
||||
* @param userId The user to start a DM with.
|
||||
* @param actionState The state to update with the result of the action.
|
||||
*/
|
||||
suspend fun execute(userId: UserId, actionState: MutableState<Async<RoomId>>)
|
||||
suspend fun execute(userId: UserId, actionState: MutableState<AsyncAction<RoomId>>)
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ class ConfigureRoomFlowNode @AssistedInject constructor(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins
|
||||
) {
|
||||
|
||||
private val component by lazy {
|
||||
parent!!.bindings<CreateRoomComponent.ParentBindings>().createRoomComponentBuilder().build()
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import javax.inject.Inject
|
||||
class CreateRoomDataStore @Inject constructor(
|
||||
val selectedUserListDataStore: UserListDataStore,
|
||||
) {
|
||||
|
||||
private val createRoomConfigFlow: MutableStateFlow<CreateRoomConfig> = MutableStateFlow(CreateRoomConfig())
|
||||
private var cachedAvatarUri: Uri? = null
|
||||
set(value) {
|
||||
|
||||
@@ -50,7 +50,6 @@ class CreateRoomFlowNode @AssistedInject constructor(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins
|
||||
) {
|
||||
|
||||
sealed interface NavTarget : Parcelable {
|
||||
@Parcelize
|
||||
data object Root : NavTarget
|
||||
|
||||
@@ -27,13 +27,10 @@ import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultCreateRoomEntryPoint @Inject constructor() : CreateRoomEntryPoint {
|
||||
|
||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): CreateRoomEntryPoint.NodeBuilder {
|
||||
|
||||
val plugins = ArrayList<Plugin>()
|
||||
|
||||
return object : CreateRoomEntryPoint.NodeBuilder {
|
||||
|
||||
override fun callback(callback: CreateRoomEntryPoint.Callback): CreateRoomEntryPoint.NodeBuilder {
|
||||
plugins += callback
|
||||
return this
|
||||
|
||||
@@ -20,7 +20,7 @@ import androidx.compose.runtime.MutableState
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import im.vector.app.features.analytics.plan.CreatedRoom
|
||||
import io.element.android.features.createroom.api.StartDMAction
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
@@ -35,18 +35,17 @@ class DefaultStartDMAction @Inject constructor(
|
||||
private val matrixClient: MatrixClient,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : StartDMAction {
|
||||
|
||||
override suspend fun execute(userId: UserId, actionState: MutableState<Async<RoomId>>) {
|
||||
actionState.value = Async.Loading()
|
||||
override suspend fun execute(userId: UserId, actionState: MutableState<AsyncAction<RoomId>>) {
|
||||
actionState.value = AsyncAction.Loading
|
||||
when (val result = matrixClient.startDM(userId)) {
|
||||
is StartDMResult.Success -> {
|
||||
if (result.isNew) {
|
||||
analyticsService.capture(CreatedRoom(isDM = true))
|
||||
}
|
||||
actionState.value = Async.Success(result.roomId)
|
||||
actionState.value = AsyncAction.Success(result.roomId)
|
||||
}
|
||||
is StartDMResult.Failure -> {
|
||||
actionState.value = Async.Failure(result.throwable)
|
||||
actionState.value = AsyncAction.Failure(result.throwable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ class AddPeopleNode @AssistedInject constructor(
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: AddPeoplePresenter,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun onContinue()
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ class AddPeoplePresenter @Inject constructor(
|
||||
userRepository: UserRepository,
|
||||
dataStore: CreateRoomDataStore,
|
||||
) : Presenter<UserListState> {
|
||||
|
||||
private val userListPresenter = userListPresenterFactory.create(
|
||||
UserListPresenterArgs(
|
||||
selectionMode = SelectionMode.Multiple,
|
||||
@@ -45,4 +44,3 @@ class AddPeoplePresenter @Inject constructor(
|
||||
return userListPresenter.present()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,11 +37,13 @@ open class AddPeopleUserListStateProvider : PreviewParameterProvider<UserListSta
|
||||
selectionMode = SelectionMode.Multiple,
|
||||
),
|
||||
aUserListState().copy(
|
||||
searchResults = SearchBarResultState.Results(aMatrixUserList()
|
||||
.mapIndexed { index, matrixUser ->
|
||||
UserSearchResult(matrixUser, index % 2 == 0)
|
||||
}
|
||||
.toImmutableList()),
|
||||
searchResults = SearchBarResultState.Results(
|
||||
aMatrixUserList()
|
||||
.mapIndexed { index, matrixUser ->
|
||||
UserSearchResult(matrixUser, index % 2 == 0)
|
||||
}
|
||||
.toImmutableList()
|
||||
),
|
||||
selectedUsers = aListOfSelectedUsers(),
|
||||
isSearchActive = true,
|
||||
selectionMode = SelectionMode.Multiple,
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package io.element.android.features.createroom.impl.addpeople
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -27,6 +26,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.createroom.impl.R
|
||||
import io.element.android.features.createroom.impl.components.UserListView
|
||||
import io.element.android.features.createroom.impl.userlist.UserListEvents
|
||||
@@ -39,10 +39,8 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun AddPeopleView(
|
||||
state: UserListState,
|
||||
|
||||
@@ -29,14 +29,14 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomPrivacyItem
|
||||
import io.element.android.features.createroom.impl.configureroom.roomPrivacyItems
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.RadioButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
|
||||
@Composable
|
||||
fun RoomPrivacyOption(
|
||||
@@ -85,7 +85,8 @@ fun RoomPrivacyOption(
|
||||
.align(Alignment.CenterVertically)
|
||||
.size(48.dp),
|
||||
selected = isSelected,
|
||||
onClick = null // null recommended for accessibility with screenreaders
|
||||
// null recommended for accessibility with screenreaders
|
||||
onClick = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,10 +57,7 @@ fun SearchMultipleUsersResultItem(
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun SearchMultipleUsersResultItemPreview() = ElementThemedPreview { ContentToPreview() }
|
||||
|
||||
@Composable
|
||||
private fun ContentToPreview() {
|
||||
internal fun SearchMultipleUsersResultItemPreview() = ElementThemedPreview {
|
||||
Column {
|
||||
SearchMultipleUsersResultItem(
|
||||
searchResult = UserSearchResult(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user