mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-21 17:05:39 +03:00
Merge tag 'v1.4.11' into sc
Change-Id: I454f1ec4f1df6366065d5690d9704eb1bd573c2d Conflicts: dependencies_groups.gradle library/ui-styles/build.gradle matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt vector/src/main/AndroidManifest.xml vector/src/main/assets/open_source_licenses.html vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt vector/src/main/res/menu/menu_timeline.xml
This commit is contained in:
commit
7c35f5fda5
369 changed files with 5583 additions and 2167 deletions
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
@ -26,7 +26,7 @@ jobs:
|
|||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
@ -50,7 +50,7 @@ jobs:
|
|||
# Only runs on main, no concurrency.
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
|
24
.github/workflows/nightly.yml
vendored
24
.github/workflows/nightly.yml
vendored
|
@ -34,7 +34,7 @@ jobs:
|
|||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: 3.8
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
@ -43,7 +43,7 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Start synapse server
|
||||
uses: michaelkaye/setup-matrix-synapse@v0.3.0
|
||||
uses: michaelkaye/setup-matrix-synapse@v0.4.0
|
||||
with:
|
||||
uploadLogs: true
|
||||
httpPort: 8080
|
||||
|
@ -174,7 +174,7 @@ jobs:
|
|||
# package: class PermalinkParserTest
|
||||
- name: Find Comment
|
||||
if: always() && github.event_name == 'pull_request'
|
||||
uses: peter-evans/find-comment@v1
|
||||
uses: peter-evans/find-comment@v2
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
|
@ -182,7 +182,7 @@ jobs:
|
|||
body-includes: Integration Tests Results
|
||||
- name: Publish results to PR
|
||||
if: always() && github.event_name == 'pull_request'
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
uses: peter-evans/create-or-update-comment@v2
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
|
@ -221,7 +221,7 @@ jobs:
|
|||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: 3.8
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
@ -230,7 +230,7 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Start synapse server
|
||||
uses: michaelkaye/setup-matrix-synapse@v0.3.0
|
||||
uses: michaelkaye/setup-matrix-synapse@v0.4.0
|
||||
with:
|
||||
uploadLogs: true
|
||||
httpPort: 8080
|
||||
|
@ -273,7 +273,7 @@ jobs:
|
|||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
@ -293,7 +293,7 @@ jobs:
|
|||
sonarqube:
|
||||
name: Sonarqube upload
|
||||
runs-on: macos-latest
|
||||
if: always()
|
||||
if: always() && github.event_name == 'schedule'
|
||||
needs:
|
||||
- codecov-units
|
||||
steps:
|
||||
|
@ -302,7 +302,7 @@ jobs:
|
|||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
@ -319,7 +319,7 @@ jobs:
|
|||
env:
|
||||
ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
||||
# Notify the channel about scheduled runs, do not notify for manually triggered runs
|
||||
# Notify the channel about scheduled runs, or pushes to the release branches, do not notify for manually triggered runs
|
||||
notify:
|
||||
name: Notify matrix
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -335,5 +335,5 @@ jobs:
|
|||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }}
|
||||
matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }}
|
||||
text_template: "Nightly test run: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}"
|
||||
html_template: "Nightly test run results: {{#each job_statuses }}{{#with this }}{{#if completed }}<br />{{icon conclusion}} {{name}} <font color='{{color conclusion}}'>{{conclusion}} at {{completed_at}} <a href=\"{{html_url}}\">[details]</a></font>{{/if}}{{/with}}{{/each}}"
|
||||
text_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}"
|
||||
html_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }}<br />{{icon conclusion}} {{name}} <font color='{{color conclusion}}'>{{conclusion}} at {{completed_at}} <a href=\"{{html_url}}\">[details]</a></font>{{/if}}{{/with}}{{/each}}"
|
||||
|
|
8
.github/workflows/quality.yml
vendored
8
.github/workflows/quality.yml
vendored
|
@ -59,7 +59,7 @@ jobs:
|
|||
fi
|
||||
- name: Find Comment
|
||||
if: always() && github.event_name == 'pull_request'
|
||||
uses: peter-evans/find-comment@v1
|
||||
uses: peter-evans/find-comment@v2
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
|
@ -67,7 +67,7 @@ jobs:
|
|||
body-includes: Ktlint Results
|
||||
- name: Add comment if needed
|
||||
if: always() && github.event_name == 'pull_request' && steps.ktlint-results.outputs.add_comment == 'true'
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
uses: peter-evans/create-or-update-comment@v2
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
|
@ -97,7 +97,7 @@ jobs:
|
|||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
@ -130,7 +130,7 @@ jobs:
|
|||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
- name: Run Emoji script
|
||||
run: ./tools/import_emojis.py
|
||||
- name: Create Pull Request for Emojis
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
commit-message: Sync Emojis
|
||||
title: Sync Emojis
|
||||
|
@ -49,7 +49,7 @@ jobs:
|
|||
- name: Run SAS String script
|
||||
run: ./tools/import_sas_strings.py
|
||||
- name: Create Pull Request for SAS Strings
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
commit-message: Sync SAS Strings
|
||||
title: Sync SAS Strings
|
||||
|
@ -68,7 +68,7 @@ jobs:
|
|||
- name: Run analytics import script
|
||||
run: ./tools/import_analytic_plan.sh
|
||||
- name: Create Pull Request for analytics plan
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
commit-message: Sync analytics plan
|
||||
title: Sync analytics plan
|
||||
|
|
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
|
@ -25,7 +25,7 @@ jobs:
|
|||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: 11
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
@ -45,7 +45,7 @@ jobs:
|
|||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
|
49
CHANGES.md
49
CHANGES.md
|
@ -1,3 +1,52 @@
|
|||
Changes in Element v1.4.11 (2022-04-07)
|
||||
=======================================
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- Choosing "leave all rooms and spaces" while leaving Space won't cause leaving DMs in this Space anymore ([#5609](https://github.com/vector-im/element-android/issues/5609))
|
||||
|
||||
|
||||
Changes in Element v1.4.10 (2022-04-05)
|
||||
=======================================
|
||||
|
||||
Features ✨
|
||||
----------
|
||||
- Allow scrolling position of Voice Message playback ([#5426](https://github.com/vector-im/element-android/issues/5426))
|
||||
- Users will be able to provide feedback for threads ([#5647](https://github.com/vector-im/element-android/issues/5647))
|
||||
- Update Jitsi lib from 3.10.0 to 5.0.2 ([#5654](https://github.com/vector-im/element-android/issues/5654))
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- Replace "open settings" button by "disable" action in RageShake dialog if there is no session ([#4445](https://github.com/vector-im/element-android/issues/4445))
|
||||
- Fixes room summaries showing encrypted content after verifying device ([#4867](https://github.com/vector-im/element-android/issues/4867))
|
||||
- Fixes polls being votable after being ended ([#5473](https://github.com/vector-im/element-android/issues/5473))
|
||||
- [Subscribing] Blank display name ([#5497](https://github.com/vector-im/element-android/issues/5497))
|
||||
- Fixes voice call button disappearing in DM rooms with more than 2 members ([#5548](https://github.com/vector-im/element-android/issues/5548))
|
||||
- Add loader in thread list ([#5562](https://github.com/vector-im/element-android/issues/5562))
|
||||
- Fixed key export when overwriting existing files ([#5663](https://github.com/vector-im/element-android/issues/5663))
|
||||
|
||||
In development 🚧
|
||||
----------------
|
||||
- Adding combined account creation and server selection screen as part of the new FTUE ([#5277](https://github.com/vector-im/element-android/issues/5277))
|
||||
- Finalising FTUE onboarding account creation personalization steps but keeping feature disabled until other parts are complete ([#5519](https://github.com/vector-im/element-android/issues/5519))
|
||||
- Live Location Sharing - Foreground Service and Notification ([#5595](https://github.com/vector-im/element-android/issues/5595))
|
||||
- Send beacon info state event when live location sharing started ([#5651](https://github.com/vector-im/element-android/issues/5651))
|
||||
- Show a banner in timeline while location sharing service is running ([#5660](https://github.com/vector-im/element-android/issues/5660))
|
||||
- Location sharing: adding possibility to choose duration of live sharing ([#5667](https://github.com/vector-im/element-android/issues/5667))
|
||||
|
||||
Other changes
|
||||
-------------
|
||||
- Improve main timeline thread summary rendering ([#5151](https://github.com/vector-im/element-android/issues/5151))
|
||||
- "Add space" copy is replaced with "create space" in left sliding panel ([#5516](https://github.com/vector-im/element-android/issues/5516))
|
||||
- Flattening the asynchronous onboarding state and passing all errors through the same pipeline ([#5517](https://github.com/vector-im/element-android/issues/5517))
|
||||
- Changed items order in space menu. Changed capitalization for "space" in those items ([#5524](https://github.com/vector-im/element-android/issues/5524))
|
||||
- Permalinks to root thread messages will now navigate you within the thread timeline ([#5567](https://github.com/vector-im/element-android/issues/5567))
|
||||
- Live location sharing: adding way to override feature activation in debug ([#5581](https://github.com/vector-im/element-android/issues/5581))
|
||||
- Adds unit tests around the login with matrix id flow ([#5628](https://github.com/vector-im/element-android/issues/5628))
|
||||
- Setup the plugin org.owasp.dependencycheck ([#5654](https://github.com/vector-im/element-android/issues/5654))
|
||||
- Implement threads beta opt-in mechanism to notify users about threads ([#5692](https://github.com/vector-im/element-android/issues/5692))
|
||||
|
||||
|
||||
Changes in Element v1.4.8 (2022-03-28)
|
||||
======================================
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md
|
||||
|
||||
Android support can be found in this [![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org) room.
|
||||
Element Android support can be found in this room: [![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org).
|
||||
|
||||
# Specific rules for Matrix Android projects
|
||||
|
||||
|
@ -44,6 +44,8 @@ If you want to fix an issue in other languages, or add a missing translation, or
|
|||
|
||||
## I want to submit a PR to fix an issue
|
||||
|
||||
Please have a look in the [dedicated documentation](./docs/pull_request.md) about pull request.
|
||||
|
||||
Please check if a corresponding issue exists. If yes, please let us know in a comment that you're working on it.
|
||||
If an issue does not exist yet, it may be relevant to open a new issue and let us know that you're implementing it.
|
||||
|
||||
|
|
27
build.gradle
27
build.gradle
|
@ -21,7 +21,7 @@ buildscript {
|
|||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'
|
||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
|
||||
classpath "com.likethesalad.android:stem-plugin:2.0.0"
|
||||
|
||||
classpath 'org.owasp:dependency-check-gradle:7.0.4.1'
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
|
@ -32,6 +32,16 @@ plugins {
|
|||
id "org.jlleitschuh.gradle.ktlint" version "10.2.1"
|
||||
}
|
||||
|
||||
// https://github.com/jeremylong/DependencyCheck
|
||||
apply plugin: 'org.owasp.dependencycheck'
|
||||
|
||||
dependencyCheck {
|
||||
// See https://jeremylong.github.io/DependencyCheck/general/suppression.html
|
||||
suppressionFiles = [
|
||||
"./tools/dependencycheck/suppressions.xml"
|
||||
]
|
||||
}
|
||||
|
||||
allprojects {
|
||||
apply plugin: "org.jlleitschuh.gradle.ktlint"
|
||||
|
||||
|
@ -51,7 +61,7 @@ allprojects {
|
|||
}
|
||||
// Jitsi repo
|
||||
maven {
|
||||
url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-3.10.0"
|
||||
url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-5.0.2"
|
||||
// Note: to test Jitsi release you can use a local file like this:
|
||||
// url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.10.0"
|
||||
content {
|
||||
|
@ -87,6 +97,8 @@ allprojects {
|
|||
|
||||
// See https://github.com/JLLeitschuh/ktlint-gradle#configuration
|
||||
ktlint {
|
||||
// See https://github.com/pinterest/ktlint/releases/
|
||||
version = "0.45.1"
|
||||
android = true
|
||||
ignoreFailures = false
|
||||
enableExperimentalRules = true
|
||||
|
@ -96,7 +108,16 @@ allprojects {
|
|||
"spacing-between-declarations-with-comments",
|
||||
"no-multi-spaces",
|
||||
"experimental:spacing-between-declarations-with-annotations",
|
||||
"experimental:annotation"
|
||||
"experimental:annotation",
|
||||
// - Missing newline after "("
|
||||
// - Missing newline before ")"
|
||||
"wrapping",
|
||||
// - Unnecessary trailing comma before ")"
|
||||
"experimental:trailing-comma",
|
||||
// - A block comment in between other elements on the same line is disallowed
|
||||
"experimental:comment-wrapping",
|
||||
// - A KDoc comment after any other element on the same line must be separated by a new line
|
||||
"experimental:kdoc-wrapping",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,13 +9,13 @@ ext.versions = [
|
|||
|
||||
def gradle = "7.0.4"
|
||||
// Ref: https://kotlinlang.org/releases.html
|
||||
def kotlin = "1.5.31"
|
||||
def kotlinCoroutines = "1.5.2"
|
||||
def kotlin = "1.6.0"
|
||||
def kotlinCoroutines = "1.6.0"
|
||||
def dagger = "2.40.5"
|
||||
def retrofit = "2.9.0"
|
||||
def arrow = "0.8.2"
|
||||
def markwon = "4.6.2"
|
||||
def moshi = "1.12.0"
|
||||
def moshi = "1.13.0"
|
||||
def lifecycle = "2.4.0"
|
||||
def flowBinding = "1.2.0"
|
||||
def epoxy = "4.6.2"
|
||||
|
|
|
@ -7,6 +7,7 @@ ext.groups = [
|
|||
'com.github.chrisbanes',
|
||||
'com.github.hyuwah',
|
||||
'com.github.jetradarmobile',
|
||||
'com.github.MatrixFrog',
|
||||
'com.github.SchildiChat',
|
||||
'com.github.tapadoo',
|
||||
'com.github.UnifiedPush',
|
||||
|
@ -41,6 +42,7 @@ ext.groups = [
|
|||
regex: [
|
||||
],
|
||||
group: [
|
||||
'ch.qos.logback',
|
||||
'com.adevinta.android',
|
||||
'com.airbnb.android',
|
||||
'com.almworks.sqlite4java',
|
||||
|
@ -50,10 +52,12 @@ ext.groups = [
|
|||
'com.beust',
|
||||
'com.davemorrissey.labs',
|
||||
'com.dropbox.core',
|
||||
'com.facebook.fbjni',
|
||||
'com.facebook.fresco',
|
||||
'com.facebook.infer.annotation',
|
||||
'com.facebook.soloader',
|
||||
'com.facebook.stetho',
|
||||
'com.facebook.yoga',
|
||||
'com.fasterxml',
|
||||
'com.fasterxml.jackson',
|
||||
'com.fasterxml.jackson.core',
|
||||
|
@ -115,6 +119,7 @@ ext.groups = [
|
|||
'info.picocli',
|
||||
'io.arrow-kt',
|
||||
'io.github.detekt.sarif4k',
|
||||
'io.github.microutils',
|
||||
'io.github.reactivecircus.flowbinding',
|
||||
'io.grpc',
|
||||
'io.jsonwebtoken',
|
||||
|
|
|
@ -18,6 +18,8 @@ The generated maven repository is then host in the project https://github.com/ve
|
|||
|
||||
Update the script `./tools/jitsi/build_jisti_libs.sh` with the tag of the project `https://github.com/jitsi/jitsi-meet`.
|
||||
|
||||
Latest tag can be found from this page: https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md
|
||||
|
||||
Currently we are building the version with the tag `android-sdk-3.10.0`.
|
||||
|
||||
### Run the build script
|
||||
|
|
236
docs/pull_request.md
Normal file
236
docs/pull_request.md
Normal file
|
@ -0,0 +1,236 @@
|
|||
# Pull requests
|
||||
|
||||
## Introduction
|
||||
|
||||
This document gives some clue about how to efficiently manage Pull Requests (PR). This document is a first draft and may be improved later.
|
||||
|
||||
## Who should read this document?
|
||||
|
||||
Every pull request reviewers, but also probably every ones who submit PRs.
|
||||
|
||||
## Submitting PR
|
||||
|
||||
### Who can submit pull requests?
|
||||
|
||||
Basically every one who wants to contribute to the project! But there are some rules to follow.
|
||||
|
||||
#### Humans
|
||||
|
||||
People with write access to the project can directly clone the project, push their branches and create PR.
|
||||
|
||||
External contributors must first fork the project and create PR to the mainline from there.
|
||||
|
||||
##### Draft PR?
|
||||
|
||||
Draft PR can be created when the submitter does not expect the PR to be reviewed and merged yet. It can be useful to publicly show the work, or to do a self-review first.
|
||||
|
||||
Draft PR can also be created when it depends on other un-merged PR.
|
||||
|
||||
In any case, it is better to explicitly declare in the description why the PR is a draft PR.
|
||||
|
||||
Also, draft PR should not stay indefinitely in this state. It may be removed if it is the case and the submitter does not update it after a few days.
|
||||
|
||||
##### PR Review Assignment
|
||||
|
||||
We use automatic assignment for PR reviews. A PR is automatically routed by GitHub to a team member using the round robin algorithm. The process is the following:
|
||||
|
||||
- The PR creator assigns the [element-android](https://github.com/orgs/vector-im/teams/element-android) team as a reviewer. They can skip this process and assign directly a specific member if they think they should take a look at it.
|
||||
- GitHub automatically assigns one reviewer. If the chosen reviewer is not available (holiday, etc.), remove them and set again the team, GitHub will select another reviewer.
|
||||
- The reviewer gets a notification to make the review: they review the code following the good practice (see the rest of this document).
|
||||
- After making their own review, if they feel not confident enough, they can ask another person for a full review, or they can tag someone within a PR comment to check specific lines.
|
||||
|
||||
For PRs coming from the community, the issue wrangler can assign either the team [element-android](https://github.com/orgs/vector-im/teams/element-android) or any member directly.
|
||||
|
||||
##### PR review time
|
||||
|
||||
As a PR submitter, you deserve a quick review. As a reviewer, you should do your best to unblock others.
|
||||
|
||||
Some tips to achieve it:
|
||||
|
||||
- Set up your GH notifications correctly
|
||||
- Check your pulls page: [https://github.com/pulls](https://github.com/pulls)
|
||||
- Check your pending assigned PRs before starting or resuming your day to day tasks
|
||||
|
||||
It is hard to define a deadline for a review. It depends on the PR size and the complexity. Let's start with a goal of 24h (working day!) for a PR smaller than 500 lines. If bigger, the submitter and the reviewer should discuss.
|
||||
|
||||
After this time, the submitter can ping the reviewer to get a status of the review.
|
||||
|
||||
##### Re-request PR review
|
||||
|
||||
Once all the remarks have been handled, it's possible to re-request a review from the (same) reviewer to let them know that the PR has been updated the PR is ready to be reviewed again. Use the double arrow next to the reviewer name to do that.
|
||||
|
||||
##### When create split PR?
|
||||
|
||||
To implement big new feature, it may be efficient to split the work into several smaller and scoped PRs. They will be easier to review, and they can be merged on `develop` faster.
|
||||
|
||||
Big PR can take time, and there is a risk of future merge conflict.
|
||||
|
||||
Feature flag can be used to avoid half implemented feature to be available in the application.
|
||||
|
||||
That said, splitting into several PRs should not have the side effect to have more review to do, for instance if some code is added, then finally removed.
|
||||
|
||||
##### Avoid fixing other unrelated issue in a big PR
|
||||
|
||||
Each PR should focus on a single task. If other issues may be fixed when working in the area of it, it's preferable to open a dedicated PR.
|
||||
|
||||
It will have the advantage to be reviewed and merged faster, and not interfere with the main PR.
|
||||
|
||||
It's also applicable for code rework (such as renaming for instance), or code formatting. Sometimes, it is more efficient to extract that work to a dedicated PR, and rebase your branch once this "rework" PR has been merged.
|
||||
|
||||
#### Bots
|
||||
|
||||
Some bots can create PR, but they still have to be reviewed by the team
|
||||
|
||||
##### Dependabot
|
||||
|
||||
Dependabot is a tool which maintain all our external dependencies up to date. A dedicated PR is created for each new available release for one of our external dependency.Dependabot
|
||||
|
||||
To review such PR, you have to
|
||||
- **IMPORTANT** check the diff files (as always).
|
||||
- Check the release note. Some existing bugs in Element project may be fixed by the upgrade
|
||||
- Make sure that the CI is happy
|
||||
- If the code does not compile (API break for instance), you have to checkout the branch and push new commits
|
||||
- Do some smoke test, depending of the library which has been upgraded
|
||||
|
||||
For some reason dependabot sometimes does not upgrade some dependencies. In this case, and when detected, the upgrade has to be done manually.
|
||||
|
||||
##### Gradle wrapper
|
||||
|
||||
`Update Gradle Wrapper` is a tool which can create PR to upgrade our gradle.properties file.
|
||||
Review such PR is the same recipe than for PR from Dependabot
|
||||
|
||||
##### Sync analytics plan
|
||||
|
||||
This tools imports any update in the analytics plan. See instruction in the PR itself to handle it.
|
||||
More info can be found in the file [analytics.md]
|
||||
|
||||
## Reviewing PR
|
||||
|
||||
### Who can review pull requests?
|
||||
|
||||
As an open source project, every one can review each others PR. Of course an approval from internal developer is mandatory for a PR to be merged.
|
||||
But comment in PR from the community are always appreciated!
|
||||
|
||||
### What to have in mind when reviewing a PR
|
||||
|
||||
1. User experience: is the UX and UI correct? You will probably be the second person to test the new thing, the first one is the developer.
|
||||
2. Developer experience: does the code look nice and decoupled? No big functions, new classes added to the right module, etc.
|
||||
3. Code maintenance. A bit similar to point 2. Tricky code must be documented for instance
|
||||
4. Fork consideration. Will configuration of forks be easy? Some documentation may help in some cases.
|
||||
5. We are building long term products. "Quick and dirty" code must be avoided.
|
||||
6. The PR includes new tests for the added code, updated test for the existing code
|
||||
7. All PRs from external contributors **MUST** include a sign-off. It's in the checklist, and sometimes it's checked by the submitter, but there is actually no sign-off. In this case, ask nicely for a sign-off and request changes (do not approve the PR, even if everything else is fine).
|
||||
|
||||
### Rules
|
||||
|
||||
#### Check the form
|
||||
|
||||
##### PR title
|
||||
|
||||
PR title should describe in one line what's brought by the PR. Reviewer can edit the title if it's not clear enough, or to add suffix like `[BLOCKED]` or similar. Fixing typo is also a good practice, since GitHub search is quite not efficient, so the words must be spelled without any issue. Adding suffix will help when viewing the PR list.
|
||||
|
||||
It's free form, but prefix tags could also be used to help understand what's in the PR.
|
||||
|
||||
Examples of prefixes:
|
||||
- `[Refacto]`
|
||||
- `[Feature]`
|
||||
- `[Bugfix]`
|
||||
- etc.
|
||||
|
||||
Also, it's still possible to add labels to the PRs, such as `A-` or `T-` labels, even if this is not a string requirement. We prefer to spend time to add labels on issues.
|
||||
|
||||
##### PR description
|
||||
|
||||
PR description should follow the PR template, and at least provide some context about the code change.
|
||||
|
||||
##### File change
|
||||
|
||||
1. Code should follow the guidelines
|
||||
2. Code should be formatted correctly
|
||||
3. XML attribute must be sorted
|
||||
4. New code is added at the correct location
|
||||
5. New classes are added to the correct location
|
||||
6. Naming is correct. Naming is really important, it's considered part of the documentation
|
||||
7. Architecture is followed. For instance, the logic is in the ViewModel and not in the Fragment
|
||||
8. There is at least one file for the changelog. Exception if the PR fixes something which has not been released yet. Changelog content should target their audience: `.sdk` extension are mainly targeted for developers, other extensions are targeted for users and forks maintainers. It should generally describe visual change rather than give technical details. More details can be found [here](../CONTRIBUTING.md#changelog).
|
||||
9. PR includes tests. allScreensTest when applicable, and unit tests
|
||||
10. Avoid over complicating things. Keep it simple (KISS)!
|
||||
11. PR contains only the expected change. Sometimes, the diff is showing changes that are already on `develop`. This is not good, submitter has to fix that up.
|
||||
|
||||
##### Check the commit
|
||||
|
||||
Commit message must be short, one line and valuable. "WIP" is not a good commit message. Commit message can contain issue number, starting with `#`. GitHub will add some link between the issue and such commit, which can be useful. It's possible to change a commit message at any time (may require a force push).
|
||||
|
||||
Commit messages can contain extra lines with more details, links, etc. But keep in mind that those lines are quite less visible than the first line.
|
||||
|
||||
Also commit history should be nice. Having commits like "Adding temporary code" then later "Removing temporary code" is not good. The branch has to be rebased and those commit have to be dropped.
|
||||
|
||||
PR merger could decide to squash and merge if commit history is not good.
|
||||
|
||||
Commit like "Code review fixes" is good when reviewing the PR, since new changes can be reviewed easily, but is less valuable when looking at git history. To avoid this, PR submitter should always push new commits after a review (no commit amend with force push), and when the PR is approved decide to interactive rebase the PR to improve the git history and reduce noise.
|
||||
|
||||
##### Check the substance
|
||||
|
||||
1. Test the changes!
|
||||
2. Test the nominal case and the edge cases
|
||||
3. Run the sanity test for critical PR
|
||||
|
||||
##### Make a dedicated meeting to review the PR
|
||||
|
||||
Sometimes a big PR can be hard to review. Setting up a call with the PR submitter can speed up the communication, rather than putting comments and questions in GitHub comments. It has the inconvenience of making the discussion non-public, consider including a summary of the main points of the "offline" conversation in the PR.
|
||||
|
||||
### What happen to the issue(s)?
|
||||
|
||||
The issue(s) should be referenced in the PR description using keywords like `Closes` of `Fixes` followed by the issue number.
|
||||
|
||||
Example:
|
||||
> Closes #1
|
||||
|
||||
Note that you have to repeat the keyword in case of a list of issue
|
||||
|
||||
> Closes #1, Closes #2, etc.
|
||||
|
||||
When PR will be merged, such referenced issue will be automatically closed.
|
||||
It is up to the person who has merged the PR to go to the (closed) issue(s) and to add a comment to inform in which version the issue fix will be available. Use the current version of `develop` branch.
|
||||
|
||||
> Closed in Element Android v1.x.y
|
||||
|
||||
### Merge conflict
|
||||
|
||||
It's up to the submitter to handle merge conflict. Sometimes, they can be fixed directly from GitHub, sometimes this is not possible. The branch can be rebased on `develop`, or the `develop` branch can be merged on the branch, it's up to the submitter to decide what is best.
|
||||
Keep in mind that Github Actions are not run in case of conflict.
|
||||
|
||||
### When and who can merge PR
|
||||
|
||||
PR can be merged by the submitter, if and only if at least one approval from another developer is done. Approval from all people added as reviewer is also a good thing to have. Approval from design team may be mandatory, but is not sufficient to merge a PR.
|
||||
|
||||
PR can also be merged by the reviewer, to reduce the time the PR is open. But only if the PR is not in draft and the change are quite small, or behind a feature flag.
|
||||
|
||||
Dangerous PR should not be merged just before a release. Dangerous PR are PR that could break the app. Update of Realm library, rework in the chunk of Events management in the SDK, etc.
|
||||
|
||||
We prefer to merge such PR after a release so that it can be tested during several days by the team before behind included in a release candidate.
|
||||
|
||||
PR from bots will always be merged by the reviewer, right after approving the changes, or in case of critical changes, right after a release.
|
||||
|
||||
#### Merge type
|
||||
|
||||
Generally we use "Create a merge commit", which has the advantage to keep the branch visible.
|
||||
|
||||
If git history is noisy (code added, then removed, etc.), it's possible to use "Squash and merge". But the branch will not be visible anymore, a commit will be added on top of develop. Git commit message can (and probably must) be edited from the GitHub web app. It's better if the submitter do the work to cleanup the git history by using a git interactive rebase of their branch.
|
||||
|
||||
### Resolve conversation
|
||||
|
||||
Generally we do not close conversation added during PR review and update by clicking on "Resolve conversation"
|
||||
If the submitter or the reviewer do so, it will more difficult for further readers to see again the content. They will have to open the conversation to see it again. it's a waste of time.
|
||||
|
||||
When remarks are handled, a small comment like "done" is enough, commit hash can also be added to the conversation.
|
||||
|
||||
Exception: for big PRs with lots of conversations, using "Resolve conversation" may help to see the remaining remarks.
|
||||
|
||||
Also "Resolve conversation" should probably be hit by the creator of the conversation.
|
||||
|
||||
## Responsibility
|
||||
|
||||
PR submitter is responsible of the incoming change. PR reviewers who approved the PR take a part of responsibility on the code which will land to develop, and then be used by our users, and the user of our forks.
|
||||
|
||||
That said, bug may still be merged on `develop`, this is still acceptable of course. In this case, please make sure an issue is created and correctly labelled. Ideally, such issues should be fixed before the next release candidate, i.e. with a higher priority. But as we release the application every 10 working days, it can be hard to fix every bugs. That's why PR should be fully tested and reviewed before being merge and we should never comment code review remark with "will be handled later", or similar comments.
|
2
fastlane/metadata/android/cs-CZ/changelogs/40104040.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40104040.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Hlavní změny v této verzi: vylepšení indikátoru psaní. Opravy různých chyb a vylepšení stability.
|
||||
Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.4.4
|
2
fastlane/metadata/android/de-DE/changelogs/40104040.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/40104040.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Neuer Tippindikator und Bugfixes
|
||||
Alle Änderungen: https://github.com/vector-im/element-android/releases/tag/v1.4.4
|
2
fastlane/metadata/android/en-US/changelogs/40104100.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40104100.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: Scroll in voice message. Various bug fixes and stability improvements.
|
||||
Full changelog: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/en-US/changelogs/40104110.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40104110.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: Various bug fixes and stability improvements.
|
||||
Full changelog: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/et/changelogs/40104040.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40104040.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Põhilised muutused selles versioonis: kirjutusteatiste liidese uuendused ning pisiparandused ja stabiilsust parandavad kohendused.
|
||||
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.4.4
|
2
fastlane/metadata/android/fa/changelogs/40104040.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40104040.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
تغییرات عمده در این نگارش: بهروز رسانیهای رابط کاربری نشانگر نوشتن. چندین رفع اشکال و بهبودهای پایداری.
|
||||
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.4.4
|
2
fastlane/metadata/android/id/changelogs/40104040.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40104040.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Perubahan utama dalam versi ini: pembaruan UI indikator pengetikan. Beberapa perbaikan kutu dan perbaikan stabilitas.
|
||||
Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.4.4
|
2
fastlane/metadata/android/it-IT/changelogs/40104040.txt
Normal file
2
fastlane/metadata/android/it-IT/changelogs/40104040.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Modifiche principali in questa versione: agg.mento indicatore scrittura. Correzioni errori e miglioramenti stabilità.
|
||||
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.4.4
|
2
fastlane/metadata/android/pt-BR/changelogs/40104040.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40104040.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Principais mudanças nesta versão: atualizações de UI de indicador de digitação. Vários consertos de bugs e melhorias de estabilidade.
|
||||
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.4.4
|
2
fastlane/metadata/android/sk/changelogs/40104040.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40104040.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Hlavné zmeny v tejto verzii: aktualizácie používateľského rozhrania indikátora písania. Rôzne opravy chýb a vylepšenia stability.
|
||||
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.4.4
|
2
fastlane/metadata/android/sq/changelogs/40104040.txt
Normal file
2
fastlane/metadata/android/sq/changelogs/40104040.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Ndryshimet kryesore në këtë version: përditësime UI treguesi shtypjeje. Ndreqje të metash të ndryshme dhe përmirësime qëndrueshmërie.
|
||||
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.4.4
|
2
fastlane/metadata/android/sv-SE/changelogs/40104040.txt
Normal file
2
fastlane/metadata/android/sv-SE/changelogs/40104040.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Huvudsakliga ändringar i den här versionen: gränssnittsuppdateringar för skrivindikator. Diverse buggfixar och stabilitetsförbättringar.
|
||||
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.4.4
|
2
fastlane/metadata/android/uk/changelogs/40104040.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40104040.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Основні зміни в цій версії: оновлено індикатор набору. Виправлено різні вади й удосконалено стабільність.
|
||||
Вичерпний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.4.4
|
2
fastlane/metadata/android/zh-TW/changelogs/40104040.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40104040.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
此版本中的主要變動:輸入指示器使用者介面更新。許多臭蟲修復與穩定性改善。
|
||||
完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.4.4
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=a9a7b7baba105f6557c9dcf9c3c6e8f7e57e6b49889c5f1d133f015d0727e4be
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-all.zip
|
||||
distributionSha256Sum=e6d864e3b5bc05cc62041842b306383fc1fefcec359e70cebb1d470a6094ca82
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
@ -20,13 +20,12 @@ import android.content.Context
|
|||
import android.view.View
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Success
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import me.gujun.android.span.Span
|
||||
import me.gujun.android.span.span
|
||||
|
||||
internal class JSonViewerEpoxyController(private val context: Context) :
|
||||
TypedEpoxyController<JSonViewerState>() {
|
||||
TypedEpoxyController<JSonViewerState>() {
|
||||
|
||||
private var styleProvider: JSonViewerStyleProvider = JSonViewerStyleProvider.default(context)
|
||||
|
||||
|
@ -44,10 +43,8 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
|||
text(async.error.localizedMessage?.toEpoxyCharSequence())
|
||||
}
|
||||
}
|
||||
is Success -> {
|
||||
val model = data.root.invoke()
|
||||
|
||||
model?.let {
|
||||
else -> {
|
||||
async.invoke()?.let {
|
||||
buildRec(it, 0, "")
|
||||
}
|
||||
}
|
||||
|
@ -55,9 +52,9 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
|||
}
|
||||
|
||||
private fun buildRec(
|
||||
model: JSonViewerModel,
|
||||
depth: Int,
|
||||
idBase: String
|
||||
model: JSonViewerModel,
|
||||
depth: Int,
|
||||
idBase: String
|
||||
) {
|
||||
val host = this
|
||||
val id = "$idBase/${model.key ?: model.index}_${model.isExpanded}}"
|
||||
|
@ -74,34 +71,34 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
|||
id(id + "_sum")
|
||||
depth(depth)
|
||||
text(
|
||||
span {
|
||||
if (model.key != null) {
|
||||
span("\"${model.key}\"") {
|
||||
textColor = host.styleProvider.keyColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
if (model.index != null) {
|
||||
span("${model.index}") {
|
||||
textColor = host.styleProvider.secondaryColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
span {
|
||||
+"{+${model.keys.size}}"
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}.toEpoxyCharSequence()
|
||||
if (model.key != null) {
|
||||
span("\"${model.key}\"") {
|
||||
textColor = host.styleProvider.keyColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
if (model.index != null) {
|
||||
span("${model.index}") {
|
||||
textColor = host.styleProvider.secondaryColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
span {
|
||||
+"{+${model.keys.size}}"
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
itemClickListener(View.OnClickListener { host.itemClicked(model) })
|
||||
}
|
||||
}
|
||||
}
|
||||
is JSonViewerArray -> {
|
||||
is JSonViewerArray -> {
|
||||
if (model.isExpanded) {
|
||||
open(id, model.key, model.index, depth, false, model)
|
||||
model.items.forEach {
|
||||
|
@ -113,6 +110,38 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
|||
id(id + "_sum")
|
||||
depth(depth)
|
||||
text(
|
||||
span {
|
||||
if (model.key != null) {
|
||||
span("\"${model.key}\"") {
|
||||
textColor = host.styleProvider.keyColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
if (model.index != null) {
|
||||
span("${model.index}") {
|
||||
textColor = host.styleProvider.secondaryColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
span {
|
||||
+"[+${model.items.size}]"
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
itemClickListener(View.OnClickListener { host.itemClicked(model) })
|
||||
}
|
||||
}
|
||||
}
|
||||
is JSonViewerLeaf -> {
|
||||
valueItem {
|
||||
id(id)
|
||||
depth(depth)
|
||||
text(
|
||||
span {
|
||||
if (model.key != null) {
|
||||
span("\"${model.key}\"") {
|
||||
|
@ -122,6 +151,7 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
|||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
|
||||
if (model.index != null) {
|
||||
span("${model.index}") {
|
||||
textColor = host.styleProvider.secondaryColor
|
||||
|
@ -130,41 +160,8 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
|||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
span {
|
||||
+"[+${model.items.size}]"
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
append(host.valueToSpan(model))
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
itemClickListener(View.OnClickListener { host.itemClicked(model) })
|
||||
}
|
||||
}
|
||||
}
|
||||
is JSonViewerLeaf -> {
|
||||
valueItem {
|
||||
id(id)
|
||||
depth(depth)
|
||||
text(
|
||||
span {
|
||||
if (model.key != null) {
|
||||
span("\"${model.key}\"") {
|
||||
textColor = host.styleProvider.keyColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
|
||||
if (model.index != null) {
|
||||
span("${model.index}") {
|
||||
textColor = host.styleProvider.secondaryColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
append(host.valueToSpan(model))
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
copyValue(model.stringRes)
|
||||
}
|
||||
|
@ -175,12 +172,12 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
|||
private fun valueToSpan(leaf: JSonViewerLeaf): Span {
|
||||
val host = this
|
||||
return when (leaf.type) {
|
||||
JSONType.STRING -> {
|
||||
JSONType.STRING -> {
|
||||
span("\"${leaf.stringRes}\"") {
|
||||
textColor = host.styleProvider.stringColor
|
||||
}
|
||||
}
|
||||
JSONType.NUMBER -> {
|
||||
JSONType.NUMBER -> {
|
||||
span(leaf.stringRes) {
|
||||
textColor = host.styleProvider.numberColor
|
||||
}
|
||||
|
@ -190,7 +187,7 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
|||
textColor = host.styleProvider.booleanColor
|
||||
}
|
||||
}
|
||||
JSONType.NULL -> {
|
||||
JSONType.NULL -> {
|
||||
span("null") {
|
||||
textColor = host.styleProvider.booleanColor
|
||||
}
|
||||
|
@ -199,42 +196,42 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
|||
}
|
||||
|
||||
private fun open(
|
||||
id: String,
|
||||
key: String?,
|
||||
index: Int?,
|
||||
depth: Int,
|
||||
isObject: Boolean = true,
|
||||
composed: JSonViewerModel
|
||||
id: String,
|
||||
key: String?,
|
||||
index: Int?,
|
||||
depth: Int,
|
||||
isObject: Boolean = true,
|
||||
composed: JSonViewerModel
|
||||
) {
|
||||
val host = this
|
||||
valueItem {
|
||||
id("${id}_Open")
|
||||
depth(depth)
|
||||
text(
|
||||
span {
|
||||
if (key != null) {
|
||||
span("\"$key\"") {
|
||||
textColor = host.styleProvider.keyColor
|
||||
span {
|
||||
if (key != null) {
|
||||
span("\"$key\"") {
|
||||
textColor = host.styleProvider.keyColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
if (index != null) {
|
||||
span("$index") {
|
||||
textColor = host.styleProvider.secondaryColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
}
|
||||
if (index != null) {
|
||||
span("$index") {
|
||||
span("- ") {
|
||||
textColor = host.styleProvider.secondaryColor
|
||||
}
|
||||
span(" : ") {
|
||||
span("{".takeIf { isObject } ?: "[") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
span("- ") {
|
||||
textColor = host.styleProvider.secondaryColor
|
||||
}
|
||||
span("{".takeIf { isObject } ?: "[") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}.toEpoxyCharSequence()
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
itemClickListener(View.OnClickListener { host.itemClicked(composed) })
|
||||
}
|
||||
|
@ -251,10 +248,10 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
|||
id("${id}_Close")
|
||||
depth(depth)
|
||||
text(
|
||||
span {
|
||||
text = "}".takeIf { isObject } ?: "]"
|
||||
textColor = host.styleProvider.baseColor
|
||||
}.toEpoxyCharSequence()
|
||||
span {
|
||||
text = "}".takeIf { isObject } ?: "]"
|
||||
textColor = host.styleProvider.baseColor
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,4 @@ dependencies {
|
|||
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12'
|
||||
// dialpad dimen
|
||||
implementation 'im.dlg:android-dialer:1.2.5'
|
||||
// AudioRecordView attr
|
||||
implementation 'com.github.Armen101:AudioRecordView:1.0.5'
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle" >
|
||||
<gradient
|
||||
android:angle="-90"
|
||||
android:startColor="#00000000"
|
||||
android:endColor="#1A000000"
|
||||
/>
|
||||
</shape>
|
|
@ -40,6 +40,7 @@
|
|||
<dimen name="menu_item_icon_size">24dp</dimen>
|
||||
<dimen name="menu_item_size">48dp</dimen>
|
||||
<dimen name="menu_item_ripple_size">48dp</dimen>
|
||||
<dimen name="menu_item_width_small">38dp</dimen>
|
||||
|
||||
<!-- Composer -->
|
||||
<dimen name="composer_min_height">48dp</dimen>
|
||||
|
@ -71,4 +72,6 @@
|
|||
<dimen name="location_sharing_locate_button_margin_vertical">16dp</dimen>
|
||||
<dimen name="location_sharing_locate_button_margin_horizontal">12dp</dimen>
|
||||
<dimen name="location_sharing_compass_button_margin_horizontal">8dp</dimen>
|
||||
<dimen name="location_sharing_live_duration_choice_margin_horizontal">12dp</dimen>
|
||||
<dimen name="location_sharing_live_duration_choice_margin_vertical">22dp</dimen>
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="AudioWaveformView">
|
||||
|
||||
<attr name="alignment" format="enum">
|
||||
<enum name="center" value="0" />
|
||||
<enum name="bottom" value="1" />
|
||||
<enum name="top" value="2" />
|
||||
</attr>
|
||||
<attr name="flow" format="enum">
|
||||
<enum name="leftToRight" value="0" />
|
||||
<enum name="rightToLeft" value="1" />
|
||||
</attr>
|
||||
<attr name="verticalPadding" format="dimension" />
|
||||
<attr name="horizontalPadding" format="dimension" />
|
||||
|
||||
<attr name="barWidth" format="dimension" />
|
||||
<attr name="barSpace" format="dimension" />
|
||||
<attr name="barMinHeight" format="dimension" />
|
||||
<attr name="isBarRounded" format="boolean" />
|
||||
</declare-styleable>
|
||||
</resources>
|
|
@ -9,6 +9,11 @@
|
|||
<item name="endIconTint">?vctr_content_secondary</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Vector.TextInputLayout.Username">
|
||||
<item name="endIconMode">clear_text</item>
|
||||
<item name="endIconTint">?vctr_content_secondary</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Vector.TextInputLayout.Form">
|
||||
<item name="boxStrokeColor">@color/form_edit_text_stroke_color_selector</item>
|
||||
<item name="android:textColorHint">@color/form_edit_text_hint_color_selector</item>
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
<resources>
|
||||
|
||||
<style name="VoicePlaybackWaveform">
|
||||
<item name="chunkColor">?vctr_content_secondary</item>
|
||||
<item name="chunkAlignTo">center</item>
|
||||
<item name="chunkMinHeight">1dp</item>
|
||||
<item name="chunkRoundedCorners">true</item>
|
||||
<item name="chunkSoftTransition">true</item>
|
||||
<item name="chunkSpace">2dp</item>
|
||||
<item name="chunkWidth">2dp</item>
|
||||
<item name="direction">rightToLeft</item>
|
||||
<item name="alignment">center</item>
|
||||
<item name="flow">leftToRight</item>
|
||||
<item name="verticalPadding">4dp</item>
|
||||
<item name="horizontalPadding">4dp</item>
|
||||
<item name="barWidth">2dp</item>
|
||||
<item name="barSpace">2dp</item>
|
||||
<item name="barMinHeight">1dp</item>
|
||||
<item name="isBarRounded">true</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -31,7 +31,7 @@ android {
|
|||
// that the app's state is completely cleared between tests.
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
|
||||
buildConfigField "String", "SDK_VERSION", "\"1.4.8\""
|
||||
buildConfigField "String", "SDK_VERSION", "\"1.4.11\""
|
||||
|
||||
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
||||
|
@ -166,7 +166,7 @@ dependencies {
|
|||
implementation libs.apache.commonsImaging
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.45'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.46'
|
||||
|
||||
testImplementation libs.tests.junit
|
||||
testImplementation 'org.robolectric:robolectric:4.7.3'
|
||||
|
|
|
@ -138,7 +138,7 @@ class WithHeldTests : InstrumentedTest {
|
|||
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun test_WithHeldNoOlm() {
|
||||
fun test_WithHeldNoOlm() {
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
val aliceSession = testData.firstSession
|
||||
val bobSession = testData.secondSession!!
|
||||
|
|
|
@ -58,12 +58,36 @@ fun Throwable.getRetryDelay(defaultValue: Long): Long {
|
|||
?: defaultValue
|
||||
}
|
||||
|
||||
fun Throwable.isUsernameInUse(): Boolean {
|
||||
return this is Failure.ServerError && error.code == MatrixError.M_USER_IN_USE
|
||||
}
|
||||
|
||||
fun Throwable.isInvalidUsername(): Boolean {
|
||||
return this is Failure.ServerError &&
|
||||
error.code == MatrixError.M_INVALID_USERNAME
|
||||
}
|
||||
|
||||
fun Throwable.isInvalidPassword(): Boolean {
|
||||
return this is Failure.ServerError &&
|
||||
error.code == MatrixError.M_FORBIDDEN &&
|
||||
error.message == "Invalid password"
|
||||
}
|
||||
|
||||
fun Throwable.isRegistrationDisabled(): Boolean {
|
||||
return this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN &&
|
||||
httpCode == HttpsURLConnection.HTTP_FORBIDDEN
|
||||
}
|
||||
|
||||
fun Throwable.isWeakPassword(): Boolean {
|
||||
return this is Failure.ServerError && error.code == MatrixError.M_WEAK_PASSWORD
|
||||
}
|
||||
|
||||
fun Throwable.isLoginEmailUnknown(): Boolean {
|
||||
return this is Failure.ServerError &&
|
||||
error.code == MatrixError.M_FORBIDDEN &&
|
||||
error.message.isEmpty()
|
||||
}
|
||||
|
||||
fun Throwable.isInvalidUIAAuth(): Boolean {
|
||||
return this is Failure.ServerError &&
|
||||
error.code == MatrixError.M_FORBIDDEN &&
|
||||
|
@ -104,8 +128,8 @@ fun Throwable.isRegistrationAvailabilityError(): Boolean {
|
|||
return this is Failure.ServerError &&
|
||||
httpCode == HttpsURLConnection.HTTP_BAD_REQUEST && /* 400 */
|
||||
(error.code == MatrixError.M_USER_IN_USE ||
|
||||
error.code == MatrixError.M_INVALID_USERNAME ||
|
||||
error.code == MatrixError.M_EXCLUSIVE)
|
||||
error.code == MatrixError.M_INVALID_USERNAME ||
|
||||
error.code == MatrixError.M_EXCLUSIVE)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -140,7 +140,6 @@ interface CryptoService {
|
|||
fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>>
|
||||
|
||||
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
||||
|
||||
fun removeSessionListener(listener: NewSessionListener)
|
||||
|
||||
fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest>
|
||||
|
|
|
@ -49,6 +49,7 @@ object EventType {
|
|||
const val STATE_ROOM_JOIN_RULES = "m.room.join_rules"
|
||||
const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access"
|
||||
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
|
||||
private const val STATE_ROOM_BEACON_INFO_PREFIX = "org.matrix.msc3489.beacon_info."
|
||||
|
||||
const val STATE_SPACE_CHILD = "m.space.child"
|
||||
|
||||
|
@ -120,4 +121,12 @@ object EventType {
|
|||
type == CALL_REJECT ||
|
||||
type == CALL_REPLACES
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an event type like org.matrix.msc3489.beacon_info.@userid:matrix.org.1648814272273
|
||||
*/
|
||||
fun generateBeaconInfoStateEventType(userId: String): String {
|
||||
val uniqueId = System.currentTimeMillis()
|
||||
return "$STATE_ROOM_BEACON_INFO_PREFIX$userId.$uniqueId"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,3 +46,5 @@ data class UnsignedData(
|
|||
@Json(name = "replaces_state") val replacesState: String? = null
|
||||
|
||||
)
|
||||
|
||||
fun UnsignedData?.isRedacted() = this?.redactedEvent != null
|
||||
|
|
|
@ -21,6 +21,7 @@ import android.net.Uri
|
|||
import androidx.lifecycle.LiveData
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
|
||||
|
@ -118,4 +119,17 @@ interface ProfileService {
|
|||
* Remove a 3Pid from the Matrix account.
|
||||
*/
|
||||
suspend fun deleteThreePid(threePid: ThreePid)
|
||||
|
||||
/**
|
||||
* Return a User object from a userId
|
||||
*/
|
||||
suspend fun getProfileAsUser(userId: String): User {
|
||||
return getProfile(userId).let { dict ->
|
||||
User(
|
||||
userId = userId,
|
||||
displayName = dict[DISPLAY_NAME_KEY] as? String,
|
||||
avatarUrl = dict[AVATAR_URL_KEY] as? String
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -242,4 +242,12 @@ interface RoomService {
|
|||
*/
|
||||
fun getFlattenRoomSummaryChildrenOfLive(spaceId: String?,
|
||||
memberships: List<Membership> = Membership.activeMemberships()): LiveData<List<RoomSummary>>
|
||||
|
||||
/**
|
||||
* Refreshes the RoomSummary LatestPreviewContent for the given @param roomId
|
||||
* If the roomId is null, all rooms are updated
|
||||
*
|
||||
* This is useful for refreshing summary content with encrypted messages after receiving new room keys
|
||||
*/
|
||||
fun refreshJoinedRoomSummaryPreviews(roomId: String?)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api.session.room.model.livelocation
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class BeaconInfo(
|
||||
@Json(name = "description") val description: String? = null,
|
||||
/**
|
||||
* Beacon should be considered as inactive after this timeout as milliseconds.
|
||||
*/
|
||||
@Json(name = "timeout") val timeout: Long? = null,
|
||||
/**
|
||||
* Should be set true to start sharing beacon.
|
||||
*/
|
||||
@Json(name = "live") val isLive: Boolean? = null
|
||||
)
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api.session.room.model.livelocation
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.api.session.room.model.message.LocationAsset
|
||||
import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class LiveLocationBeaconContent(
|
||||
/**
|
||||
* Indicates user's intent to share ephemeral location.
|
||||
*/
|
||||
@Json(name = "org.matrix.msc3489.beacon_info") val unstableBeaconInfo: BeaconInfo? = null,
|
||||
@Json(name = "m.beacon_info") val beaconInfo: BeaconInfo? = null,
|
||||
/**
|
||||
* Beacon creation timestamp.
|
||||
*/
|
||||
@Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null,
|
||||
@Json(name = "m.ts") val timestampAsMilliseconds: Long? = null,
|
||||
/**
|
||||
* Live location asset type.
|
||||
*/
|
||||
@Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset = LocationAsset(LocationAssetType.SELF),
|
||||
@Json(name = "m.asset") val locationAsset: LocationAsset? = null
|
||||
) {
|
||||
|
||||
fun getBestBeaconInfo() = beaconInfo ?: unstableBeaconInfo
|
||||
|
||||
fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds
|
||||
|
||||
fun getBestLocationAsset() = locationAsset ?: unstableLocationAsset
|
||||
}
|
|
@ -137,8 +137,7 @@ internal abstract class CryptoModule {
|
|||
@JvmStatic
|
||||
@Provides
|
||||
@CryptoDatabase
|
||||
fun providesClearCacheTask(@CryptoDatabase
|
||||
realmConfiguration: RealmConfiguration): ClearCacheTask {
|
||||
fun providesClearCacheTask(@CryptoDatabase realmConfiguration: RealmConfiguration): ClearCacheTask {
|
||||
return RealmClearCacheTask(realmConfiguration)
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,15 @@
|
|||
*/
|
||||
package org.matrix.android.sdk.internal.crypto
|
||||
|
||||
/**
|
||||
* This listener notifies on new Megolm sessions being created
|
||||
*/
|
||||
interface NewSessionListener {
|
||||
|
||||
/**
|
||||
* @param roomId the room id where the new Megolm session has been created for, may be null when importing from external sessions
|
||||
* @param senderKey the sender key of the device which the Megolm session is shared with
|
||||
* @param sessionId the session id of the Megolm session
|
||||
*/
|
||||
fun onNewSession(roomId: String?, senderKey: String, sessionId: String)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
|||
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
||||
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
|
||||
import org.matrix.android.sdk.internal.crypto.RoomDecryptorProvider
|
||||
import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmDecryption
|
||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
|
@ -85,7 +86,11 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
|
|||
outgoingGossipingRequestManager.cancelRoomKeyRequest(roomKeyRequestBody)
|
||||
|
||||
// Have another go at decrypting events sent with this session
|
||||
decrypting.onNewSession(megolmSessionData.senderKey!!, sessionId!!)
|
||||
when (decrypting) {
|
||||
is MXMegolmDecryption -> {
|
||||
decrypting.onNewSession(megolmSessionData.roomId, megolmSessionData.senderKey!!, sessionId!!)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## importRoomKeys() : onNewSession failed")
|
||||
}
|
||||
|
|
|
@ -45,14 +45,6 @@ internal interface IMXDecrypting {
|
|||
*/
|
||||
fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {}
|
||||
|
||||
/**
|
||||
* Check if the some messages can be decrypted with a new session
|
||||
*
|
||||
* @param senderKey the session sender key
|
||||
* @param sessionId the session id
|
||||
*/
|
||||
fun onNewSession(senderKey: String, sessionId: String) {}
|
||||
|
||||
/**
|
||||
* Determine if we have the keys necessary to respond to a room key request
|
||||
*
|
||||
|
|
|
@ -318,19 +318,20 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
|
||||
outgoingGossipingRequestManager.cancelRoomKeyRequest(content)
|
||||
|
||||
onNewSession(senderKey, roomKeyContent.sessionId)
|
||||
onNewSession(roomKeyContent.roomId, senderKey, roomKeyContent.sessionId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the some messages can be decrypted with a new session
|
||||
*
|
||||
* @param roomId the room id where the new Megolm session has been created for, may be null when importing from external sessions
|
||||
* @param senderKey the session sender key
|
||||
* @param sessionId the session id
|
||||
*/
|
||||
override fun onNewSession(senderKey: String, sessionId: String) {
|
||||
fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
|
||||
Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey")
|
||||
newSessionListener?.onNewSession(null, senderKey, sessionId)
|
||||
newSessionListener?.onNewSession(roomId, senderKey, sessionId)
|
||||
}
|
||||
|
||||
override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean {
|
||||
|
|
|
@ -52,7 +52,7 @@ import timber.log.Timber
|
|||
import javax.inject.Inject
|
||||
|
||||
internal class UpdateTrustWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
|
||||
SessionSafeCoroutineWorker<UpdateTrustWorker.Params>(context, params, sessionManager, Params::class.java) {
|
||||
SessionSafeCoroutineWorker<UpdateTrustWorker.Params>(context, params, sessionManager, Params::class.java) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
|
|
|
@ -130,7 +130,7 @@ inline fun <T> MXUsersDevicesMap<T>.forEach(action: (String, String, T) -> Unit)
|
|||
}
|
||||
}
|
||||
|
||||
internal fun <T> MXUsersDevicesMap<T>.toDebugString() =
|
||||
internal fun <T> MXUsersDevicesMap<T>.toDebugString() =
|
||||
map.entries.joinToString { "${it.key} [${it.value.keys.joinToString { it }}]" }
|
||||
|
||||
internal fun <T> MXUsersDevicesMap<T>.toDebugCount() =
|
||||
|
|
|
@ -70,7 +70,7 @@ object HkdfSha256 {
|
|||
T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
|
||||
T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
|
||||
...
|
||||
*/
|
||||
*/
|
||||
val n = ceil(outputLength.toDouble() / HASH_LEN.toDouble()).toInt()
|
||||
|
||||
var stepHash = ByteArray(0) // T(0) empty string (zero length)
|
||||
|
|
|
@ -364,14 +364,14 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
dispatchRequestAdded(pendingVerificationRequest)
|
||||
|
||||
/*
|
||||
* After the m.key.verification.ready event is sent, either party can send an m.key.verification.start event
|
||||
* to begin the verification.
|
||||
* If both parties send an m.key.verification.start event, and they both specify the same verification method,
|
||||
* then the event sent by the user whose user ID is the smallest is used, and the other m.key.verification.start
|
||||
* event is ignored.
|
||||
* In the case of a single user verifying two of their devices, the device ID is compared instead.
|
||||
* If both parties send an m.key.verification.start event, but they specify different verification methods,
|
||||
* the verification should be cancelled with a code of m.unexpected_message.
|
||||
* After the m.key.verification.ready event is sent, either party can send an m.key.verification.start event
|
||||
* to begin the verification.
|
||||
* If both parties send an m.key.verification.start event, and they both specify the same verification method,
|
||||
* then the event sent by the user whose user ID is the smallest is used, and the other m.key.verification.start
|
||||
* event is ignored.
|
||||
* In the case of a single user verifying two of their devices, the device ID is compared instead.
|
||||
* If both parties send an m.key.verification.start event, but they specify different verification methods,
|
||||
* the verification should be cancelled with a code of m.unexpected_message.
|
||||
*/
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64Safe
|
|||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationTransaction
|
||||
import org.matrix.android.sdk.internal.crypto.verification.ValidVerificationInfoStart
|
||||
import org.matrix.android.sdk.internal.util.exhaustive
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DefaultQrCodeVerificationTransaction(
|
||||
|
@ -129,7 +128,7 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
// Nothing special here, we will send a reciprocate start event, and then the other session will trust it's view of the MSK
|
||||
}
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
val toVerifyDeviceIds = mutableListOf<String>()
|
||||
|
||||
|
@ -174,7 +173,7 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
Unit
|
||||
}
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
if (!canTrustOtherUserMasterKey && toVerifyDeviceIds.isEmpty()) {
|
||||
// Nothing to verify
|
||||
|
@ -272,6 +271,7 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
// I now know that i can trust my MSK
|
||||
trust(true, emptyList(), true)
|
||||
}
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,9 +16,12 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.database.helper
|
||||
|
||||
import com.squareup.moshi.JsonDataException
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.Sort
|
||||
import org.matrix.android.sdk.api.session.events.model.UnsignedData
|
||||
import org.matrix.android.sdk.api.session.events.model.isRedacted
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
|
||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||
|
@ -33,6 +36,8 @@ import org.matrix.android.sdk.internal.database.query.findIncludingEvent
|
|||
import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.database.query.whereRoomId
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import timber.log.Timber
|
||||
|
||||
private typealias Summary = Pair<Int, TimelineEventEntity>?
|
||||
|
||||
|
@ -48,14 +53,14 @@ internal fun Map<String, EventEntity>.updateThreadSummaryIfNeeded(
|
|||
for ((rootThreadEventId, eventEntity) in this) {
|
||||
eventEntity.threadSummaryInThread(eventEntity.realm, rootThreadEventId, chunkEntity)?.let { threadSummary ->
|
||||
|
||||
val numberOfMessages = threadSummary.first
|
||||
val inThreadMessages = threadSummary.first
|
||||
val latestEventInThread = threadSummary.second
|
||||
|
||||
// If this is a thread message, find its root event if exists
|
||||
val rootThreadEvent = if (eventEntity.isThread()) eventEntity.findRootThreadEvent() else eventEntity
|
||||
|
||||
rootThreadEvent?.markEventAsRoot(
|
||||
threadsCounted = numberOfMessages,
|
||||
inThreadMessages = inThreadMessages,
|
||||
latestMessageTimelineEventEntity = latestEventInThread
|
||||
)
|
||||
}
|
||||
|
@ -81,28 +86,27 @@ internal fun EventEntity.findRootThreadEvent(): EventEntity? =
|
|||
* Mark or update the current event a root thread event
|
||||
*/
|
||||
internal fun EventEntity.markEventAsRoot(
|
||||
threadsCounted: Int,
|
||||
inThreadMessages: Int,
|
||||
latestMessageTimelineEventEntity: TimelineEventEntity?) {
|
||||
isRootThread = true
|
||||
numberOfThreads = threadsCounted
|
||||
numberOfThreads = inThreadMessages
|
||||
threadSummaryLatestMessage = latestMessageTimelineEventEntity
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of threads for the provided root thread eventId, and finds the latest event message
|
||||
* note: Redactions are handled by RedactionEventProcessor
|
||||
* @param rootThreadEventId The root eventId that will find the number of threads
|
||||
* @return A ThreadSummary containing the counted threads and the latest event message
|
||||
*/
|
||||
internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary {
|
||||
// Number of messages
|
||||
val messages = TimelineEventEntity
|
||||
.whereRoomId(realm, roomId = roomId)
|
||||
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId)
|
||||
.distinct(TimelineEventEntityFields.ROOT.EVENT_ID)
|
||||
.count()
|
||||
.toInt()
|
||||
val inThreadMessages = countInThreadMessages(
|
||||
realm = realm,
|
||||
roomId = roomId,
|
||||
rootThreadEventId = rootThreadEventId
|
||||
)
|
||||
|
||||
if (messages <= 0) return null
|
||||
if (inThreadMessages <= 0) return null
|
||||
|
||||
// Find latest thread event, we know it exists
|
||||
var chunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: chunkEntity ?: return null
|
||||
|
@ -124,9 +128,38 @@ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId:
|
|||
|
||||
result ?: return null
|
||||
|
||||
return Summary(messages, result)
|
||||
return Summary(inThreadMessages, result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of thread replies in the main timeline thread summary,
|
||||
* with respect to redactions.
|
||||
*/
|
||||
internal fun countInThreadMessages(realm: Realm, roomId: String, rootThreadEventId: String): Int =
|
||||
TimelineEventEntity
|
||||
.whereRoomId(realm, roomId = roomId)
|
||||
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId)
|
||||
.distinct(TimelineEventEntityFields.ROOT.EVENT_ID)
|
||||
.findAll()
|
||||
.filterNot { timelineEvent ->
|
||||
timelineEvent.root
|
||||
?.unsignedData
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?.toUnsignedData()
|
||||
.isRedacted()
|
||||
}.size
|
||||
|
||||
/**
|
||||
* Mapping string to UnsignedData using Moshi
|
||||
*/
|
||||
private fun String.toUnsignedData(): UnsignedData? =
|
||||
try {
|
||||
MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).fromJson(this)
|
||||
} catch (ex: JsonDataException) {
|
||||
Timber.e(ex, "Failed to parse UnsignedData")
|
||||
null
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets compare them in case user is moving forward in the timeline and we cannot know the
|
||||
* exact chunk sequence while currentChunk is not yet committed in the DB
|
||||
|
|
|
@ -44,6 +44,7 @@ internal open class EventEntity(@Index var eventId: String = "",
|
|||
// Thread related, no need to create a new Entity for performance
|
||||
@Index var isRootThread: Boolean = false,
|
||||
@Index var rootThreadEventId: String? = null,
|
||||
// Number messages within the thread
|
||||
var numberOfThreads: Int = 0,
|
||||
var threadSummaryLatestMessage: TimelineEventEntity? = null
|
||||
) : RealmObject() {
|
||||
|
|
|
@ -49,6 +49,10 @@ internal fun RoomSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: Strin
|
|||
return where(realm, roomId).findFirst() ?: realm.createObject(roomId)
|
||||
}
|
||||
|
||||
internal fun RoomSummaryEntity.Companion.getOrNull(realm: Realm, roomId: String): RoomSummaryEntity? {
|
||||
return where(realm, roomId).findFirst()
|
||||
}
|
||||
|
||||
internal fun RoomSummaryEntity.Companion.getDirectRooms(realm: Realm,
|
||||
excludeRoomIds: Set<String>? = null): RealmResults<RoomSummaryEntity> {
|
||||
return RoomSummaryEntity.where(realm)
|
||||
|
|
|
@ -21,3 +21,11 @@ fun <A> Result<A>.foldToCallback(callback: MatrixCallback<A>): Unit = fold(
|
|||
{ callback.onSuccess(it) },
|
||||
{ callback.onFailure(it) }
|
||||
)
|
||||
|
||||
@Suppress("UNCHECKED_CAST") // We're casting null failure results to R
|
||||
inline fun <T, R> Result<T>.andThen(block: (T) -> Result<R>): Result<R> {
|
||||
return when (val result = getOrNull()) {
|
||||
null -> this as Result<R>
|
||||
else -> block(result)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
|
|||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultCacheService @Inject constructor(@SessionDatabase
|
||||
private val clearCacheTask: ClearCacheTask,
|
||||
private val taskExecutor: TaskExecutor
|
||||
internal class DefaultCacheService @Inject constructor(
|
||||
@SessionDatabase private val clearCacheTask: ClearCacheTask,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : CacheService {
|
||||
|
||||
override suspend fun clearCache() {
|
||||
|
|
|
@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.Transformations
|
||||
import androidx.paging.PagedList
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
import org.matrix.android.sdk.api.session.room.RoomService
|
||||
|
@ -32,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
|||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
|
@ -51,6 +53,7 @@ import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask
|
|||
import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
|
||||
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
|
||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
||||
import org.matrix.android.sdk.internal.util.fetchCopied
|
||||
import javax.inject.Inject
|
||||
|
@ -69,6 +72,7 @@ internal class DefaultRoomService @Inject constructor(
|
|||
private val roomSummaryDataSource: RoomSummaryDataSource,
|
||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
||||
private val leaveRoomTask: LeaveRoomTask,
|
||||
private val roomSummaryUpdater: RoomSummaryUpdater
|
||||
) : RoomService {
|
||||
|
||||
override suspend fun createRoom(createRoomParams: CreateRoomParams): String {
|
||||
|
@ -92,6 +96,23 @@ internal class DefaultRoomService @Inject constructor(
|
|||
return roomSummaryDataSource.getRoomSummaries(queryParams, sortOrder)
|
||||
}
|
||||
|
||||
override fun refreshJoinedRoomSummaryPreviews(roomId: String?) {
|
||||
val roomSummaries = getRoomSummaries(roomSummaryQueryParams {
|
||||
if (roomId != null) {
|
||||
this.roomId = QueryStringValue.Equals(roomId)
|
||||
}
|
||||
memberships = listOf(Membership.JOIN)
|
||||
})
|
||||
|
||||
if (roomSummaries.isNotEmpty()) {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
roomSummaries.forEach {
|
||||
roomSummaryUpdater.refreshLatestPreviewContent(realm, it.roomId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||
sortOrder: RoomSortOrder): LiveData<List<RoomSummary>> {
|
||||
return roomSummaryDataSource.getRoomSummariesLive(queryParams, sortOrder)
|
||||
|
|
|
@ -482,46 +482,39 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||
roomId: String,
|
||||
isLocalEcho: Boolean) {
|
||||
val pollEventId = content.relatesTo?.eventId ?: return
|
||||
|
||||
val pollOwnerId = getPollEvent(roomId, pollEventId)?.root?.senderId
|
||||
val isPollOwner = pollOwnerId == event.senderId
|
||||
|
||||
val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
|
||||
?.content?.toModel<PowerLevelsContent>()
|
||||
?.let { PowerLevelsHelper(it) }
|
||||
|
||||
if (!isPollOwner && !powerLevelsHelper?.isUserAbleToRedact(event.senderId ?: "").orFalse()) {
|
||||
Timber.v("## Received poll.end event $pollEventId but user ${event.senderId} doesn't have enough power level in room $roomId")
|
||||
return
|
||||
}
|
||||
|
||||
var existing = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst()
|
||||
if (existing == null) {
|
||||
var existingPoll = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst()
|
||||
if (existingPoll == null) {
|
||||
Timber.v("## POLL creating new relation summary for $pollEventId")
|
||||
existing = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId)
|
||||
existingPoll = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId)
|
||||
}
|
||||
|
||||
// we have it
|
||||
val existingPollSummary = existing.pollResponseSummary
|
||||
val existingPollSummary = existingPoll.pollResponseSummary
|
||||
?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also {
|
||||
existing.pollResponseSummary = it
|
||||
existingPoll.pollResponseSummary = it
|
||||
}
|
||||
|
||||
if (existingPollSummary.closedTime != null) {
|
||||
Timber.v("## Received poll.end event for already ended poll $pollEventId")
|
||||
return
|
||||
}
|
||||
|
||||
val txId = event.unsignedData?.transactionId
|
||||
existingPollSummary.closedTime = event.originServerTs
|
||||
|
||||
// is it a remote echo?
|
||||
if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) {
|
||||
// ok it has already been managed
|
||||
Timber.v("## POLL Receiving remote echo of response eventId:$pollEventId")
|
||||
existingPollSummary.sourceLocalEchoEvents.remove(txId)
|
||||
existingPollSummary.sourceEvents.add(event.eventId)
|
||||
return
|
||||
}
|
||||
|
||||
existingPollSummary.closedTime = event.originServerTs
|
||||
}
|
||||
|
||||
private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? {
|
||||
|
|
|
@ -21,11 +21,14 @@ import org.matrix.android.sdk.api.session.events.model.Event
|
|||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||
import org.matrix.android.sdk.api.session.events.model.UnsignedData
|
||||
import org.matrix.android.sdk.internal.database.helper.countInThreadMessages
|
||||
import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent
|
||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||
import org.matrix.android.sdk.internal.database.mapper.EventMapper
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.query.findWithSenderMembershipEvent
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
|
@ -89,6 +92,8 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
|
|||
eventToPrune.unsignedData = MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(modified)
|
||||
eventToPrune.decryptionResultJson = null
|
||||
eventToPrune.decryptionErrorCode = null
|
||||
|
||||
handleTimelineThreadSummaryIfNeeded(realm, eventToPrune, isLocalEcho)
|
||||
}
|
||||
// EventType.REACTION -> {
|
||||
// eventRelationsAggregationUpdater.handleReactionRedact(eventToPrune, realm, userId)
|
||||
|
@ -104,6 +109,39 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the number of threads in the main timeline thread summary,
|
||||
* with respect to redactions.
|
||||
*/
|
||||
private fun handleTimelineThreadSummaryIfNeeded(
|
||||
realm: Realm,
|
||||
eventToPrune: EventEntity,
|
||||
isLocalEcho: Boolean,
|
||||
) {
|
||||
if (eventToPrune.isThread() && !isLocalEcho) {
|
||||
val roomId = eventToPrune.roomId
|
||||
val rootThreadEvent = eventToPrune.findRootThreadEvent() ?: return
|
||||
val rootThreadEventId = eventToPrune.rootThreadEventId ?: return
|
||||
|
||||
val inThreadMessages = countInThreadMessages(
|
||||
realm = realm,
|
||||
roomId = roomId,
|
||||
rootThreadEventId = rootThreadEventId
|
||||
)
|
||||
|
||||
rootThreadEvent.numberOfThreads = inThreadMessages
|
||||
if (inThreadMessages == 0) {
|
||||
// We should also clear the thread summary list
|
||||
rootThreadEvent.isRootThread = false
|
||||
rootThreadEvent.threadSummaryLatestMessage = null
|
||||
ThreadSummaryEntity
|
||||
.where(realm, roomId = roomId, rootThreadEventId)
|
||||
.findFirst()
|
||||
?.deleteFromRealm()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeAllowedKeys(type: String): List<String> {
|
||||
// Add filtered content, allowed keys in content depends on the event type
|
||||
return when (type) {
|
||||
|
|
|
@ -340,6 +340,7 @@ internal class RoomSummaryDataSource @Inject constructor(
|
|||
.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0).or()
|
||||
.equalTo(RoomSummaryEntityFields.MARKED_UNREAD, true).endGroup()
|
||||
RoomCategoryFilter.ALL -> Unit // nop
|
||||
null -> Unit
|
||||
}
|
||||
|
||||
// Timber.w("VAL: activeSpaceId : ${queryParams.activeSpaceId}")
|
||||
|
|
|
@ -65,7 +65,6 @@ import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataD
|
|||
import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
|
||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||
import org.matrix.android.sdk.internal.session.room.relationship.RoomChildRelationInfo
|
||||
import org.matrix.android.sdk.internal.util.Normalizer
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.min
|
||||
|
@ -77,8 +76,18 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
private val roomAvatarResolver: RoomAvatarResolver,
|
||||
private val eventDecryptor: EventDecryptor,
|
||||
private val crossSigningService: DefaultCrossSigningService,
|
||||
private val roomAccountDataDataSource: RoomAccountDataDataSource,
|
||||
private val normalizer: Normalizer) {
|
||||
private val roomAccountDataDataSource: RoomAccountDataDataSource
|
||||
) {
|
||||
|
||||
fun refreshLatestPreviewContent(realm: Realm, roomId: String) {
|
||||
val roomSummaryEntity = RoomSummaryEntity.getOrNull(realm, roomId)
|
||||
if (roomSummaryEntity != null) {
|
||||
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScAll(realm, roomId)
|
||||
val latestPreviewableContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||
val latestPreviewableOriginalContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScOriginalContent(realm, roomId)
|
||||
attemptToDecryptLatestPreviewables(latestPreviewableEvent, latestPreviewableContentEvent, latestPreviewableOriginalContentEvent)
|
||||
}
|
||||
}
|
||||
|
||||
fun update(realm: Realm,
|
||||
roomId: String,
|
||||
|
@ -144,6 +153,11 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
val lastActivityFromEvent = scLatestPreviewableEvent?.root?.originServerTs
|
||||
if (lastActivityFromEvent != null) {
|
||||
roomSummaryEntity.lastActivityTime = lastActivityFromEvent
|
||||
attemptToDecryptLatestPreviewables(
|
||||
roomSummaryEntity.latestPreviewableEvent,
|
||||
roomSummaryEntity.latestPreviewableContentEvent,
|
||||
roomSummaryEntity.latestPreviewableOriginalContentEvent
|
||||
)
|
||||
}
|
||||
|
||||
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 ||
|
||||
|
@ -185,18 +199,6 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
}
|
||||
roomSummaryEntity.updateHasFailedSending()
|
||||
|
||||
val root = latestPreviewableOriginalContentEvent?.root
|
||||
if (root?.type == EventType.ENCRYPTED && root.decryptionResultJson == null) {
|
||||
Timber.v("Should decrypt ${latestPreviewableOriginalContentEvent.eventId}")
|
||||
// mmm i want to decrypt now or is it ok to do it async?
|
||||
tryOrNull {
|
||||
runBlocking {
|
||||
eventDecryptor.decryptEvent(root.asDomain(), "")
|
||||
}
|
||||
}
|
||||
?.let { root.setDecryptionResult(it) }
|
||||
}
|
||||
|
||||
if (updateMembers) {
|
||||
val otherRoomMembers = RoomMemberHelper(realm, roomId)
|
||||
.queryActiveRoomMembersEvent()
|
||||
|
@ -228,6 +230,32 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun TimelineEventEntity.attemptToDecrypt() {
|
||||
when (val root = this.root) {
|
||||
null -> {
|
||||
Timber.v("Decryption skipped due to missing root event $eventId")
|
||||
}
|
||||
else -> {
|
||||
if (root.type == EventType.ENCRYPTED && root.decryptionResultJson == null) {
|
||||
Timber.v("Should decrypt $eventId")
|
||||
tryOrNull {
|
||||
runBlocking { eventDecryptor.decryptEvent(root.asDomain(), "") }
|
||||
}?.let { root.setDecryptionResult(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun attemptToDecryptLatestPreviewables(vararg events: TimelineEventEntity?) {
|
||||
val attempted = mutableListOf<String>()
|
||||
events.forEach { event ->
|
||||
if (event != null && event.eventId !in attempted) {
|
||||
attempted.add(event.eventId)
|
||||
event.attemptToDecrypt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun RoomSummaryEntity.updateHasFailedSending() {
|
||||
hasFailedSending = TimelineEventEntity.findAllInRoomWithSendStates(realm, roomId, SendState.HAS_FAILED_STATES).isNotEmpty()
|
||||
}
|
||||
|
|
|
@ -45,9 +45,11 @@ internal class RealmSendingEventsDataSource(
|
|||
private var frozenSendingTimelineEvents: RealmList<TimelineEventEntity>? = null
|
||||
|
||||
private val sendingTimelineEventsListener = RealmChangeListener<RealmList<TimelineEventEntity>> { events ->
|
||||
uiEchoManager.onSentEventsInDatabase(events.map { it.eventId })
|
||||
updateFrozenResults(events)
|
||||
onEventsUpdated(false)
|
||||
if (events.isValid) {
|
||||
uiEchoManager.onSentEventsInDatabase(events.map { it.eventId })
|
||||
updateFrozenResults(events)
|
||||
onEventsUpdated(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.sync
|
|||
|
||||
import android.os.SystemClock
|
||||
import okhttp3.ResponseBody
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.initsync.InitSyncStep
|
||||
|
@ -104,7 +105,11 @@ internal class DefaultSyncTask @Inject constructor(
|
|||
val isInitialSync = token == null
|
||||
if (isInitialSync) {
|
||||
// We might want to get the user information in parallel too
|
||||
userStore.createOrUpdate(userId)
|
||||
val user = tryOrNull { session.getProfileAsUser(userId) }
|
||||
userStore.createOrUpdate(
|
||||
userId = userId,
|
||||
displayName = user?.displayName,
|
||||
avatarUrl = user?.avatarUrl)
|
||||
defaultSyncStatusService.startRoot(InitSyncStep.ImportingAccount, 100)
|
||||
}
|
||||
// Maybe refresh the homeserver capabilities data we know
|
||||
|
|
|
@ -24,8 +24,9 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
|
|||
import org.matrix.android.sdk.internal.session.sync.model.accountdata.DirectMessagesContent
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DirectChatsHelper @Inject constructor(@SessionDatabase
|
||||
private val realmConfiguration: RealmConfiguration) {
|
||||
internal class DirectChatsHelper @Inject constructor(
|
||||
@SessionDatabase private val realmConfiguration: RealmConfiguration
|
||||
) {
|
||||
|
||||
/**
|
||||
* @return a map of userId <-> list of roomId
|
||||
|
|
|
@ -88,10 +88,10 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh
|
|||
}
|
||||
|
||||
/*
|
||||
* *********************************************************************************************
|
||||
* Message sending methods
|
||||
* *********************************************************************************************
|
||||
*/
|
||||
* *********************************************************************************************
|
||||
* Message sending methods
|
||||
* *********************************************************************************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Send a boolean response
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.session.pushers
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.amshove.kluent.internal.assertFailsWith
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Test
|
||||
|
@ -39,6 +40,7 @@ private val A_JSON_PUSHER = JsonPusher(
|
|||
data = JsonPusherData(brand = "Element")
|
||||
)
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class DefaultAddPusherTaskTest {
|
||||
|
||||
private val pushersAPI = FakePushersAPI()
|
||||
|
@ -55,7 +57,7 @@ class DefaultAddPusherTaskTest {
|
|||
fun `given no persisted pusher when adding Pusher then updates api and inserts result with Registered state`() {
|
||||
monarchy.givenWhereReturns<PusherEntity>(result = null)
|
||||
|
||||
runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
|
||||
runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
|
||||
|
||||
pushersAPI.verifySetPusher(A_JSON_PUSHER)
|
||||
monarchy.verifyInsertOrUpdate<PusherEntity> {
|
||||
|
@ -70,7 +72,7 @@ class DefaultAddPusherTaskTest {
|
|||
val realmResult = PusherEntity(appDisplayName = null)
|
||||
monarchy.givenWhereReturns(result = realmResult)
|
||||
|
||||
runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
|
||||
runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
|
||||
|
||||
pushersAPI.verifySetPusher(A_JSON_PUSHER)
|
||||
|
||||
|
@ -85,7 +87,7 @@ class DefaultAddPusherTaskTest {
|
|||
pushersAPI.givenSetPusherErrors(SocketException())
|
||||
|
||||
assertFailsWith<SocketException> {
|
||||
runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
|
||||
runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
|
||||
}
|
||||
|
||||
realmResult.state shouldBeEqualTo PusherState.FAILED_TO_REGISTER
|
||||
|
@ -97,7 +99,7 @@ class DefaultAddPusherTaskTest {
|
|||
pushersAPI.givenSetPusherErrors(SocketException())
|
||||
|
||||
assertFailsWith<SocketException> {
|
||||
runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
|
||||
runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
package org.matrix.android.sdk.internal.session.space
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runBlockingTest
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Test
|
||||
|
@ -35,7 +35,7 @@ internal class DefaultResolveSpaceInfoTaskTest {
|
|||
private val resolveSpaceInfoTask = DefaultResolveSpaceInfoTask(spaceApi.instance, globalErrorReceiver)
|
||||
|
||||
@Test
|
||||
fun `given stable endpoint works, when execute, then return stable api data`() = runBlockingTest {
|
||||
fun `given stable endpoint works, when execute, then return stable api data`() = runTest {
|
||||
spaceApi.givenStableEndpointReturns(response)
|
||||
|
||||
val result = resolveSpaceInfoTask.execute(spaceApi.params)
|
||||
|
@ -44,7 +44,7 @@ internal class DefaultResolveSpaceInfoTaskTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `given stable endpoint fails, when execute, then fallback to unstable endpoint`() = runBlockingTest {
|
||||
fun `given stable endpoint fails, when execute, then fallback to unstable endpoint`() = runTest {
|
||||
spaceApi.givenStableEndpointThrows(httpException)
|
||||
spaceApi.givenUnstableEndpointReturns(response)
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import kotlinx.coroutines.asCoroutineDispatcher
|
|||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.MatrixTest
|
||||
|
@ -51,7 +51,7 @@ class CoroutineSequencersTest : MatrixTest {
|
|||
.also { results.add(it) }
|
||||
}
|
||||
)
|
||||
runBlocking {
|
||||
runTest {
|
||||
jobs.joinAll()
|
||||
}
|
||||
assertEquals(3, results.size)
|
||||
|
@ -81,7 +81,7 @@ class CoroutineSequencersTest : MatrixTest {
|
|||
.also { results.add(it) }
|
||||
}
|
||||
)
|
||||
runBlocking {
|
||||
runTest {
|
||||
jobs.joinAll()
|
||||
}
|
||||
assertEquals(3, results.size)
|
||||
|
@ -109,7 +109,7 @@ class CoroutineSequencersTest : MatrixTest {
|
|||
)
|
||||
// We are canceling the second job
|
||||
jobs[1].cancel()
|
||||
runBlocking {
|
||||
runTest {
|
||||
jobs.joinAll()
|
||||
}
|
||||
assertEquals(2, results.size)
|
||||
|
|
17
tools/dependencycheck/suppressions.xml
Normal file
17
tools/dependencycheck/suppressions.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
|
||||
<suppress until="2023-01-01Z">
|
||||
<notes><![CDATA[
|
||||
file name: ktlint-reporter-checkstyle-0.45.1.jar
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/com\.pinterest\.ktlint/ktlint\-reporter\-checkstyle@.*$</packageUrl>
|
||||
<cve>CVE-2019-10782</cve>
|
||||
</suppress>
|
||||
<suppress until="2023-01-01Z">
|
||||
<notes><![CDATA[
|
||||
file name: ktlint-reporter-checkstyle-0.45.1.jar
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/com\.pinterest\.ktlint/ktlint\-reporter\-checkstyle@.*$</packageUrl>
|
||||
<cve>CVE-2019-9658</cve>
|
||||
</suppress>
|
||||
</suppressions>
|
|
@ -17,6 +17,9 @@ cd ..
|
|||
rm -rf jitsi-meet
|
||||
git clone https://github.com/jitsi/jitsi-meet
|
||||
|
||||
# Android SDK
|
||||
export ANDROID_SDK_ROOT=~/Library/Android/sdk
|
||||
|
||||
# We want a libre build!
|
||||
export LIBRE_BUILD=true
|
||||
|
||||
|
@ -25,8 +28,9 @@ cd jitsi-meet
|
|||
# This is commit after version 2.2.2, which does not compile
|
||||
# git checkout 5a934c071a5cbe64de275a25d0ed62d8193cdd03
|
||||
|
||||
# Version android-sdk-3.10.0, commit 99e56e229dfa3c490096e37c3e5b76d2a3f23e32
|
||||
git checkout android-sdk-3.10.0
|
||||
# Changelog: https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md
|
||||
|
||||
git checkout android-sdk-5.0.2
|
||||
|
||||
echo
|
||||
echo "##################################################"
|
||||
|
|
|
@ -7,7 +7,6 @@ import com.airbnb.mvrx.ViewModelContext
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.assisted.AssistedFactory
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
|
||||
<#if createViewEvents>
|
||||
|
@ -42,6 +41,6 @@ class ${viewModelClass} @AssistedInject constructor(@Assisted initialState: ${vi
|
|||
override fun handle(action: ${actionClass}) {
|
||||
when (action) {
|
||||
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
11
vector-config/src/main/res/values/config-features.xml
Executable file
11
vector-config/src/main/res/values/config-features.xml
Executable file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- This file contains values to show or hide some features, that are not available from
|
||||
settings & labs. Values here are mainly modified by developers and are not exposed
|
||||
to the users.
|
||||
-->
|
||||
|
||||
<bool name="feature_threads_beta_feedback_enabled">true</bool>
|
||||
|
||||
</resources>
|
6
vector-config/src/main/res/values/urls.xml
Normal file
6
vector-config/src/main/res/values/urls.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- This file contains url values-->
|
||||
|
||||
<string name="threads_learn_more_url" translatable="false">https://element.io/help#threads</string>
|
||||
</resources>
|
|
@ -18,7 +18,7 @@ ext.versionMinor = 4
|
|||
// Note: even values are reserved for regular release, odd values for hotfix release.
|
||||
// When creating a hotfix, you should decrease the value, since the current value
|
||||
// is the value for the next regular release.
|
||||
ext.versionPatch = 8
|
||||
ext.versionPatch = 11
|
||||
|
||||
ext.scVersion = 50
|
||||
|
||||
|
@ -384,7 +384,7 @@ dependencies {
|
|||
implementation 'com.facebook.stetho:stetho:1.6.0'
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.45'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.46'
|
||||
|
||||
// FlowBinding
|
||||
implementation libs.github.flowBinding
|
||||
|
@ -424,7 +424,6 @@ dependencies {
|
|||
implementation 'jp.wasabeef:glide-transformations:4.3.0'
|
||||
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12'
|
||||
implementation 'com.github.hyuwah:DraggableView:1.0.0'
|
||||
implementation 'com.github.Armen101:AudioRecordView:1.0.5'
|
||||
|
||||
// Custom Tab
|
||||
implementation 'androidx.browser:browser:1.4.0'
|
||||
|
@ -486,10 +485,10 @@ dependencies {
|
|||
// WebRTC
|
||||
// org.webrtc:google-webrtc is for development purposes only
|
||||
// implementation 'org.webrtc:google-webrtc:1.0.+'
|
||||
implementation('com.facebook.react:react-native-webrtc:1.92.1-jitsi-9093212@aar')
|
||||
implementation('com.facebook.react:react-native-webrtc:1.94.2-jitsi-10227332@aar')
|
||||
|
||||
// Jitsi
|
||||
implementation('org.jitsi.react:jitsi-meet-sdk:3.10.0') {
|
||||
implementation('org.jitsi.react:jitsi-meet-sdk:5.0.2') {
|
||||
exclude group: 'com.google.firebase'
|
||||
exclude group: 'com.google.android.gms'
|
||||
exclude group: 'com.android.installreferrer'
|
||||
|
|
|
@ -29,6 +29,7 @@ import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
|
|||
import com.adevinta.android.barista.interaction.BaristaEditTextInteractions.writeTo
|
||||
import im.vector.app.R
|
||||
import im.vector.app.espresso.tools.waitUntilViewVisible
|
||||
import im.vector.app.features.DefaultVectorFeatures
|
||||
import im.vector.app.waitForView
|
||||
|
||||
class OnboardingRobot {
|
||||
|
@ -57,7 +58,21 @@ class OnboardingRobot {
|
|||
fun createAccount(userId: String, password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") {
|
||||
initSession(true, userId, password, homeServerUrl)
|
||||
waitUntilViewVisible(withText(R.string.ftue_account_created_congratulations_title))
|
||||
clickOn(R.string.ftue_account_created_take_me_home)
|
||||
if (DefaultVectorFeatures().isOnboardingPersonalizeEnabled()) {
|
||||
clickOn(R.string.ftue_account_created_personalize)
|
||||
|
||||
waitUntilViewVisible(withText(R.string.ftue_display_name_title))
|
||||
writeTo(R.id.displayNameInput, "UI automation")
|
||||
clickOn(R.string.ftue_personalize_submit)
|
||||
|
||||
waitUntilViewVisible(withText(R.string.ftue_profile_picture_title))
|
||||
clickOn(R.string.ftue_personalize_skip_this_step)
|
||||
|
||||
waitUntilViewVisible(withText(R.string.ftue_personalize_complete_title))
|
||||
clickOn(R.string.ftue_personalize_lets_go)
|
||||
} else {
|
||||
clickOn(R.string.ftue_account_created_take_me_home)
|
||||
}
|
||||
}
|
||||
|
||||
fun login(userId: String, password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") {
|
||||
|
|
|
@ -56,8 +56,10 @@ class RoomDetailRobot {
|
|||
// Menu
|
||||
openMenu()
|
||||
pressBack()
|
||||
/* TODO something has changed in the menu :/
|
||||
clickMenu(R.id.voice_call)
|
||||
pressBack()
|
||||
*/
|
||||
clickMenu(R.id.video_call)
|
||||
pressBack()
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ class SpaceRobot {
|
|||
|
||||
fun createSpace(block: SpaceCreateRobot.() -> Unit) {
|
||||
openDrawer()
|
||||
clickOn(R.string.add_space)
|
||||
clickOn(R.string.create_space)
|
||||
block(SpaceCreateRobot())
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ class TestLinkifyActivity : AppCompatActivity() {
|
|||
.show()
|
||||
}
|
||||
})
|
||||
*/
|
||||
*/
|
||||
}
|
||||
|
||||
subViews.testLinkifyCustomText.apply {
|
||||
|
@ -108,7 +108,7 @@ class TestLinkifyActivity : AppCompatActivity() {
|
|||
.show()
|
||||
}
|
||||
})
|
||||
*/
|
||||
*/
|
||||
|
||||
// TODO Call VectorLinkify.addLinks(text)
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import dagger.assisted.AssistedFactory
|
|||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.analytics.store.AnalyticsStore
|
||||
|
@ -53,7 +52,7 @@ class DebugAnalyticsViewModel @AssistedInject constructor(
|
|||
override fun handle(action: DebugAnalyticsViewActions) {
|
||||
when (action) {
|
||||
DebugAnalyticsViewActions.ResetAnalyticsOptInDisplayed -> handleResetAnalyticsOptInDisplayed()
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleResetAnalyticsOptInDisplayed() {
|
||||
|
|
|
@ -54,6 +54,16 @@ class DebugFeaturesStateFactory @Inject constructor(
|
|||
key = DebugFeatureKeys.onboardingPersonalize,
|
||||
factory = VectorFeatures::isOnboardingPersonalizeEnabled
|
||||
),
|
||||
createBooleanFeature(
|
||||
label = "FTUE Combined register",
|
||||
key = DebugFeatureKeys.onboardingCombinedRegister,
|
||||
factory = VectorFeatures::isOnboardingCombinedRegisterEnabled
|
||||
),
|
||||
createBooleanFeature(
|
||||
label = "Live location sharing",
|
||||
key = DebugFeatureKeys.liveLocationSharing,
|
||||
factory = VectorFeatures::isLiveLocationEnabled
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,12 @@ class DebugVectorFeatures(
|
|||
override fun isOnboardingPersonalizeEnabled(): Boolean = read(DebugFeatureKeys.onboardingPersonalize)
|
||||
?: vectorFeatures.isOnboardingPersonalizeEnabled()
|
||||
|
||||
override fun isOnboardingCombinedRegisterEnabled(): Boolean = read(DebugFeatureKeys.onboardingCombinedRegister)
|
||||
?: vectorFeatures.isOnboardingCombinedRegisterEnabled()
|
||||
|
||||
override fun isLiveLocationEnabled(): Boolean = read(DebugFeatureKeys.liveLocationSharing)
|
||||
?: vectorFeatures.isLiveLocationEnabled()
|
||||
|
||||
fun <T> override(value: T?, key: Preferences.Key<T>) = updatePreferences {
|
||||
if (value == null) {
|
||||
it.remove(key)
|
||||
|
@ -104,6 +110,8 @@ private fun <T : Enum<T>> enumPreferencesKey(type: KClass<T>) = stringPreference
|
|||
object DebugFeatureKeys {
|
||||
val onboardingAlreadyHaveAnAccount = booleanPreferencesKey("onboarding-already-have-an-account")
|
||||
val onboardingSplashCarousel = booleanPreferencesKey("onboarding-splash-carousel")
|
||||
val onboardingUseCase = booleanPreferencesKey("onbboarding-splash-carousel")
|
||||
val onboardingPersonalize = booleanPreferencesKey("onbboarding-personalize")
|
||||
val onboardingUseCase = booleanPreferencesKey("onboarding-splash-carousel")
|
||||
val onboardingPersonalize = booleanPreferencesKey("onboarding-personalize")
|
||||
val onboardingCombinedRegister = booleanPreferencesKey("onboarding-combined-register")
|
||||
val liveLocationSharing = booleanPreferencesKey("live-location-sharing")
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import dagger.assisted.AssistedFactory
|
|||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.debug.features.DebugVectorOverrides
|
||||
|
@ -69,9 +68,9 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor(
|
|||
when (action) {
|
||||
is DebugPrivateSettingsViewActions.SetDialPadVisibility -> handleSetDialPadVisibility(action)
|
||||
is DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled -> handleSetForceLoginFallbackEnabled(action)
|
||||
is SetDisplayNameCapabilityOverride -> handSetDisplayNameCapabilityOverride(action)
|
||||
is SetAvatarCapabilityOverride -> handSetAvatarCapabilityOverride(action)
|
||||
}.exhaustive
|
||||
is SetDisplayNameCapabilityOverride -> handleSetDisplayNameCapabilityOverride(action)
|
||||
is SetAvatarCapabilityOverride -> handleSetAvatarCapabilityOverride(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSetDialPadVisibility(action: DebugPrivateSettingsViewActions.SetDialPadVisibility) {
|
||||
|
@ -86,14 +85,14 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handSetDisplayNameCapabilityOverride(action: SetDisplayNameCapabilityOverride) {
|
||||
private fun handleSetDisplayNameCapabilityOverride(action: SetDisplayNameCapabilityOverride) {
|
||||
viewModelScope.launch {
|
||||
val forceDisplayName = action.option.toBoolean()
|
||||
debugVectorOverrides.setHomeserverCapabilities { copy(canChangeDisplayName = forceDisplayName) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun handSetAvatarCapabilityOverride(action: SetAvatarCapabilityOverride) {
|
||||
private fun handleSetAvatarCapabilityOverride(action: SetAvatarCapabilityOverride) {
|
||||
viewModelScope.launch {
|
||||
val forceAvatar = action.option.toBoolean()
|
||||
debugVectorOverrides.setHomeserverCapabilities { copy(canChangeAvatar = forceAvatar) }
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
<!--<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />-->
|
||||
|
||||
<!-- Jitsi SDK is now API23+ -->
|
||||
<uses-sdk tools:overrideLibrary="org.jitsi.meet.sdk,com.oney.WebRTCModule,com.learnium.RNDeviceInfo,com.reactnativecommunity.asyncstorage,com.ocetnik.timer,com.calendarevents,com.reactnativecommunity.netinfo,com.kevinresol.react_native_default_preference,com.rnimmersive,com.corbt.keepawake,com.BV.LinearGradient,com.horcrux.svg,com.oblador.performance,com.reactnativecommunity.slider,com.brentvatne.react" />
|
||||
<uses-sdk tools:overrideLibrary="org.jitsi.meet.sdk,com.oney.WebRTCModule,com.learnium.RNDeviceInfo,com.reactnativecommunity.asyncstorage,com.ocetnik.timer,com.calendarevents,com.reactnativecommunity.netinfo,com.kevinresol.react_native_default_preference,com.rnimmersive,com.corbt.keepawake,com.BV.LinearGradient,com.horcrux.svg,com.oblador.performance,com.reactnativecommunity.slider,com.brentvatne.react,com.reactnativecommunity.clipboard,com.swmansion.gesturehandler.react,org.linusu,org.reactnative.maskedview,com.reactnativepagerview,com.swmansion.reanimated,com.th3rdwave.safeareacontext,com.swmansion.rnscreens,org.devio.rn.splashscreen,com.reactnativecommunity.webview" />
|
||||
|
||||
<!-- Adding CAMERA permission prevents Chromebooks to see the application on the PlayStore -->
|
||||
<!-- Tell that the Camera is not mandatory to install the application -->
|
||||
|
@ -92,8 +92,8 @@
|
|||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme.SC.Light"
|
||||
android:taskAffinity="${applicationId}.${appTaskAffinitySuffix}"
|
||||
android:theme="@style/AppTheme.SC.Light"
|
||||
tools:replace="android:allowBackup,android:theme">
|
||||
|
||||
<!-- No limit for screen ratio: avoid black strips -->
|
||||
|
@ -388,6 +388,11 @@
|
|||
android:exported="false"
|
||||
tools:ignore="Instantiatable" />
|
||||
|
||||
<service
|
||||
android:name=".features.location.LocationSharingService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="location" />
|
||||
|
||||
<!-- Receivers -->
|
||||
|
||||
<receiver
|
||||
|
|
|
@ -447,11 +447,6 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
<br/>
|
||||
Copyright 2018, Nick / materialdesignicons.com
|
||||
</li>
|
||||
<li>
|
||||
<b>Armen101 / AudioRecordView</b>
|
||||
<br/>
|
||||
Copyright 2019 Armen Gevorgyan
|
||||
</li>
|
||||
</ul>
|
||||
<pre>
|
||||
Apache License
|
||||
|
|
|
@ -71,6 +71,9 @@ abstract class BottomSheetActionItem : VectorEpoxyModel<BottomSheetActionItem.Ho
|
|||
@EpoxyAttribute
|
||||
var destructive = false
|
||||
|
||||
@EpoxyAttribute
|
||||
var showBetaLabel = false
|
||||
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||
lateinit var listener: ClickListener
|
||||
|
||||
|
@ -106,6 +109,7 @@ abstract class BottomSheetActionItem : VectorEpoxyModel<BottomSheetActionItem.Ho
|
|||
} else {
|
||||
holder.text.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null)
|
||||
}
|
||||
holder.betaLabel.isVisible = showBetaLabel
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
|
@ -113,5 +117,6 @@ abstract class BottomSheetActionItem : VectorEpoxyModel<BottomSheetActionItem.Ho
|
|||
val icon by bind<ImageView>(R.id.actionIcon)
|
||||
val text by bind<TextView>(R.id.actionTitle)
|
||||
val selected by bind<ImageView>(R.id.actionSelected)
|
||||
val betaLabel by bind<TextView>(R.id.actionBetaTextView)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,12 @@
|
|||
|
||||
package im.vector.app.core.extensions
|
||||
|
||||
import android.view.View
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.doOnLayout
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun ConstraintLayout.updateConstraintSet(block: (ConstraintSet) -> Unit) {
|
||||
ConstraintSet().let {
|
||||
|
@ -26,3 +30,21 @@ fun ConstraintLayout.updateConstraintSet(block: (ConstraintSet) -> Unit) {
|
|||
it.applyTo(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to recalculate all ConstraintLayout child views with percentage based height against the parent's height.
|
||||
* This is helpful when using a ConstraintLayout within a ScrollView as any percentages will use the total scrolling size
|
||||
* instead of the viewport/ScrollView height
|
||||
*/
|
||||
fun ConstraintLayout.realignPercentagesToParent() {
|
||||
doOnLayout {
|
||||
val rootHeight = (parent as View).height
|
||||
children.forEach { child ->
|
||||
val params = child.layoutParams as ConstraintLayout.LayoutParams
|
||||
if (params.matchConstraintPercentHeight != 1.0f) {
|
||||
params.height = (rootHeight * params.matchConstraintPercentHeight).roundToInt()
|
||||
child.layoutParams = params
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.app.core.extensions
|
|||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.style.ImageSpan
|
||||
|
@ -31,6 +32,7 @@ import androidx.datastore.preferences.core.Preferences
|
|||
import dagger.hilt.EntryPoints
|
||||
import im.vector.app.core.datastore.dataStoreProvider
|
||||
import im.vector.app.core.di.SingletonEntryPoint
|
||||
import java.io.OutputStream
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun Context.singletonEntryPoint(): SingletonEntryPoint {
|
||||
|
@ -68,3 +70,10 @@ private fun Float.toAndroidAlpha(): Int {
|
|||
}
|
||||
|
||||
val Context.dataStoreProvider: (String) -> DataStore<Preferences> by dataStoreProvider()
|
||||
|
||||
/**
|
||||
* Open Uri in truncate mode to make sure we don't partially overwrite content when we get passed a Uri to an existing file.
|
||||
*/
|
||||
fun Context.safeOpenOutputStream(uri: Uri): OutputStream? {
|
||||
return contentResolver.openOutputStream(uri, "wt")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.core.extensions
|
||||
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import kotlinx.coroutines.flow.map
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
|
||||
fun TextInputLayout.editText() = this.editText!!
|
||||
|
||||
/**
|
||||
* Detect if a field starts or ends with spaces
|
||||
*/
|
||||
fun TextInputLayout.hasSurroundingSpaces() = editText().text.toString().let { it.trim() != it }
|
||||
|
||||
fun TextInputLayout.hasContentFlow(mapper: (CharSequence) -> CharSequence = { it }) = editText().textChanges().map { mapper(it).isNotEmpty() }
|
||||
|
||||
fun TextInputLayout.content() = editText().text.toString()
|
|
@ -54,7 +54,6 @@ import im.vector.app.core.di.ActiveSessionHolder
|
|||
import im.vector.app.core.di.ActivityEntryPoint
|
||||
import im.vector.app.core.dialogs.DialogLocker
|
||||
import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.extensions.observeEvent
|
||||
import im.vector.app.core.extensions.observeNotNull
|
||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||
|
@ -267,7 +266,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||
is GlobalError.CertificateError ->
|
||||
handleCertificateError(globalError)
|
||||
GlobalError.ExpiredAccount -> Unit // TODO Handle account expiration
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCertificateError(certificateError: GlobalError.CertificateError) {
|
||||
|
|
|
@ -83,6 +83,7 @@ class PushRulePreference : VectorPreference {
|
|||
NotificationIndex.NOISY -> {
|
||||
radioGroup?.check(R.id.bingPreferenceRadioBingRuleNoisy)
|
||||
}
|
||||
null -> Unit
|
||||
}
|
||||
|
||||
radioGroup?.setOnCheckedChangeListener { _, checkedId ->
|
||||
|
|
|
@ -77,13 +77,10 @@ class KeysBackupBanner @JvmOverloads constructor(
|
|||
|
||||
override fun onClick(v: View?) {
|
||||
when (state) {
|
||||
is State.Setup -> {
|
||||
delegate?.setupKeysBackup()
|
||||
}
|
||||
is State.Setup -> delegate?.setupKeysBackup()
|
||||
is State.Update,
|
||||
is State.Recover -> {
|
||||
delegate?.recoverKeysBackup()
|
||||
}
|
||||
is State.Recover -> delegate?.recoverKeysBackup()
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ import androidx.core.text.italic
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import im.vector.app.core.error.ResourceLimitErrorFormatter
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.utils.DimensionConverter
|
||||
import im.vector.app.databinding.ViewNotificationAreaBinding
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
|
@ -77,7 +76,7 @@ class NotificationAreaView @JvmOverloads constructor(
|
|||
is State.UnsupportedAlgorithm -> renderUnsupportedAlgorithm(newState)
|
||||
is State.Tombstone -> renderTombstone()
|
||||
is State.ResourceLimitExceededError -> renderResourceLimitExceededError(newState)
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
// PRIVATE METHODS ****************************************************************************************************************************************
|
||||
|
|
|
@ -49,6 +49,7 @@ class PresenceStateImageView @JvmOverloads constructor(
|
|||
setImageResource(R.drawable.ic_presence_offline)
|
||||
contentDescription = context.getString(R.string.a11y_presence_offline)
|
||||
}
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,21 +40,21 @@ class ShieldImageView @JvmOverloads constructor(
|
|||
isVisible = roomEncryptionTrustLevel != null
|
||||
|
||||
when (roomEncryptionTrustLevel) {
|
||||
RoomEncryptionTrustLevel.Default -> {
|
||||
RoomEncryptionTrustLevel.Default -> {
|
||||
contentDescription = context.getString(R.string.a11y_trust_level_default)
|
||||
setImageResource(
|
||||
if (borderLess) R.drawable.ic_shield_black_no_border
|
||||
else R.drawable.ic_shield_black
|
||||
)
|
||||
}
|
||||
RoomEncryptionTrustLevel.Warning -> {
|
||||
RoomEncryptionTrustLevel.Warning -> {
|
||||
contentDescription = context.getString(R.string.a11y_trust_level_warning)
|
||||
setImageResource(
|
||||
if (borderLess) R.drawable.ic_shield_warning_no_border
|
||||
else R.drawable.ic_shield_warning
|
||||
)
|
||||
}
|
||||
RoomEncryptionTrustLevel.Trusted -> {
|
||||
RoomEncryptionTrustLevel.Trusted -> {
|
||||
contentDescription = context.getString(R.string.a11y_trust_level_trusted)
|
||||
setImageResource(
|
||||
if (borderLess) R.drawable.ic_shield_trusted_no_border
|
||||
|
@ -65,6 +65,7 @@ class ShieldImageView @JvmOverloads constructor(
|
|||
contentDescription = context.getString(R.string.a11y_trust_level_trusted)
|
||||
setImageResource(R.drawable.ic_warning_badge)
|
||||
}
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,9 +73,9 @@ class ShieldImageView @JvmOverloads constructor(
|
|||
@DrawableRes
|
||||
fun RoomEncryptionTrustLevel.toDrawableRes(): Int {
|
||||
return when (this) {
|
||||
RoomEncryptionTrustLevel.Default -> R.drawable.ic_shield_black
|
||||
RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning
|
||||
RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted
|
||||
RoomEncryptionTrustLevel.Default -> R.drawable.ic_shield_black
|
||||
RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning
|
||||
RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted
|
||||
RoomEncryptionTrustLevel.E2EWithUnsupportedAlgorithm -> R.drawable.ic_warning_badge
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ interface VectorFeatures {
|
|||
fun isOnboardingSplashCarouselEnabled(): Boolean
|
||||
fun isOnboardingUseCaseEnabled(): Boolean
|
||||
fun isOnboardingPersonalizeEnabled(): Boolean
|
||||
fun isOnboardingCombinedRegisterEnabled(): Boolean
|
||||
fun isLiveLocationEnabled(): Boolean
|
||||
|
||||
enum class OnboardingVariant {
|
||||
LEGACY,
|
||||
|
@ -39,4 +41,6 @@ class DefaultVectorFeatures : VectorFeatures {
|
|||
override fun isOnboardingSplashCarouselEnabled() = true
|
||||
override fun isOnboardingUseCaseEnabled() = true
|
||||
override fun isOnboardingPersonalizeEnabled() = false
|
||||
override fun isOnboardingCombinedRegisterEnabled() = false
|
||||
override fun isLiveLocationEnabled(): Boolean = BuildConfig.ENABLE_LIVE_LOCATION_SHARING
|
||||
}
|
||||
|
|
|
@ -49,6 +49,16 @@ data class JoinedRoom(
|
|||
*/
|
||||
Invite,
|
||||
|
||||
/**
|
||||
* Room joined via space explore
|
||||
*/
|
||||
MobileExploreRooms,
|
||||
|
||||
/**
|
||||
* Room joined via link
|
||||
*/
|
||||
MobilePermalink,
|
||||
|
||||
/**
|
||||
* Room joined via a push/desktop notification.
|
||||
*/
|
||||
|
|
|
@ -63,6 +63,74 @@ data class ViewRoom(
|
|||
*/
|
||||
MessageUser,
|
||||
|
||||
/**
|
||||
* Room accessed via space explore
|
||||
*/
|
||||
MobileExploreRooms,
|
||||
|
||||
/**
|
||||
* Room switched due to user interacting with a file search result.
|
||||
*/
|
||||
MobileFileSearch,
|
||||
|
||||
/**
|
||||
* Room accessed via interacting with the incall screen.
|
||||
*/
|
||||
MobileInCall,
|
||||
|
||||
/**
|
||||
* Room accessed during external sharing
|
||||
*/
|
||||
MobileLinkShare,
|
||||
|
||||
/**
|
||||
* Room accessed via link
|
||||
*/
|
||||
MobilePermalink,
|
||||
|
||||
/**
|
||||
* Room accessed via interacting with direct chat item in the room
|
||||
* contact detail screen.
|
||||
*/
|
||||
MobileRoomMemberDetail,
|
||||
|
||||
/**
|
||||
* Room accessed via preview
|
||||
*/
|
||||
MobileRoomPreview,
|
||||
|
||||
/**
|
||||
* Room switched due to user interacting with a room search result.
|
||||
*/
|
||||
MobileRoomSearch,
|
||||
|
||||
/**
|
||||
* Room accessed via interacting with direct chat item in the search
|
||||
* contact detail screen.
|
||||
*/
|
||||
MobileSearchContactDetail,
|
||||
|
||||
/**
|
||||
* Room accessed via interacting with direct chat item in the space
|
||||
* contact detail screen.
|
||||
*/
|
||||
MobileSpaceMemberDetail,
|
||||
|
||||
/**
|
||||
* Room accessed via space members list
|
||||
*/
|
||||
MobileSpaceMembers,
|
||||
|
||||
/**
|
||||
* Space accessed via interacting with the space menu.
|
||||
*/
|
||||
MobileSpaceMenu,
|
||||
|
||||
/**
|
||||
* Space accessed via interacting with a space settings menu item.
|
||||
*/
|
||||
MobileSpaceSettings,
|
||||
|
||||
/**
|
||||
* Room accessed via a push/desktop notification.
|
||||
*/
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue