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:
Onuray Sahin 2022-01-11 14:42:11 +03:00
commit 9ea30445ef
517 changed files with 14698 additions and 6826 deletions

View file

@ -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: |

View file

@ -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:

View file

@ -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:

View file

@ -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 }}

View file

@ -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:

View file

@ -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>

View file

@ -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.

View file

@ -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
View file

@ -0,0 +1 @@
Add labs support for rendering LaTeX maths (MSC2191)

1
changelog.d/2614.feature Normal file
View file

@ -0,0 +1 @@
Allow changing nick colors from the member detail screen

1
changelog.d/3444.feature Normal file
View file

@ -0,0 +1 @@
New attachment picker UI

1
changelog.d/4382.feature Normal file
View 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
View file

@ -0,0 +1 @@
Change internal timeline management.

1
changelog.d/4405.removal Normal file
View file

@ -0,0 +1 @@
Introduce method onStateUpdated on Timeline.Callback

1
changelog.d/4540.bugfix Normal file
View file

@ -0,0 +1 @@
Fix message replies/quotes to respect newlines.

1
changelog.d/4612.misc Normal file
View file

@ -0,0 +1 @@
Workaround to fetch all the pending toDevice events from a Synapse homeserver

1
changelog.d/4644.misc Normal file
View file

@ -0,0 +1 @@
Toolbar is added to a views with QR code scan

1
changelog.d/4719.feature Normal file
View file

@ -0,0 +1 @@
Analytics: Track Errors

1
changelog.d/4735.bugfix Normal file
View file

@ -0,0 +1 @@
Polls: unable to create a poll with more than 10 answers

1
changelog.d/4745.misc Normal file
View file

@ -0,0 +1 @@
Open share UI provides by the system when sharing media or text.

1
changelog.d/4747.misc Normal file
View file

@ -0,0 +1 @@
Cleaning rendering of state events in timeline

1
changelog.d/4749.bugfix Normal file
View 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
View file

@ -0,0 +1 @@
Support tagged events in Room Account Data (MSC2437)

1
changelog.d/4756.bugfix Normal file
View file

@ -0,0 +1 @@
Fixes newer emojis rendering strangely when inserting from the system keyboard

1
changelog.d/4767.bugfix Normal file
View file

@ -0,0 +1 @@
Fixing unable to change change avatar in some scenarios

1
changelog.d/4781.bugfix Normal file
View 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
View file

@ -0,0 +1 @@
Fixing crashes when quickly scrolling or restoring the room timeline

1
changelog.d/4804.bugfix Normal file
View 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
View 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
View 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
View file

@ -0,0 +1 @@
Fix github actions ktlint reports and publish results on PR as comment

1
changelog.d/4872.misc Normal file
View 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
View file

@ -0,0 +1 @@
Olm lib is now hosted in MavenCentral - upgrade to 3.2.10

1
changelog.d/4888.misc Normal file
View file

@ -0,0 +1 @@
Add ktlint results on github as a comment only on fail

1
changelog.d/4889.removal Normal file
View file

@ -0,0 +1 @@
Remove deprecated experimental restricted space lab option

1
changelog.d/4892.feature Normal file
View file

@ -0,0 +1 @@
Display Analytics opt-in screen at first start-up of the app

View file

@ -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 : [

View file

@ -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',
]

View file

@ -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

View 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

View 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

View 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

View file

@ -0,0 +1,2 @@
Hauptänderungen: Verbesserungen bei Sprachnachrichten, Bugfixes.
Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.9

View 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

View file

@ -0,0 +1,2 @@
تغییرات عمده در این نگارش: افزودن پشتیبان از چرک‌نویس‌های صوتی. رفع چندین مشکل!
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.9

View 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

View 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

View 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

View file

@ -1 +1 @@
Perpesanan grup - perpesanan, panggilan suara dan video grup terenkripsi
Perpesanan grup perpesanan, panggilan suara dan video grup terenkripsi

View file

@ -1 +1 @@
Element - Perpesanan Aman
Element Perpesanan Aman

View 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

View 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

View 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

View 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

View file

@ -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.

View file

@ -1 +1 @@
Element (kedysi Riot.im)
Element - Bezpečný messenger

View 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

View 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

View file

@ -0,0 +1,2 @@
Основні зміни в цій версії: підтримка чернеток голосових повідомлень. Багато виправлень помилок!
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.9

View file

@ -0,0 +1,2 @@
版本的主要变化:增加了对语音信息草稿的支持。许多修正!
完整更新日志https://github.com/vector-im/element-android/releases/tag/v1.3.9

View file

@ -0,0 +1,2 @@
此版本中的主要變動:新增對語音訊息草稿的支援。許多臭蟲修復!
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.3.9

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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) {

View file

@ -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'

View file

@ -208,4 +208,4 @@ public final class LiveDataTestObserver<T> implements Observer<T> {
liveData.observeForever(observer);
return observer;
}
}
}

View file

@ -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)) {

View file

@ -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()
}

View file

@ -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(

View file

@ -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)
}
}

View file

@ -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()

View file

@ -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()

View file

@ -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)
}
}

View file

@ -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()
// }
}

View file

@ -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
}
}

View file

@ -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 -> "\\)"
}
}
}

View file

@ -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()
}
}
}

View file

@ -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)
}
}

View file

@ -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")
}
}

View file

@ -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
}
}

View file

@ -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)
}

View file

@ -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..
}

View file

@ -84,7 +84,9 @@ interface Session :
SyncStatusService,
HomeServerCapabilitiesService,
SecureStorageService,
AccountService {
AccountService,
ToDeviceService,
EventStreamService {
val coroutineDispatchers: MatrixCoroutineDispatchers

View file

@ -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())
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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

View file

@ -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.
*/

View file

@ -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,

View file

@ -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)
}
/**

View file

@ -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)

View file

@ -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)
}
}

View file

@ -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