mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 02:15:35 +03:00
Merge branch 'develop' into feature/ons/static_location
* develop: (281 commits) Add a comment about the workaround Remove deprecated restricted lab option (#4889) Bump actions/github-script from 3 to 5.1.0 Add some missing language in the change language screen Workaround to not to reuse poll option cells. Olm lib is now hosted in MavenCentral. Upgrade to 3.2.10 Changelog Test: Fix test after change on OnBoarding screens Test: Analytics opt-in Cleanup Revert "Disable automatic opt-in screen display." - Do not add GitHub comments on successful ktlint runs - Remove already existing comments when ktlint succeed restore deprecated lab preference Update nb of enum classes Change autoUisi label + rename matching_issue code review Fix enabling was broken Simple rate limiting of RS sending use flow instead of reactivex Fix UISIS preference listener ... # Conflicts: # vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt # vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt # vector/src/main/res/layout/view_attachment_type_selector.xml # vector/src/main/res/values/strings.xml
This commit is contained in:
commit
9ea30445ef
517 changed files with 14698 additions and 6826 deletions
62
.github/workflows/quality.yml
vendored
62
.github/workflows/quality.yml
vendored
|
@ -14,6 +14,7 @@ jobs:
|
|||
- name: Run code quality check suite
|
||||
run: ./tools/check/check_code_quality.sh
|
||||
|
||||
# ktlint for all the modules
|
||||
ktlint:
|
||||
name: Kotlin Linter
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -23,12 +24,66 @@ jobs:
|
|||
run: |
|
||||
./gradlew ktlintCheck --continue
|
||||
- name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ktlinting-report
|
||||
path: vector/build/reports/ktlint/*.*
|
||||
path: |
|
||||
*/build/reports/ktlint/ktlint*/ktlint*.txt
|
||||
- name: Handle Results
|
||||
if: always()
|
||||
id: ktlint-results
|
||||
run: |
|
||||
results="$(cat */*/build/reports/ktlint/ktlint*/ktlint*.txt */build/reports/ktlint/ktlint*/ktlint*.txt | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g")"
|
||||
if [ -z "$results" ]; then
|
||||
echo "::set-output name=add_comment::false"
|
||||
else
|
||||
body="👎\`Failed${results}\`"
|
||||
body="${body//'%'/'%25'}"
|
||||
body="${body//$'\n'/'%0A'}"
|
||||
body="${body//$'\r'/'%0D'}"
|
||||
body="$( echo $body | sed 's/\/home\/runner\/work\/element-android\/element-android\//\`<br\/>\`/g')"
|
||||
body="$( echo $body | sed 's/\/src\/main\/java\// 🔸 /g')"
|
||||
body="$( echo $body | sed 's/im\/vector\/app\///g')"
|
||||
body="$( echo $body | sed 's/im\/vector\/lib\/attachmentviewer\///g')"
|
||||
body="$( echo $body | sed 's/im\/vector\/lib\/multipicker\///g')"
|
||||
body="$( echo $body | sed 's/im\/vector\/lib\///g')"
|
||||
body="$( echo $body | sed 's/org\/matrix\/android\/sdk\///g')"
|
||||
body="$( echo $body | sed 's/\/src\/androidTest\/java\// 🔸 /g')"
|
||||
echo "::set-output name=add_comment::true"
|
||||
echo "::set-output name=body::$body"
|
||||
fi
|
||||
- name: Find Comment
|
||||
if: always()
|
||||
uses: peter-evans/find-comment@v1
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: Ktlint Results
|
||||
- name: Add comment if needed
|
||||
if: always() && steps.ktlint-results.outputs.add_comment == 'true'
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
### Ktlint Results
|
||||
|
||||
# Lint for main module and all the other modules
|
||||
${{ steps.ktlint-results.outputs.body }}
|
||||
edit-mode: replace
|
||||
- name: Delete comment if needed
|
||||
if: always() && steps.fc.outputs.comment-id != '' && steps.ktlint-results.outputs.add_comment == 'false'
|
||||
uses: actions/github-script@v5.1.0
|
||||
with:
|
||||
script: |
|
||||
github.issues.deleteComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: ${{ steps.fc.outputs.comment-id }}
|
||||
})
|
||||
|
||||
# Lint for main module
|
||||
android-lint:
|
||||
name: Android Linter
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -45,6 +100,7 @@ jobs:
|
|||
- name: Lint analysis
|
||||
run: ./gradlew clean :vector:lint --stacktrace
|
||||
- name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lint-report
|
||||
|
@ -73,8 +129,8 @@ jobs:
|
|||
- name: Lint ${{ matrix.target }} release
|
||||
run: ./gradlew clean lint${{ matrix.target }}Release --stacktrace
|
||||
- name: Upload ${{ matrix.target }} linting report
|
||||
uses: actions/upload-artifact@v2
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: release-lint-report-${{ matrix.target }}
|
||||
path: |
|
||||
|
|
2
.github/workflows/triage-incoming.yml
vendored
2
.github/workflows/triage-incoming.yml
vendored
|
@ -7,6 +7,8 @@ on:
|
|||
jobs:
|
||||
automate-project-columns:
|
||||
runs-on: ubuntu-latest
|
||||
# Skip in forks
|
||||
if: github.repository == 'vector-im/element-android'
|
||||
steps:
|
||||
- uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488
|
||||
with:
|
||||
|
|
82
.github/workflows/triage-move-labelled.yml
vendored
82
.github/workflows/triage-move-labelled.yml
vendored
|
@ -3,11 +3,13 @@ name: Move labelled issues to correct boards and columns
|
|||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
|
||||
jobs:
|
||||
move_needs_info_issues:
|
||||
name: X-Needs-Info issues to Need info column on triage board
|
||||
runs-on: ubuntu-latest
|
||||
# Skip in forks
|
||||
if: github.repository == 'vector-im/element-android'
|
||||
steps:
|
||||
- uses: konradpabjan/move-labeled-or-milestoned-issue@219d384e03fa4b6460cd24f9f37d19eb033a4338
|
||||
with:
|
||||
|
@ -19,15 +21,17 @@ jobs:
|
|||
add_priority_design_issues_to_project:
|
||||
name: P1 X-Needs-Design to Design project board
|
||||
runs-on: ubuntu-latest
|
||||
# Skip in forks
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'X-Needs-Design') &&
|
||||
(contains(github.event.issue.labels.*.name, 'S-Critical') &&
|
||||
(contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
github.repository == 'vector-im/element-android' &&
|
||||
contains(github.event.issue.labels.*.name, 'X-Needs-Design') &&
|
||||
(contains(github.event.issue.labels.*.name, 'S-Critical') &&
|
||||
(contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
contains(github.event.issue.labels.*.name, 'O-Occasional')) ||
|
||||
contains(github.event.issue.labels.*.name, 'S-Major') &&
|
||||
contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
contains(github.event.issue.labels.*.name, 'A11y') &&
|
||||
contains(github.event.issue.labels.*.name, 'O-Frequent'))
|
||||
contains(github.event.issue.labels.*.name, 'S-Major') &&
|
||||
contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
contains(github.event.issue.labels.*.name, 'A11y') &&
|
||||
contains(github.event.issue.labels.*.name, 'O-Frequent'))
|
||||
steps:
|
||||
- uses: octokit/graphql-action@v2.x
|
||||
id: add_to_project
|
||||
|
@ -47,36 +51,40 @@ jobs:
|
|||
PROJECT_ID: "PN_kwDOAM0swc0sUA"
|
||||
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
# delight_issues_to_board:
|
||||
# name: Spaces issues to new Delight project board
|
||||
# runs-on: ubuntu-latest
|
||||
# if: >
|
||||
# contains(github.event.issue.labels.*.name, 'A-Spaces') ||
|
||||
# contains(github.event.issue.labels.*.name, 'A-Space-Settings') ||
|
||||
# contains(github.event.issue.labels.*.name, 'A-Subspaces')
|
||||
# steps:
|
||||
# - uses: octokit/graphql-action@v2.x
|
||||
# with:
|
||||
# headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||
# query: |
|
||||
# mutation add_to_project($projectid:ID!,$contentid:ID!) {
|
||||
# addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
||||
# projectNextItem {
|
||||
# id
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# projectid: ${{ env.PROJECT_ID }}
|
||||
# contentid: ${{ github.event.issue.node_id }}
|
||||
# env:
|
||||
# PROJECT_ID: "PN_kwDOAM0swc1HvQ"
|
||||
# GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
# delight_issues_to_board:
|
||||
# name: Spaces issues to new Delight project board
|
||||
# runs-on: ubuntu-latest
|
||||
# # Skip in forks
|
||||
# if: >
|
||||
# github.repository == 'vector-im/element-android' &&
|
||||
# contains(github.event.issue.labels.*.name, 'A-Spaces') ||
|
||||
# contains(github.event.issue.labels.*.name, 'A-Space-Settings') ||
|
||||
# contains(github.event.issue.labels.*.name, 'A-Subspaces')
|
||||
# steps:
|
||||
# - uses: octokit/graphql-action@v2.x
|
||||
# with:
|
||||
# headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||
# query: |
|
||||
# mutation add_to_project($projectid:ID!,$contentid:ID!) {
|
||||
# addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
||||
# projectNextItem {
|
||||
# id
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# projectid: ${{ env.PROJECT_ID }}
|
||||
# contentid: ${{ github.event.issue.node_id }}
|
||||
# env:
|
||||
# PROJECT_ID: "PN_kwDOAM0swc1HvQ"
|
||||
# GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
move_voice-message_issues:
|
||||
name: A-Voice Messages to voice message board
|
||||
runs-on: ubuntu-latest
|
||||
# Skip in forks
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'A-Voice Messages')
|
||||
github.repository == 'vector-im/element-android' &&
|
||||
contains(github.event.issue.labels.*.name, 'A-Voice Messages')
|
||||
steps:
|
||||
- uses: octokit/graphql-action@v2.x
|
||||
with:
|
||||
|
@ -98,8 +106,10 @@ jobs:
|
|||
move_threads_issues:
|
||||
name: A-Threads to Thread board
|
||||
runs-on: ubuntu-latest
|
||||
# Skip in forks
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'A-Threads')
|
||||
github.repository == 'vector-im/element-android' &&
|
||||
contains(github.event.issue.labels.*.name, 'A-Threads')
|
||||
steps:
|
||||
- uses: octokit/graphql-action@v2.x
|
||||
with:
|
||||
|
@ -121,8 +131,10 @@ jobs:
|
|||
move_message_bubbles_issues:
|
||||
name: A-Message-Bubbles to Message bubbles board
|
||||
runs-on: ubuntu-latest
|
||||
# Skip in forks
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'A-Message-Bubbles')
|
||||
github.repository == 'vector-im/element-android' &&
|
||||
contains(github.event.issue.labels.*.name, 'A-Message-Bubbles')
|
||||
steps:
|
||||
- uses: octokit/graphql-action@v2.x
|
||||
with:
|
||||
|
|
7
.github/workflows/triage-move-unlabelled.yml
vendored
7
.github/workflows/triage-move-unlabelled.yml
vendored
|
@ -3,14 +3,15 @@ name: Move unlabelled from needs info columns to triaged
|
|||
on:
|
||||
issues:
|
||||
types: [unlabeled]
|
||||
|
||||
|
||||
jobs:
|
||||
Move_Unabeled_Issue_On_Project_Board:
|
||||
name: Move no longer X-Needs-Info issues to Triaged
|
||||
runs-on: ubuntu-latest
|
||||
# Skip in forks
|
||||
if: >
|
||||
${{
|
||||
!contains(github.event.issue.labels.*.name, 'X-Needs-Info') }}
|
||||
github.repository == 'vector-im/element-android' &&
|
||||
!contains(github.event.issue.labels.*.name, 'X-Needs-Info')
|
||||
env:
|
||||
BOARD_NAME: "Issue triage"
|
||||
OWNER: ${{ github.repository_owner }}
|
||||
|
|
58
.github/workflows/triage-priority-bugs.yml
vendored
58
.github/workflows/triage-priority-bugs.yml
vendored
|
@ -7,23 +7,25 @@ on:
|
|||
jobs:
|
||||
p1_issues_to_team_workboard:
|
||||
runs-on: ubuntu-latest
|
||||
# Skip in forks
|
||||
if: >
|
||||
(!contains(github.event.issue.labels.*.name, 'A-E2EE') &&
|
||||
!contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') &&
|
||||
!contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') &&
|
||||
!contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') &&
|
||||
!contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification') &&
|
||||
!contains(github.event.issue.labels.*.name, 'A-Spaces') &&
|
||||
!contains(github.event.issue.labels.*.name, 'A-Spaces-Settings') &&
|
||||
!contains(github.event.issue.labels.*.name, 'A-Subspaces')) &&
|
||||
(contains(github.event.issue.labels.*.name, 'T-Defect') &&
|
||||
contains(github.event.issue.labels.*.name, 'S-Critical') &&
|
||||
(contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
github.repository == 'vector-im/element-android' &&
|
||||
(!contains(github.event.issue.labels.*.name, 'A-E2EE') &&
|
||||
!contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') &&
|
||||
!contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') &&
|
||||
!contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') &&
|
||||
!contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification') &&
|
||||
!contains(github.event.issue.labels.*.name, 'A-Spaces') &&
|
||||
!contains(github.event.issue.labels.*.name, 'A-Spaces-Settings') &&
|
||||
!contains(github.event.issue.labels.*.name, 'A-Subspaces')) &&
|
||||
(contains(github.event.issue.labels.*.name, 'T-Defect') &&
|
||||
contains(github.event.issue.labels.*.name, 'S-Critical') &&
|
||||
(contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
contains(github.event.issue.labels.*.name, 'O-Occasional')) ||
|
||||
contains(github.event.issue.labels.*.name, 'S-Major') &&
|
||||
contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
contains(github.event.issue.labels.*.name, 'A11y') &&
|
||||
contains(github.event.issue.labels.*.name, 'O-Frequent'))
|
||||
contains(github.event.issue.labels.*.name, 'S-Major') &&
|
||||
contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
contains(github.event.issue.labels.*.name, 'A11y') &&
|
||||
contains(github.event.issue.labels.*.name, 'O-Frequent'))
|
||||
steps:
|
||||
- uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488
|
||||
with:
|
||||
|
@ -33,20 +35,22 @@ jobs:
|
|||
|
||||
P1_issues_to_crypto_team_workboard:
|
||||
runs-on: ubuntu-latest
|
||||
# Skip in forks
|
||||
if: >
|
||||
(contains(github.event.issue.labels.*.name, 'A-E2EE') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification')) &&
|
||||
(contains(github.event.issue.labels.*.name, 'T-Defect') &&
|
||||
contains(github.event.issue.labels.*.name, 'S-Critical') &&
|
||||
(contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
github.repository == 'vector-im/element-android' &&
|
||||
(contains(github.event.issue.labels.*.name, 'A-E2EE') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification')) &&
|
||||
(contains(github.event.issue.labels.*.name, 'T-Defect') &&
|
||||
contains(github.event.issue.labels.*.name, 'S-Critical') &&
|
||||
(contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
contains(github.event.issue.labels.*.name, 'O-Occasional')) ||
|
||||
contains(github.event.issue.labels.*.name, 'S-Major') &&
|
||||
contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
contains(github.event.issue.labels.*.name, 'A11y') &&
|
||||
contains(github.event.issue.labels.*.name, 'O-Frequent'))
|
||||
contains(github.event.issue.labels.*.name, 'S-Major') &&
|
||||
contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
contains(github.event.issue.labels.*.name, 'A11y') &&
|
||||
contains(github.event.issue.labels.*.name, 'O-Frequent'))
|
||||
steps:
|
||||
- uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488
|
||||
with:
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
<w>ssss</w>
|
||||
<w>sygnal</w>
|
||||
<w>threepid</w>
|
||||
<w>uisi</w>
|
||||
<w>unpublish</w>
|
||||
<w>unwedging</w>
|
||||
<w>vctr</w>
|
||||
|
|
|
@ -46,3 +46,9 @@ If you would like to receive releases more quickly (bearing in mind that they ma
|
|||
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md) if you want to contribute on Matrix Android projects!
|
||||
|
||||
Come chat with the community in the dedicated Matrix [room](https://matrix.to/#/#element-android:matrix.org).
|
||||
|
||||
## Triaging issues
|
||||
|
||||
Issues are triaged by community members and the Android App Team, following the [triage process](https://github.com/vector-im/element-meta/wiki/Triage-process).
|
||||
|
||||
We use [issue labels](https://github.com/vector-im/element-meta/wiki/Issue-labelling) to sort all incoming issues.
|
10
build.gradle
10
build.gradle
|
@ -29,21 +29,13 @@ buildscript {
|
|||
|
||||
// ktlint Plugin
|
||||
plugins {
|
||||
id "org.jlleitschuh.gradle.ktlint" version "10.2.0"
|
||||
id "org.jlleitschuh.gradle.ktlint" version "10.2.1"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
apply plugin: "org.jlleitschuh.gradle.ktlint"
|
||||
|
||||
repositories {
|
||||
// For olm library.
|
||||
maven {
|
||||
url 'https://gitlab.matrix.org/api/v4/projects/27/packages/maven'
|
||||
content {
|
||||
groups.olm.regex.each { includeGroupByRegex it }
|
||||
groups.olm.group.each { includeGroup it }
|
||||
}
|
||||
}
|
||||
maven {
|
||||
url 'https://jitpack.io'
|
||||
content {
|
||||
|
|
1
changelog.d/2133.feature
Normal file
1
changelog.d/2133.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Add labs support for rendering LaTeX maths (MSC2191)
|
1
changelog.d/2614.feature
Normal file
1
changelog.d/2614.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Allow changing nick colors from the member detail screen
|
1
changelog.d/3444.feature
Normal file
1
changelog.d/3444.feature
Normal file
|
@ -0,0 +1 @@
|
|||
New attachment picker UI
|
1
changelog.d/4382.feature
Normal file
1
changelog.d/4382.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Updates onboarding splash screen to have a dedicated sign in button and removes the dual purpose sign in/up stage
|
1
changelog.d/4405.feature
Normal file
1
changelog.d/4405.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Change internal timeline management.
|
1
changelog.d/4405.removal
Normal file
1
changelog.d/4405.removal
Normal file
|
@ -0,0 +1 @@
|
|||
Introduce method onStateUpdated on Timeline.Callback
|
1
changelog.d/4540.bugfix
Normal file
1
changelog.d/4540.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix message replies/quotes to respect newlines.
|
1
changelog.d/4612.misc
Normal file
1
changelog.d/4612.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Workaround to fetch all the pending toDevice events from a Synapse homeserver
|
1
changelog.d/4644.misc
Normal file
1
changelog.d/4644.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Toolbar is added to a views with QR code scan
|
1
changelog.d/4719.feature
Normal file
1
changelog.d/4719.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Analytics: Track Errors
|
1
changelog.d/4735.bugfix
Normal file
1
changelog.d/4735.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Polls: unable to create a poll with more than 10 answers
|
1
changelog.d/4745.misc
Normal file
1
changelog.d/4745.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Open share UI provides by the system when sharing media or text.
|
1
changelog.d/4747.misc
Normal file
1
changelog.d/4747.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Cleaning rendering of state events in timeline
|
1
changelog.d/4749.bugfix
Normal file
1
changelog.d/4749.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix for broken unread message indicator on the room list when there are no messages in the room.
|
1
changelog.d/4753.removal
Normal file
1
changelog.d/4753.removal
Normal file
|
@ -0,0 +1 @@
|
|||
Support tagged events in Room Account Data (MSC2437)
|
1
changelog.d/4756.bugfix
Normal file
1
changelog.d/4756.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fixes newer emojis rendering strangely when inserting from the system keyboard
|
1
changelog.d/4767.bugfix
Normal file
1
changelog.d/4767.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fixing unable to change change avatar in some scenarios
|
1
changelog.d/4781.bugfix
Normal file
1
changelog.d/4781.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Tentative fix for the speaker being used instead of earpiece for the outgoing call ringtone on lineage os
|
1
changelog.d/4789.bugfix
Normal file
1
changelog.d/4789.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fixing crashes when quickly scrolling or restoring the room timeline
|
1
changelog.d/4804.bugfix
Normal file
1
changelog.d/4804.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fixing encrypted non message events showing up as notification messages (eg when a participant joins, mutes or leaves a voice call)
|
1
changelog.d/4837.bugfix
Normal file
1
changelog.d/4837.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Stop using CharSequence as EpoxyAttribute because it can lead to crash if the CharSequence mutates during rendering.
|
1
changelog.d/4847.bugfix
Normal file
1
changelog.d/4847.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Translate the error observed when the user is not allowed to join a room
|
1
changelog.d/4864.misc
Normal file
1
changelog.d/4864.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Fix github actions ktlint reports and publish results on PR as comment
|
1
changelog.d/4872.misc
Normal file
1
changelog.d/4872.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Enabling new FTUE Auth onboarding base, includes the "I already have an account" button in the splash
|
1
changelog.d/4882.misc
Normal file
1
changelog.d/4882.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Olm lib is now hosted in MavenCentral - upgrade to 3.2.10
|
1
changelog.d/4888.misc
Normal file
1
changelog.d/4888.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Add ktlint results on github as a comment only on fail
|
1
changelog.d/4889.removal
Normal file
1
changelog.d/4889.removal
Normal file
|
@ -0,0 +1 @@
|
|||
Remove deprecated experimental restricted space lab option
|
1
changelog.d/4892.feature
Normal file
1
changelog.d/4892.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Display Analytics opt-in screen at first start-up of the app
|
|
@ -95,6 +95,8 @@ ext.libs = [
|
|||
],
|
||||
markwon : [
|
||||
'core' : "io.noties.markwon:core:$markwon",
|
||||
'extLatex' : "io.noties.markwon:ext-latex:$markwon",
|
||||
'inlineParser' : "io.noties.markwon:inline-parser:$markwon",
|
||||
'html' : "io.noties.markwon:html:$markwon"
|
||||
],
|
||||
airbnb : [
|
||||
|
|
|
@ -14,13 +14,6 @@ ext.groups = [
|
|||
'com.github.Zhuinden',
|
||||
]
|
||||
],
|
||||
olm : [
|
||||
regex: [
|
||||
],
|
||||
group: [
|
||||
'org.matrix.android',
|
||||
]
|
||||
],
|
||||
jitsi : [
|
||||
regex: [
|
||||
],
|
||||
|
@ -166,6 +159,7 @@ ext.groups = [
|
|||
'org.junit.jupiter',
|
||||
'org.junit.platform',
|
||||
'org.jvnet.staxex',
|
||||
'org.matrix.android',
|
||||
'org.mockito',
|
||||
'org.mongodb',
|
||||
'org.objenesis',
|
||||
|
@ -179,6 +173,7 @@ ext.groups = [
|
|||
'org.sonatype.oss',
|
||||
'org.testng',
|
||||
'org.threeten',
|
||||
'ru.noties',
|
||||
'xerces',
|
||||
'xml-apis',
|
||||
]
|
||||
|
|
|
@ -50,6 +50,17 @@ It's also possible for any icon to go to the main component by right-clicking on
|
|||
- open the created vector drawable
|
||||
- optionally update the color(s) to "#FF0000" (red) to ensure that the drawable is correctly tinted at runtime.
|
||||
|
||||
### Images
|
||||
|
||||
Android 4.3 (18+) fully supports the WebP image format which can often provide smaller image sizes without drastically impacting image quality (depending on the output encoding quality).
|
||||
When importing non vector images, WebP is the preferred format.
|
||||
|
||||
Images can be converted to the WebP within Android Studio by
|
||||
- right clicking the image file within the project file explorer
|
||||
- select `Convert to WebP`
|
||||
|
||||
https://developer.android.com/studio/write/convert-webp
|
||||
|
||||
## Figma links
|
||||
|
||||
Figma links can be included in the layout, for future reference, but it is also OK to add a paragraph below here, to centralize the information
|
||||
|
|
2
fastlane/metadata/android/cs-CZ/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40103090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Hlavní změny v této verzi: Přidání podpory pro návrh hlasové zprávy. Opravy mnoha chyb!
|
||||
Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
2
fastlane/metadata/android/de-DE/changelogs/40103050.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/40103050.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Änderungen in dieser Version: Unterstützung für Anwesenheitsstatus in Direktnachrichten (Momentan auf matrix.org deaktiviert), Android Auto funktioniert wieder.
|
||||
Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.5
|
2
fastlane/metadata/android/de-DE/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/40103060.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Änderungen in dieser Version: Unterstützung für Anwesenheitsstatus in Direktnachrichten (Momentan auf matrix.org deaktiviert), Android Auto funktioniert wieder.
|
||||
Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
2
fastlane/metadata/android/de-DE/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/40103090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Hauptänderungen: Verbesserungen bei Sprachnachrichten, Bugfixes.
|
||||
Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
2
fastlane/metadata/android/et/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40103090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Põhilised muutused selles versioonis: Häälsõnumite võimalus. Palju veaparandusi!
|
||||
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
2
fastlane/metadata/android/fa/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40103090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
تغییرات عمده در این نگارش: افزودن پشتیبان از چرکنویسهای صوتی. رفع چندین مشکل!
|
||||
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
2
fastlane/metadata/android/fr-FR/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40103090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Principaux changements pour cette version : Ajout du support pour les brouillons de messages vocaux. Beaucoup de corrections de bugs !
|
||||
Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
2
fastlane/metadata/android/hu-HU/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/hu-HU/changelogs/40103090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Fő változás ebben a verzióban: Hang üzenet piszkozat támogatás. Sok egyéb hibajavítás.
|
||||
Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
2
fastlane/metadata/android/id/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40103090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Perubahan utama di versi ini: Tambahkan dukungan untuk draf pesan suara. Banyak perbaikan bug!
|
||||
Changelog lengkap: https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
|
@ -1 +1 @@
|
|||
Perpesanan grup - perpesanan, panggilan suara dan video grup terenkripsi
|
||||
Perpesanan grup — perpesanan, panggilan suara dan video grup terenkripsi
|
||||
|
|
|
@ -1 +1 @@
|
|||
Element - Perpesanan Aman
|
||||
Element — Perpesanan Aman
|
||||
|
|
2
fastlane/metadata/android/it-IT/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/it-IT/changelogs/40103090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Modifiche principali in questa versione: aggiunto supporto per le bozze dei vocali. Molte correzioni!
|
||||
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
2
fastlane/metadata/android/pt-BR/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40103090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Principais mudanças nesta versão: Adicionar suporte para rascunho de mensagem de voz. Muitos consertos de bugs!
|
||||
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
2
fastlane/metadata/android/sk/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40103060.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Hlavné zmeny v tejto verzii: Pridanie podpory prítomnosti pre miestnosť s priamymi správami (poznámka: prítomnosť je na matrix.org vypnutá). Opätovné pridanie podpory Android Auto.
|
||||
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
2
fastlane/metadata/android/sk/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40103090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Hlavné zmeny v tejto verzii: Pridanie podpory pre návrh hlasovej správy. Oprava mnohých chýb!
|
||||
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
|
@ -1,30 +1,41 @@
|
|||
Element je inovatívny kolaboračný komunikátor a messenger ktorý:
|
||||
Element je zabezpečený messenger a zároveň aplikácia na tímovú spoluprácu, ktorá je ideálna na skupinové konverzácie pri práci na diaľku. Táto komunikačná aplikácia využíva end-to-end šifrovanie na poskytovanie výkonných videokonferencií, zdieľania súborov a hlasových hovorov.
|
||||
|
||||
1. Ponecháva kontrolu nad vaším súkromím
|
||||
2. Umožňuje komunikovať s kýmkoľvek v sieti Matrix a vďaka integráciám aj s rôznymi inými aplikáciami ako napríklad Slack
|
||||
3. Chráni vás pred reklamami, zhromažďovaním údajov a uzavretými platformami
|
||||
4. Posilňuje vašu bezpečnosť vďaka E2E šifrovaniu a krížovému podpisovaniu určenému na overovanie ostatných
|
||||
<b>Funkcie aplikácie Element zahŕňajú:</b>
|
||||
- Pokročilé nástroje na online komunikáciu
|
||||
- Plne šifrované správy umožňujúce bezpečnejšiu firemnú komunikáciu aj pre pracovníkov na diaľku
|
||||
- Decentralizované konverzácie založené na open source frameworku Matrix
|
||||
- Bezpečné zdieľanie súborov so šifrovanými údajmi pri správe projektov
|
||||
- Videochaty s funkciou Voice over IP a zdieľaním obrazovky
|
||||
- Jednoduchá integrácia s obľúbenými nástrojmi na online spoluprácu, nástrojmi na riadenie projektov, službami VoIP a inými aplikáciami na tímovú komunikáciu
|
||||
|
||||
Element sa od ostatných komunikačných a kolaboračných aplikácií odlišuje tým, že je decentralizovaný a open-source.
|
||||
Element sa úplne líši od ostatných aplikácií na zasielanie správ a spoluprácu. Funguje na Matrixe, otvorenej sieti na bezpečné posielanie správ a decentralizovanú komunikáciu. Umožňuje vlastný hosting, aby používatelia získali maximálne vlastníctvo a kontrolu nad svojimi údajmi a správami.
|
||||
|
||||
S Elementom sa môžete pripojiť k vlastnému serveru alebo si môžete vybrať server s dôveryhodným poskytovateľom, čím si zachováte súkromie, vlastníctvo a kontrolu nad vašimi konverzáciami a údajmi. Získate tak prístup do otvorenej siete a teda nie ste limitovaní na komunikáciu len s ostatnými Element používateľmi. A samozrejme je vaša komunikácia dobre zabezpečná.
|
||||
<b>Súkromie a šifrovanie správ</b>
|
||||
Element vás chráni pred nežiaducimi reklamami, ťažbou údajov a tzv. walled gardens. Zabezpečuje tiež všetky vaše údaje, video a hlasovú komunikáciu jeden na jedného prostredníctvom end-to-end šifrovania a overovania zariadení krížovým podpisovaním
|
||||
Element vám poskytuje kontrolu nad vaším súkromím a zároveň vám umožňuje bezpečne komunikovať s kýmkoľvek v sieti Matrix alebo s inými nástrojmi na podnikovú spoluprácu vďaka integrácii s aplikáciami, ako je napríklad Slack.
|
||||
|
||||
Element všetko toto dokáže vďaka tomu, že pracuje podľa protokolu Matrix - štandardu na otvorenú, decentralizovanú komunikáciu.
|
||||
<b>Element môže byť na vašom vlastnom serveri</b>.
|
||||
Aby ste mali väčšiu kontrolu nad svojimi citlivými údajmi a konverzáciami, Element môže byť na vašom vlastnom serveri alebo si môžete vybrať ľubovoľný hosting založený na systéme Matrix - štandarde pre decentralizovanú komunikáciu s otvoreným zdrojovým kódom. Element vám poskytuje súkromie, súlad s bezpečnostnými predpismi a flexibilitu integrácie.
|
||||
|
||||
Element vám dáva kontrolu tým, že si samy vyberiete, ako budete spravovať (ang. host) vaše konverzácie. Priamo v aplikácii Element si môžete vybrať z rôznych spôsobov hostovania:
|
||||
<b>Vlastnite svoje údaje</b>
|
||||
Vy rozhodujete o tom, kde budú vaše údaje a správy uložené. Bez rizika ťažby údajov alebo prístupu tretích strán.
|
||||
|
||||
1. Získajte účet zdarma na verejnom servery matrix.org od vývojárov protokolu Matrix alebo si vyberte z tísíce iných serverov hostovaných dobrovoľníkmi
|
||||
2. Hostujte si účet spustením vlastného servera použitím vlastného hardvéru
|
||||
3. Prihláste sa k účtu na vlastnom servery objednaním služieb na platforme Element Matrix Services
|
||||
Element vám dáva kontrolu rôznymi spôsobmi:
|
||||
1. Získajte bezplatné konto na verejnom serveri matrix.org, ktorý hostia vývojári Matrixu, alebo si vyberte z tisícov verejných serverov, ktoré hostia dobrovoľníci.
|
||||
2. Vlastný hosting účtu spustením servera na vlastnej IT infraštruktúre.
|
||||
3. Zaregistrujte si účet na vlastnom serveri tak, že si jednoducho predplatíte hostingovú platformu Element Matrix Services.
|
||||
|
||||
<b>Prečo si vybrať Element?</b>
|
||||
<b>Otvorené zasielanie správ a spolupráca</b>
|
||||
Môžete komunikovať s kýmkoľvek v sieti Matrix, či už používa aplikáciu Element, inú aplikáciu Matrix alebo dokonca ak používa inú aplikáciu na zasielanie správ.
|
||||
|
||||
<b>PONECHAJTE SI VAŠE ÚDAJE</b>: Len vy rozhodujete o tom, kde si budete uchovávať vaše správy a ostatné údaje. Len vy vlastníte vaše údaje a riadite zaobchádzanie s nimi, nie nejaká megakorporácia, ktorá z nich ťaží alebo ich poskytuje tretím stranám.
|
||||
<b>Vynikajúce zabezpečenie</b>
|
||||
Skutočné end-to-end šifrovanie (správy môžu dešifrovať len účastníci konverzácie) a krížové overovanie zariadení.
|
||||
|
||||
<b>OTVORENÁ KOMUNIKÁCIA a KOLABORÁCIA</b>: Konverzovať môžete s kýmkoľvek v otvorenej sieti Matrix nezávisle na tom, či používa Element, inú kompatibilnú aplikáciu, ba dokkonca aj s tými, ktorí používajú úplne inú platformu určenú na okamžitú komunikáciu ako sú Slack, IRC alebo XMPP.
|
||||
<b>Kompletná komunikácia a integrácia</b>
|
||||
Správy, hlasové a video hovory, zdieľanie súborov, zdieľanie obrazovky a celý rad integrácií, botov a widgetov. Vytvárajte miestnosti, komunity, zostaňte v kontakte a vybavujte veci.
|
||||
|
||||
<b>VEĽMI VYSOKÉ ZABEZPEČENIE</b>: Skutočné šifrovanie od zariadenia k zariadeniu (len diskutujúci môžu dešifrovať správy) a krížové podpisovanie určené na overovanie jednotlivých zariadení členov konverzácií.
|
||||
<b>Nadviažte tam, kde ste skončili</b>
|
||||
Buďte v kontakte, nech ste kdekoľvek, vďaka plne synchronizovanej histórii správ vo všetkých zariadeniach a na webe na adrese https://app.element.io.
|
||||
|
||||
<b>KOMPLETNÁ KOMUNIKÁCIA</b>: Okamžité správy, telefonáty a video hovory, zdieľanie súborov, zdieľanie obrazovky a veľké množstvo integrácií, botov a widgetov. Vytvorte si vlastné miestnosti, založte komunity, ostante v kontakte a vyriešte problémy.
|
||||
|
||||
<b>KDEKOĽVEK SA NACHÁDZATE</b>: Ostante v kontakte kdekoľvek ste s plne synchronizovanou históriou konverzácií naprieč všetkými vašimi zariadeniami a aj cez web na adrese https://app.element.io.
|
||||
<b>Otvorený zdroj</b>
|
||||
Element Android je projekt s otvoreným zdrojovým kódom, ktorého hostiteľom je GitHub. Nahlasujte chyby a/alebo prispievajte k jeho vývoju na adrese https://github.com/vector-im/element-android.
|
||||
|
|
|
@ -1 +1 @@
|
|||
Element (kedysi Riot.im)
|
||||
Element - Bezpečný messenger
|
||||
|
|
2
fastlane/metadata/android/sq/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/sq/changelogs/40103090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Ndryshimet kryesore në këtë version: Shtim mbulimi për skica mesazhesh zanore. Mjaft ndreqje të metash!
|
||||
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
2
fastlane/metadata/android/sv-SE/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/sv-SE/changelogs/40103090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Huvudsakliga ändringar i den här versionen: Lägg till stöd för röstmeddelandeutkast. Många buggfixar!
|
||||
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
2
fastlane/metadata/android/uk/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40103090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Основні зміни в цій версії: підтримка чернеток голосових повідомлень. Багато виправлень помилок!
|
||||
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
2
fastlane/metadata/android/zh-CN/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/zh-CN/changelogs/40103090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
版本的主要变化:增加了对语音信息草稿的支持。许多修正!
|
||||
完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
2
fastlane/metadata/android/zh-TW/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40103090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
此版本中的主要變動:新增對語音訊息草稿的支援。許多臭蟲修復!
|
||||
完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
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=dd54e87b4d7aa8ff3c6afb0f7805aa121d4b70bca55b8c9b1b896eb103184582
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-all.zip
|
||||
distributionSha256Sum=c9490e938b221daf0094982288e4038deed954a3f12fb54cbf270ddf4e37d879
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<gradient
|
||||
android:endColor="#3372C7DA"
|
||||
android:startColor="#33BBE7CF" />
|
||||
</shape>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<gradient
|
||||
android:endColor="#33B972DA"
|
||||
android:startColor="#3372C7DA" />
|
||||
</shape>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<gradient
|
||||
android:endColor="#330DBD8B"
|
||||
android:startColor="#33B972DA" />
|
||||
</shape>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<gradient
|
||||
android:endColor="#33BBE7CF"
|
||||
android:startColor="#330DBD8B" />
|
||||
</shape>
|
|
@ -2,4 +2,8 @@
|
|||
<resources>
|
||||
<!-- Navigation Drawer -->
|
||||
<dimen name="navigation_drawer_max_width">400dp</dimen>
|
||||
|
||||
<!-- Onboarding -->
|
||||
<item name="ftue_auth_gutter_start_percent" format="float" type="dimen">0.25</item>
|
||||
<item name="ftue_auth_gutter_end_percent" format="float" type="dimen">0.75</item>
|
||||
</resources>
|
|
@ -42,4 +42,13 @@
|
|||
|
||||
<!-- Preview Url -->
|
||||
<dimen name="preview_url_view_corner_radius">8dp</dimen>
|
||||
|
||||
<!-- Composer -->
|
||||
<dimen name="composer_min_height">56dp</dimen>
|
||||
<dimen name="composer_attachment_size">52dp</dimen>
|
||||
<dimen name="composer_attachment_margin">1dp</dimen>
|
||||
|
||||
<!-- Onboarding -->
|
||||
<item name="ftue_auth_gutter_start_percent" format="float" type="dimen">0.05</item>
|
||||
<item name="ftue_auth_gutter_end_percent" format="float" type="dimen">0.95</item>
|
||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<declare-styleable name="PollResultLineView">
|
||||
<attr name="optionName" format="string" localization="suggested" />
|
||||
<attr name="optionCount" format="string" />
|
||||
<attr name="optionSelected" format="boolean" />
|
||||
<attr name="optionIsWinner" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
|
@ -152,6 +152,13 @@ class FlowSession(private val session: Session) {
|
|||
}
|
||||
}
|
||||
|
||||
fun liveUserAccountData(type: String): Flow<Optional<UserAccountDataEvent>> {
|
||||
return session.accountDataService().getLiveUserAccountDataEvent(type).asFlow()
|
||||
.startWith(session.coroutineDispatchers.io) {
|
||||
session.accountDataService().getUserAccountDataEvent(type).toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveRoomAccountData(types: Set<String>): Flow<List<RoomAccountDataEvent>> {
|
||||
return session.accountDataService().getLiveRoomAccountDataEvents(types).asFlow()
|
||||
.startWith(session.coroutineDispatchers.io) {
|
||||
|
|
|
@ -140,8 +140,8 @@ dependencies {
|
|||
implementation libs.arrow.core
|
||||
implementation libs.arrow.instances
|
||||
|
||||
// olm lib is now hosted by maven at https://gitlab.matrix.org/api/v4/projects/27/packages/maven
|
||||
implementation 'org.matrix.android:olm:3.2.7'
|
||||
// olm lib is now hosted in MavenCentral
|
||||
implementation 'org.matrix.android:olm-sdk:3.2.10'
|
||||
|
||||
// DI
|
||||
implementation libs.dagger.dagger
|
||||
|
@ -158,7 +158,7 @@ dependencies {
|
|||
implementation libs.apache.commonsImaging
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.39'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.40'
|
||||
|
||||
testImplementation libs.tests.junit
|
||||
testImplementation 'org.robolectric:robolectric:4.7.3'
|
||||
|
|
|
@ -208,4 +208,4 @@ public final class LiveDataTestObserver<T> implements Observer<T> {
|
|||
liveData.observeForever(observer);
|
||||
return observer;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -145,36 +145,9 @@ class CommonTestHelper(context: Context) {
|
|||
* @param nbOfMessages the number of time the message will be sent
|
||||
*/
|
||||
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
|
||||
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
|
||||
val timeline = room.createTimeline(null, TimelineSettings(10))
|
||||
timeline.start()
|
||||
waitWithLatch(timeout + 1_000L * nbOfMessages) { latch ->
|
||||
val timelineListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
}
|
||||
|
||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val newMessages = snapshot
|
||||
.filter { it.root.sendState == SendState.SYNCED }
|
||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
|
||||
|
||||
Timber.v("New synced message size: ${newMessages.size}")
|
||||
if (newMessages.size == nbOfMessages) {
|
||||
sentEvents.addAll(newMessages)
|
||||
// Remove listener now, if not at the next update sendEvents could change
|
||||
timeline.removeListener(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
timeline.addListener(timelineListener)
|
||||
sendTextMessagesBatched(room, message, nbOfMessages)
|
||||
}
|
||||
val sentEvents = sendTextMessagesBatched(timeline, room, message, nbOfMessages, timeout)
|
||||
timeline.dispose()
|
||||
// Check that all events has been created
|
||||
assertEquals("Message number do not match $sentEvents", nbOfMessages.toLong(), sentEvents.size.toLong())
|
||||
|
@ -182,9 +155,10 @@ class CommonTestHelper(context: Context) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Will send nb of messages provided by count parameter but waits a bit every 10 messages to avoid gap in sync
|
||||
* Will send nb of messages provided by count parameter but waits every 10 messages to avoid gap in sync
|
||||
*/
|
||||
private fun sendTextMessagesBatched(room: Room, message: String, count: Int) {
|
||||
private fun sendTextMessagesBatched(timeline: Timeline, room: Room, message: String, count: Int, timeout: Long): List<TimelineEvent> {
|
||||
val sentEvents = ArrayList<TimelineEvent>(count)
|
||||
(1 until count + 1)
|
||||
.map { "$message #$it" }
|
||||
.chunked(10)
|
||||
|
@ -192,8 +166,34 @@ class CommonTestHelper(context: Context) {
|
|||
batchedMessages.forEach { formattedMessage ->
|
||||
room.sendTextMessage(formattedMessage)
|
||||
}
|
||||
Thread.sleep(1_000L)
|
||||
waitWithLatch(timeout) { latch ->
|
||||
val timelineListener = object : Timeline.Listener {
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val allSentMessages = snapshot
|
||||
.filter { it.root.sendState == SendState.SYNCED }
|
||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
|
||||
|
||||
val hasSyncedAllBatchedMessages = allSentMessages
|
||||
.map {
|
||||
it.root.getClearContent().toModel<MessageContent>()?.body
|
||||
}
|
||||
.containsAll(batchedMessages)
|
||||
|
||||
if (allSentMessages.size == count) {
|
||||
sentEvents.addAll(allSentMessages)
|
||||
}
|
||||
if (hasSyncedAllBatchedMessages) {
|
||||
timeline.removeListener(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
timeline.addListener(timelineListener)
|
||||
}
|
||||
}
|
||||
return sentEvents
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
@ -332,13 +332,6 @@ class CommonTestHelper(context: Context) {
|
|||
|
||||
fun createEventListener(latch: CountDownLatch, predicate: (List<TimelineEvent>) -> Boolean): Timeline.Listener {
|
||||
return object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
if (predicate(snapshot)) {
|
||||
|
|
|
@ -246,8 +246,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
|||
val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
val newRoomObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
val indexOfFirst = t?.indexOfFirst { it.roomId == roomId } ?: -1
|
||||
if (indexOfFirst != -1) {
|
||||
if (t?.any { it.roomId == roomId }.orFalse()) {
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
latch.countDown()
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ class MarkdownParserTest : InstrumentedTest {
|
|||
* Create the same parser than in the RoomModule
|
||||
*/
|
||||
private val markdownParser = MarkdownParser(
|
||||
Parser.builder().build(),
|
||||
Parser.builder().build(),
|
||||
HtmlRenderer.builder().softbreak("<br />").build(),
|
||||
TextPillsUtils(
|
||||
|
|
|
@ -1,183 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 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.session.room.timeline
|
||||
|
||||
import org.amshove.kluent.shouldBeFalse
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import org.matrix.android.sdk.common.checkSendOrder
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class TimelineBackToPreviousLastForwardTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
/**
|
||||
* This test ensure that if we have a chunk in the timeline which is due to a sync, and we click to permalink of an
|
||||
* even contained in a previous lastForward chunk, we will be able to go back to the live
|
||||
*/
|
||||
@Test
|
||||
fun backToPreviousLastForwardTest() {
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
bobSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
|
||||
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30))
|
||||
bobTimeline.start()
|
||||
|
||||
var roomCreationEventId: String? = null
|
||||
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
roomCreationEventId = snapshot.lastOrNull()?.root?.eventId
|
||||
// Ok, we have the 8 first messages of the initial sync (room creation and bob join event)
|
||||
snapshot.size == 8
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob stop to sync
|
||||
bobSession.stopSync()
|
||||
|
||||
val messageRoot = "First messages from Alice"
|
||||
|
||||
// Alice sends 30 messages
|
||||
commonTestHelper.sendTextMessage(
|
||||
roomFromAlicePOV,
|
||||
messageRoot,
|
||||
30)
|
||||
|
||||
// Bob start to sync
|
||||
bobSession.startSync(true)
|
||||
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// Ok, we have the 10 last messages from Alice.
|
||||
snapshot.size == 10 &&
|
||||
snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith(messageRoot).orFalse() }
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob navigate to the first event (room creation event), so inside the previous last forward chunk
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// The event is in db, so it is fetch and auto pagination occurs, half of the number of events we have for this chunk (?)
|
||||
snapshot.size == 4
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
|
||||
// Restart the timeline to the first sent event, which is already in the database, so pagination should start automatically
|
||||
assertTrue(roomFromBobPOV.getTimeLineEvent(roomCreationEventId!!) != null)
|
||||
|
||||
bobTimeline.restartWithEventId(roomCreationEventId)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob scroll to the future
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// Bob can see the first event of the room (so Back pagination has worked)
|
||||
snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE &&
|
||||
// 8 for room creation item, and 30 for the forward pagination
|
||||
snapshot.size == 38 &&
|
||||
snapshot.checkSendOrder(messageRoot, 30, 0)
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
|
||||
bobTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
}
|
||||
bobTimeline.dispose()
|
||||
|
||||
cryptoTestData.cleanUp(commonTestHelper)
|
||||
}
|
||||
}
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.matrix.android.sdk.session.room.timeline
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.amshove.kluent.internal.assertEquals
|
||||
import org.amshove.kluent.shouldBeFalse
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.junit.FixMethodOrder
|
||||
|
@ -123,54 +125,29 @@ class TimelineForwardPaginationTest : InstrumentedTest {
|
|||
// Alice paginates BACKWARD and FORWARD of 50 events each
|
||||
// Then she can only navigate FORWARD
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Alice timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root.content}")
|
||||
}
|
||||
|
||||
// Alice can see the first event of the room (so Back pagination has worked)
|
||||
snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE &&
|
||||
// 6 for room creation item (backward pagination), 1 for the context, and 50 for the forward pagination
|
||||
snapshot.size == 57 // 6 + 1 + 50
|
||||
val snapshot = runBlocking {
|
||||
aliceTimeline.awaitPaginate(Timeline.Direction.BACKWARDS, 50)
|
||||
aliceTimeline.awaitPaginate(Timeline.Direction.FORWARDS, 50)
|
||||
}
|
||||
|
||||
aliceTimeline.addListener(aliceEventsListener)
|
||||
|
||||
// Restart the timeline to the first sent event
|
||||
// We ask to load event backward and forward
|
||||
aliceTimeline.paginate(Timeline.Direction.BACKWARDS, 50)
|
||||
aliceTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
aliceTimeline.removeAllListeners()
|
||||
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
|
||||
assertEquals(EventType.STATE_ROOM_CREATE, snapshot.lastOrNull()?.root?.getClearType())
|
||||
// 6 for room creation item (backward pagination), 1 for the context, and 50 for the forward pagination
|
||||
// 6 + 1 + 50
|
||||
assertEquals(57, snapshot.size)
|
||||
}
|
||||
|
||||
// Alice paginates once again FORWARD for 50 events
|
||||
// All the timeline is retrieved, she cannot paginate anymore in both direction
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Alice timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root.content}")
|
||||
}
|
||||
// 6 for room creation item (backward pagination),and numberOfMessagesToSend (all the message of the room)
|
||||
snapshot.size == 6 + numberOfMessagesToSend &&
|
||||
snapshot.checkSendOrder(message, numberOfMessagesToSend, 0)
|
||||
}
|
||||
|
||||
aliceTimeline.addListener(aliceEventsListener)
|
||||
|
||||
// Ask for a forward pagination
|
||||
aliceTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
aliceTimeline.removeAllListeners()
|
||||
val snapshot = runBlocking {
|
||||
aliceTimeline.awaitPaginate(Timeline.Direction.FORWARDS, 50)
|
||||
}
|
||||
// 6 for room creation item (backward pagination),and numberOfMessagesToSend (all the message of the room)
|
||||
snapshot.size == 6 + numberOfMessagesToSend &&
|
||||
snapshot.checkSendOrder(message, numberOfMessagesToSend, 0)
|
||||
|
||||
// The timeline is fully loaded
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
|
|
|
@ -168,10 +168,8 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
|
|||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
|
||||
// Restart the timeline to the first sent event, and paginate in both direction
|
||||
// Restart the timeline to the first sent event
|
||||
bobTimeline.restartWithEventId(firstMessageFromAliceId)
|
||||
bobTimeline.paginate(Timeline.Direction.BACKWARDS, 50)
|
||||
bobTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright 2020 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.session.room.timeline
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.amshove.kluent.internal.assertEquals
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.events.model.isTextMessage
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class TimelineSimpleBackPaginationTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
@Test
|
||||
fun timeline_backPaginate_shouldReachEndOfTimeline() {
|
||||
val numberOfMessagesToSent = 200
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
val roomId = cryptoTestData.roomId
|
||||
|
||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
bobSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomFromAlicePOV = aliceSession.getRoom(roomId)!!
|
||||
val roomFromBobPOV = bobSession.getRoom(roomId)!!
|
||||
|
||||
// Alice sends X messages
|
||||
val message = "Message from Alice"
|
||||
commonTestHelper.sendTextMessage(
|
||||
roomFromAlicePOV,
|
||||
message,
|
||||
numberOfMessagesToSent)
|
||||
|
||||
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30))
|
||||
bobTimeline.start()
|
||||
|
||||
commonTestHelper.waitWithLatch(timeout = TestConstants.timeOutMillis * 10) {
|
||||
val listener = object : Timeline.Listener {
|
||||
|
||||
override fun onStateUpdated(direction: Timeline.Direction, state: Timeline.PaginationState) {
|
||||
if (direction == Timeline.Direction.FORWARDS) {
|
||||
return
|
||||
}
|
||||
if (state.hasMoreToLoad && !state.loading) {
|
||||
bobTimeline.paginate(Timeline.Direction.BACKWARDS, 30)
|
||||
} else if (!state.hasMoreToLoad) {
|
||||
bobTimeline.removeListener(this)
|
||||
it.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
bobTimeline.addListener(listener)
|
||||
bobTimeline.paginate(Timeline.Direction.BACKWARDS, 30)
|
||||
}
|
||||
assertEquals(false, bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS))
|
||||
assertEquals(false, bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS))
|
||||
|
||||
val onlySentEvents = runBlocking {
|
||||
bobTimeline.getSnapshot()
|
||||
}
|
||||
.filter {
|
||||
it.root.isTextMessage()
|
||||
}.filter {
|
||||
(it.root.content.toModel<MessageTextContent>())?.body?.startsWith(message).orFalse()
|
||||
}
|
||||
assertEquals(numberOfMessagesToSent, onlySentEvents.size)
|
||||
|
||||
bobTimeline.dispose()
|
||||
cryptoTestData.cleanUp(commonTestHelper)
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 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.session.room.timeline
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
|
||||
internal class TimelineTest : InstrumentedTest {
|
||||
|
||||
companion object {
|
||||
private const val ROOM_ID = "roomId"
|
||||
}
|
||||
|
||||
private lateinit var monarchy: Monarchy
|
||||
|
||||
// @Before
|
||||
// fun setup() {
|
||||
// Timber.plant(Timber.DebugTree())
|
||||
// Realm.init(context())
|
||||
// val testConfiguration = RealmConfiguration.Builder().name("test-realm")
|
||||
// .modules(SessionRealmModule()).build()
|
||||
//
|
||||
// Realm.deleteRealm(testConfiguration)
|
||||
// monarchy = Monarchy.Builder().setRealmConfiguration(testConfiguration).build()
|
||||
// RoomDataHelper.fakeInitialSync(monarchy, ROOM_ID)
|
||||
// }
|
||||
//
|
||||
// private fun createTimeline(initialEventId: String? = null): Timeline {
|
||||
// val taskExecutor = TaskExecutor(testCoroutineDispatchers)
|
||||
// val tokenChunkEventPersistor = TokenChunkEventPersistor(monarchy)
|
||||
// val paginationTask = FakePaginationTask @Inject constructor(tokenChunkEventPersistor)
|
||||
// val getContextOfEventTask = FakeGetContextOfEventTask @Inject constructor(tokenChunkEventPersistor)
|
||||
// val roomMemberExtractor = SenderRoomMemberExtractor(ROOM_ID)
|
||||
// val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, EventRelationExtractor())
|
||||
// return DefaultTimeline(
|
||||
// ROOM_ID,
|
||||
// initialEventId,
|
||||
// monarchy.realmConfiguration,
|
||||
// taskExecutor,
|
||||
// getContextOfEventTask,
|
||||
// timelineEventFactory,
|
||||
// paginationTask,
|
||||
// null)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun backPaginate_shouldLoadMoreEvents_whenPaginateIsCalled() {
|
||||
// val timeline = createTimeline()
|
||||
// timeline.start()
|
||||
// val paginationCount = 30
|
||||
// var initialLoad = 0
|
||||
// val latch = CountDownLatch(2)
|
||||
// var timelineEvents: List<TimelineEvent> = emptyList()
|
||||
// timeline.listener = object : Timeline.Listener {
|
||||
// override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
// if (snapshot.isNotEmpty()) {
|
||||
// if (initialLoad == 0) {
|
||||
// initialLoad = snapshot.size
|
||||
// }
|
||||
// timelineEvents = snapshot
|
||||
// latch.countDown()
|
||||
// timeline.paginate(Timeline.Direction.BACKWARDS, paginationCount)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// latch.await()
|
||||
// timelineEvents.size shouldBeEqualTo initialLoad + paginationCount
|
||||
// timeline.dispose()
|
||||
// }
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.commonmark.ext.maths
|
||||
|
||||
import org.commonmark.node.CustomBlock
|
||||
|
||||
class DisplayMaths(private val delimiter: DisplayDelimiter) : CustomBlock() {
|
||||
enum class DisplayDelimiter {
|
||||
DOUBLE_DOLLAR,
|
||||
SQUARE_BRACKET_ESCAPED
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.commonmark.ext.maths
|
||||
|
||||
import org.commonmark.node.CustomNode
|
||||
import org.commonmark.node.Delimited
|
||||
|
||||
class InlineMaths(private val delimiter: InlineDelimiter) : CustomNode(), Delimited {
|
||||
enum class InlineDelimiter {
|
||||
SINGLE_DOLLAR,
|
||||
ROUND_BRACKET_ESCAPED
|
||||
}
|
||||
|
||||
override fun getOpeningDelimiter(): String {
|
||||
return when (delimiter) {
|
||||
InlineDelimiter.SINGLE_DOLLAR -> "$"
|
||||
InlineDelimiter.ROUND_BRACKET_ESCAPED -> "\\("
|
||||
}
|
||||
}
|
||||
|
||||
override fun getClosingDelimiter(): String {
|
||||
return when (delimiter) {
|
||||
InlineDelimiter.SINGLE_DOLLAR -> "$"
|
||||
InlineDelimiter.ROUND_BRACKET_ESCAPED -> "\\)"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.commonmark.ext.maths
|
||||
|
||||
import org.commonmark.Extension
|
||||
import org.commonmark.ext.maths.internal.DollarMathsDelimiterProcessor
|
||||
import org.commonmark.ext.maths.internal.MathsHtmlNodeRenderer
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
|
||||
class MathsExtension private constructor() : Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension {
|
||||
override fun extend(parserBuilder: Parser.Builder) {
|
||||
parserBuilder.customDelimiterProcessor(DollarMathsDelimiterProcessor())
|
||||
}
|
||||
|
||||
override fun extend(rendererBuilder: HtmlRenderer.Builder) {
|
||||
rendererBuilder.nodeRendererFactory { context -> MathsHtmlNodeRenderer(context) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(): Extension {
|
||||
return MathsExtension()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.commonmark.ext.maths.internal
|
||||
|
||||
import org.commonmark.ext.maths.DisplayMaths
|
||||
import org.commonmark.ext.maths.InlineMaths
|
||||
import org.commonmark.node.Text
|
||||
import org.commonmark.parser.delimiter.DelimiterProcessor
|
||||
import org.commonmark.parser.delimiter.DelimiterRun
|
||||
|
||||
class DollarMathsDelimiterProcessor : DelimiterProcessor {
|
||||
override fun getOpeningCharacter() = '$'
|
||||
|
||||
override fun getClosingCharacter() = '$'
|
||||
|
||||
override fun getMinLength() = 1
|
||||
|
||||
override fun getDelimiterUse(opener: DelimiterRun, closer: DelimiterRun): Int {
|
||||
return if (opener.length() == 1 && closer.length() == 1) 1 // inline
|
||||
else if (opener.length() == 2 && closer.length() == 2) 2 // display
|
||||
else 0
|
||||
}
|
||||
|
||||
override fun process(opener: Text, closer: Text, delimiterUse: Int) {
|
||||
val maths = if (delimiterUse == 1) {
|
||||
InlineMaths(InlineMaths.InlineDelimiter.SINGLE_DOLLAR)
|
||||
} else {
|
||||
DisplayMaths(DisplayMaths.DisplayDelimiter.DOUBLE_DOLLAR)
|
||||
}
|
||||
var tmp = opener.next
|
||||
while (tmp != null && tmp !== closer) {
|
||||
val next = tmp.next
|
||||
maths.appendChild(tmp)
|
||||
tmp = next
|
||||
}
|
||||
opener.insertAfter(maths)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.commonmark.ext.maths.internal
|
||||
|
||||
import org.commonmark.ext.maths.DisplayMaths
|
||||
import org.commonmark.node.Node
|
||||
import org.commonmark.node.Text
|
||||
import org.commonmark.renderer.html.HtmlNodeRendererContext
|
||||
import org.commonmark.renderer.html.HtmlWriter
|
||||
import java.util.Collections
|
||||
|
||||
class MathsHtmlNodeRenderer(private val context: HtmlNodeRendererContext) : MathsNodeRenderer() {
|
||||
private val html: HtmlWriter = context.writer
|
||||
override fun render(node: Node) {
|
||||
val display = node.javaClass == DisplayMaths::class.java
|
||||
val contents = node.firstChild // should be the only child
|
||||
val latex = (contents as Text).literal
|
||||
val attributes = context.extendAttributes(node, if (display) "div" else "span", Collections.singletonMap("data-mx-maths",
|
||||
latex))
|
||||
html.tag(if (display) "div" else "span", attributes)
|
||||
html.tag("code")
|
||||
context.render(contents)
|
||||
html.tag("/code")
|
||||
html.tag(if (display) "/div" else "/span")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.commonmark.ext.maths.internal
|
||||
|
||||
import org.commonmark.ext.maths.DisplayMaths
|
||||
import org.commonmark.ext.maths.InlineMaths
|
||||
import org.commonmark.node.Node
|
||||
import org.commonmark.renderer.NodeRenderer
|
||||
import java.util.HashSet
|
||||
|
||||
abstract class MathsNodeRenderer : NodeRenderer {
|
||||
override fun getNodeTypes(): Set<Class<out Node>> {
|
||||
val types: MutableSet<Class<out Node>> = HashSet()
|
||||
types.add(InlineMaths::class.java)
|
||||
types.add(DisplayMaths::class.java)
|
||||
return types
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2021 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
|
||||
|
||||
interface EventStreamService {
|
||||
|
||||
fun addEventStreamListener(streamListener: LiveEventListener)
|
||||
|
||||
fun removeEventStreamListener(streamListener: LiveEventListener)
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2021 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
|
||||
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
|
||||
interface LiveEventListener {
|
||||
|
||||
fun onLiveEvent(roomId: String, event: Event)
|
||||
|
||||
fun onPaginatedEvent(roomId: String, event: Event)
|
||||
|
||||
fun onEventDecrypted(eventId: String, roomId: String, clearEvent: JsonDict)
|
||||
|
||||
fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable)
|
||||
|
||||
fun onLiveToDeviceEvent(event: Event)
|
||||
|
||||
// Maybe later add more, like onJoin, onLeave..
|
||||
}
|
|
@ -84,7 +84,9 @@ interface Session :
|
|||
SyncStatusService,
|
||||
HomeServerCapabilitiesService,
|
||||
SecureStorageService,
|
||||
AccountService {
|
||||
AccountService,
|
||||
ToDeviceService,
|
||||
EventStreamService {
|
||||
|
||||
val coroutineDispatchers: MatrixCoroutineDispatchers
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2021 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
|
||||
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||
import java.util.UUID
|
||||
|
||||
interface ToDeviceService {
|
||||
|
||||
/**
|
||||
* Send an event to a specific list of devices
|
||||
*/
|
||||
suspend fun sendToDevice(eventType: String, contentMap: MXUsersDevicesMap<Any>, txnId: String? = UUID.randomUUID().toString())
|
||||
|
||||
suspend fun sendToDevice(eventType: String, userId: String, deviceId: String, content: Content, txnId: String? = UUID.randomUUID().toString()) {
|
||||
sendToDevice(eventType, mapOf(userId to listOf(deviceId)), content, txnId)
|
||||
}
|
||||
|
||||
suspend fun sendToDevice(eventType: String, targets: Map<String, List<String>>, content: Content, txnId: String? = UUID.randomUUID().toString())
|
||||
|
||||
suspend fun sendEncryptedToDevice(eventType: String, targets: Map<String, List<String>>, content: Content, txnId: String? = UUID.randomUUID().toString())
|
||||
}
|
|
@ -27,4 +27,5 @@ object UserAccountDataTypes {
|
|||
const val TYPE_ALLOWED_WIDGETS = "im.vector.setting.allowed_widgets"
|
||||
const val TYPE_IDENTITY_SERVER = "m.identity_server"
|
||||
const val TYPE_ACCEPTED_TERMS = "m.accepted_terms"
|
||||
const val TYPE_OVERRIDE_COLORS = "im.vector.setting.override_colors"
|
||||
}
|
||||
|
|
|
@ -21,4 +21,5 @@ object RoomAccountDataTypes {
|
|||
const val EVENT_TYPE_TAG = "m.tag"
|
||||
const val EVENT_TYPE_FULLY_READ = "m.fully_read"
|
||||
const val EVENT_TYPE_SPACE_ORDER = "org.matrix.msc3230.space_order" // m.space_order
|
||||
const val EVENT_TYPE_TAGGED_EVENTS = "m.tagged_events"
|
||||
}
|
||||
|
|
|
@ -56,6 +56,15 @@ interface SendService {
|
|||
*/
|
||||
fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String = MessageType.MSGTYPE_TEXT): Cancelable
|
||||
|
||||
/**
|
||||
* Method to quote an events content.
|
||||
* @param quotedEvent The event to which we will quote it's content.
|
||||
* @param text the text message to send
|
||||
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
|
||||
* @return a [Cancelable]
|
||||
*/
|
||||
fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean): Cancelable
|
||||
|
||||
/**
|
||||
* Method to send a media asynchronously.
|
||||
* @param attachment the media to send
|
||||
|
|
|
@ -71,14 +71,10 @@ interface Timeline {
|
|||
fun paginate(direction: Direction, count: Int)
|
||||
|
||||
/**
|
||||
* Returns the number of sending events
|
||||
* This is the same than the regular paginate method but waits for the results instead
|
||||
* of relying on the timeline listener.
|
||||
*/
|
||||
fun pendingEventCount(): Int
|
||||
|
||||
/**
|
||||
* Returns the number of failed sending events.
|
||||
*/
|
||||
fun failedToDeliverEventCount(): Int
|
||||
suspend fun awaitPaginate(direction: Direction, count: Int): List<TimelineEvent>
|
||||
|
||||
/**
|
||||
* Returns the index of a built event or null.
|
||||
|
@ -86,14 +82,14 @@ interface Timeline {
|
|||
fun getIndexOfEvent(eventId: String?): Int?
|
||||
|
||||
/**
|
||||
* Returns the built [TimelineEvent] at index or null
|
||||
* Returns the current pagination state for the direction.
|
||||
*/
|
||||
fun getTimelineEventAtIndex(index: Int): TimelineEvent?
|
||||
fun getPaginationState(direction: Direction): PaginationState
|
||||
|
||||
/**
|
||||
* Returns the built [TimelineEvent] with eventId or null
|
||||
* Returns a snapshot of the timeline in his current state.
|
||||
*/
|
||||
fun getTimelineEventWithId(eventId: String?): TimelineEvent?
|
||||
fun getSnapshot(): List<TimelineEvent>
|
||||
|
||||
interface Listener {
|
||||
/**
|
||||
|
@ -101,19 +97,33 @@ interface Timeline {
|
|||
* The latest event is the first in the list
|
||||
* @param snapshot the most up to date snapshot
|
||||
*/
|
||||
fun onTimelineUpdated(snapshot: List<TimelineEvent>)
|
||||
fun onTimelineUpdated(snapshot: List<TimelineEvent>) = Unit
|
||||
|
||||
/**
|
||||
* Called whenever an error we can't recover from occurred
|
||||
*/
|
||||
fun onTimelineFailure(throwable: Throwable)
|
||||
fun onTimelineFailure(throwable: Throwable) = Unit
|
||||
|
||||
/**
|
||||
* Called when new events come through the sync
|
||||
*/
|
||||
fun onNewTimelineEvents(eventIds: List<String>)
|
||||
fun onNewTimelineEvents(eventIds: List<String>) = Unit
|
||||
|
||||
/**
|
||||
* Called when the pagination state has changed in one direction
|
||||
*/
|
||||
fun onStateUpdated(direction: Direction, state: PaginationState) = Unit
|
||||
}
|
||||
|
||||
/**
|
||||
* Pagination state
|
||||
*/
|
||||
data class PaginationState(
|
||||
val hasMoreToLoad: Boolean = true,
|
||||
val loading: Boolean = false,
|
||||
val inError: Boolean = false
|
||||
)
|
||||
|
||||
/**
|
||||
* This is used to paginate in one or another direction.
|
||||
*/
|
||||
|
|
|
@ -47,6 +47,10 @@ data class TimelineEvent(
|
|||
*/
|
||||
val localId: Long,
|
||||
val eventId: String,
|
||||
/**
|
||||
* This display index is the position in the current chunk.
|
||||
* It's not unique on the timeline as it's reset on each chunk.
|
||||
*/
|
||||
val displayIndex: Int,
|
||||
val senderInfo: SenderInfo,
|
||||
val annotations: EventAnnotationsSummary? = null,
|
||||
|
|
|
@ -90,6 +90,7 @@ import org.matrix.android.sdk.internal.di.MoshiProvider
|
|||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.extensions.foldToCallback
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
||||
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.task.TaskThread
|
||||
|
@ -168,7 +169,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
private val eventDecryptor: EventDecryptor
|
||||
private val eventDecryptor: EventDecryptor,
|
||||
private val liveEventManager: Lazy<StreamEventsManager>
|
||||
) : CryptoService {
|
||||
|
||||
private val isStarting = AtomicBoolean(false)
|
||||
|
@ -429,7 +431,17 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
|
||||
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
|
||||
}
|
||||
if (isStarted()) {
|
||||
// There is a limit of to_device events returned per sync.
|
||||
// If we are in a case of such limited to_device sync we can't try to generate/upload
|
||||
// new otk now, because there might be some pending olm pre-key to_device messages that would fail if we rotate
|
||||
// the old otk too early. In this case we want to wait for the pending to_device before doing anything
|
||||
// As per spec:
|
||||
// If there is a large queue of send-to-device messages, the server should limit the number sent in each /sync response.
|
||||
// 100 messages is recommended as a reasonable limit.
|
||||
// The limit is not part of the spec, so it's probably safer to handle that when there are no more to_device ( so we are sure
|
||||
// that there are no pending to_device
|
||||
val toDevices = syncResponse.toDevice?.events.orEmpty()
|
||||
if (isStarted() && toDevices.isEmpty()) {
|
||||
// Make sure we process to-device messages before generating new one-time-keys #2782
|
||||
deviceListManager.refreshOutdatedDeviceLists()
|
||||
// The presence of device_unused_fallback_key_types indicates that the server supports fallback keys.
|
||||
|
@ -772,6 +784,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
liveEventManager.get().dispatchOnLiveToDevice(event)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.crypto.algorithms.megolm
|
||||
|
||||
import dagger.Lazy
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
|
@ -43,6 +44,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent
|
|||
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
||||
import timber.log.Timber
|
||||
|
||||
private val loggerTag = LoggerTag("MXMegolmDecryption", LoggerTag.CRYPTO)
|
||||
|
@ -56,7 +58,8 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
private val cryptoStore: IMXCryptoStore,
|
||||
private val sendToDeviceTask: SendToDeviceTask,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val cryptoCoroutineScope: CoroutineScope
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
private val liveEventManager: Lazy<StreamEventsManager>
|
||||
) : IMXDecrypting, IMXWithHeldExtension {
|
||||
|
||||
var newSessionListener: NewSessionListener? = null
|
||||
|
@ -108,12 +111,15 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
|
||||
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain
|
||||
.orEmpty()
|
||||
)
|
||||
).also {
|
||||
liveEventManager.get().dispatchLiveEventDecrypted(event, it)
|
||||
}
|
||||
} else {
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||
}
|
||||
},
|
||||
{ throwable ->
|
||||
liveEventManager.get().dispatchLiveEventDecryptionFailed(event, throwable)
|
||||
if (throwable is MXCryptoError.OlmError) {
|
||||
// TODO Check the value of .message
|
||||
if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
|
||||
|
@ -133,6 +139,11 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
if (requestKeysOnFail) {
|
||||
requestKeysForEvent(event, false)
|
||||
}
|
||||
|
||||
throw MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX,
|
||||
"UNKNOWN_MESSAGE_INDEX",
|
||||
null)
|
||||
}
|
||||
|
||||
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.crypto.algorithms.megolm
|
||||
|
||||
import dagger.Lazy
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
||||
|
@ -26,6 +27,7 @@ import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
|||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class MXMegolmDecryptionFactory @Inject constructor(
|
||||
|
@ -38,7 +40,8 @@ internal class MXMegolmDecryptionFactory @Inject constructor(
|
|||
private val cryptoStore: IMXCryptoStore,
|
||||
private val sendToDeviceTask: SendToDeviceTask,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val cryptoCoroutineScope: CoroutineScope
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
private val eventsManager: Lazy<StreamEventsManager>
|
||||
) {
|
||||
|
||||
fun create(): MXMegolmDecryption {
|
||||
|
@ -52,6 +55,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor(
|
|||
cryptoStore,
|
||||
sendToDeviceTask,
|
||||
coroutineDispatchers,
|
||||
cryptoCoroutineScope)
|
||||
cryptoCoroutineScope,
|
||||
eventsManager)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 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.internal.database
|
||||
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||
import org.matrix.android.sdk.internal.database.helper.nextDisplayIndex
|
||||
import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
||||
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.deleteOnCascade
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val MAX_NUMBER_OF_EVENTS_IN_DB = 35_000L
|
||||
private const val MIN_NUMBER_OF_EVENTS_BY_CHUNK = 300
|
||||
|
||||
/**
|
||||
* This class makes sure to stay under a maximum number of events as it makes Realm to be unusable when listening to events
|
||||
* when the database is getting too big. This will try incrementally to remove the biggest chunks until we get below the threshold.
|
||||
* We make sure to still have a minimum number of events so it's not becoming unusable.
|
||||
* So this won't work for users with a big number of very active rooms.
|
||||
*/
|
||||
internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration,
|
||||
private val taskExecutor: TaskExecutor) : SessionLifecycleObserver {
|
||||
|
||||
override fun onSessionStarted(session: Session) {
|
||||
taskExecutor.executorScope.launch(Dispatchers.Default) {
|
||||
awaitTransaction(realmConfiguration) { realm ->
|
||||
val allRooms = realm.where(RoomEntity::class.java).findAll()
|
||||
Timber.v("There are ${allRooms.size} rooms in this session")
|
||||
cleanUp(realm, MAX_NUMBER_OF_EVENTS_IN_DB / 2L)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanUp(realm: Realm, threshold: Long) {
|
||||
val numberOfEvents = realm.where(EventEntity::class.java).findAll().size
|
||||
val numberOfTimelineEvents = realm.where(TimelineEventEntity::class.java).findAll().size
|
||||
Timber.v("Number of events in db: $numberOfEvents | Number of timeline events in db: $numberOfTimelineEvents")
|
||||
if (threshold <= MIN_NUMBER_OF_EVENTS_BY_CHUNK || numberOfTimelineEvents < MAX_NUMBER_OF_EVENTS_IN_DB) {
|
||||
Timber.v("Db is low enough")
|
||||
} else {
|
||||
val thresholdChunks = realm.where(ChunkEntity::class.java)
|
||||
.greaterThan(ChunkEntityFields.NUMBER_OF_TIMELINE_EVENTS, threshold)
|
||||
.findAll()
|
||||
|
||||
Timber.v("There are ${thresholdChunks.size} chunks to clean with more than $threshold events")
|
||||
for (chunk in thresholdChunks) {
|
||||
val maxDisplayIndex = chunk.nextDisplayIndex(PaginationDirection.FORWARDS)
|
||||
val thresholdDisplayIndex = maxDisplayIndex - threshold
|
||||
val eventsToRemove = chunk.timelineEvents.where().lessThan(TimelineEventEntityFields.DISPLAY_INDEX, thresholdDisplayIndex).findAll()
|
||||
Timber.v("There are ${eventsToRemove.size} events to clean in chunk: ${chunk.identifier()} from room ${chunk.room?.first()?.roomId}")
|
||||
chunk.numberOfTimelineEvents = chunk.numberOfTimelineEvents - eventsToRemove.size
|
||||
eventsToRemove.forEach {
|
||||
val canDeleteRoot = it.root?.stateKey == null
|
||||
it.deleteOnCascade(canDeleteRoot)
|
||||
}
|
||||
// We reset the prevToken so we will need to fetch again.
|
||||
chunk.prevToken = null
|
||||
}
|
||||
cleanUp(realm, (threshold / 1.5).toLong())
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue