mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-12-21 16:44:37 +03:00
Merge branch 'release/1.3.8' into main
This commit is contained in:
commit
879e6ef5e2
348 changed files with 8136 additions and 2918 deletions
2
.github/ISSUE_TEMPLATE/bug.yml
vendored
2
.github/ISSUE_TEMPLATE/bug.yml
vendored
|
@ -23,7 +23,7 @@ body:
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: result
|
id: result
|
||||||
attributes:
|
attributes:
|
||||||
label: Intended result and actual result
|
label: Outcome
|
||||||
placeholder: Tell us what went wrong
|
placeholder: Tell us what went wrong
|
||||||
value: |
|
value: |
|
||||||
#### What did you expect?
|
#### What did you expect?
|
||||||
|
|
2
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
2
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
|
@ -10,7 +10,7 @@ body:
|
||||||
id: usecase
|
id: usecase
|
||||||
attributes:
|
attributes:
|
||||||
label: Your use case
|
label: Your use case
|
||||||
description: What would you like to be able to do? Please feel welcome to include screenshots or mock ups.
|
description: Please feel welcome to include screenshots or mock ups.
|
||||||
placeholder: Tell us what you would like to do!
|
placeholder: Tell us what you would like to do!
|
||||||
value: |
|
value: |
|
||||||
#### What would you like to do?
|
#### What would you like to do?
|
||||||
|
|
39
.github/workflows/sanity_test.yml
vendored
39
.github/workflows/sanity_test.yml
vendored
|
@ -1,9 +1,9 @@
|
||||||
name: Sanity Test
|
name: Sanity Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request: { }
|
schedule:
|
||||||
push:
|
# At 20:00 every day UTC
|
||||||
branches: [ main, develop ]
|
- cron: '0 20 * * *'
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
|
@ -14,13 +14,15 @@ env:
|
||||||
jobs:
|
jobs:
|
||||||
integration-tests:
|
integration-tests:
|
||||||
name: Sanity Tests (Synapse)
|
name: Sanity Tests (Synapse)
|
||||||
runs-on: ubuntu-latest
|
runs-on: macos-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
api-level: [28]
|
api-level: [ 29 ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: develop
|
||||||
- name: Set up Python 3.8
|
- name: Set up Python 3.8
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
|
@ -46,11 +48,32 @@ jobs:
|
||||||
python3 -m venv .synapse
|
python3 -m venv .synapse
|
||||||
source .synapse/bin/activate
|
source .synapse/bin/activate
|
||||||
pip install synapse matrix-synapse
|
pip install synapse matrix-synapse
|
||||||
curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
|
curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh \
|
||||||
| sed s/127.0.0.1/0.0.0.0/g | bash
|
| sed s/127.0.0.1/0.0.0.0/g | sed 's/http:\/\/localhost/http:\/\/10.0.2.2/g' | bash -s -- --no-rate-limit
|
||||||
|
- uses: actions/setup-java@v2
|
||||||
|
with:
|
||||||
|
distribution: 'adopt'
|
||||||
|
java-version: '11'
|
||||||
- name: Run sanity tests on API ${{ matrix.api-level }}
|
- name: Run sanity tests on API ${{ matrix.api-level }}
|
||||||
uses: reactivecircus/android-emulator-runner@v2
|
uses: reactivecircus/android-emulator-runner@v2
|
||||||
|
continue-on-error: true # allow pipeline to upload failure results
|
||||||
with:
|
with:
|
||||||
|
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||||
api-level: ${{ matrix.api-level }}
|
api-level: ${{ matrix.api-level }}
|
||||||
script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest
|
emulator-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
|
||||||
|
script: |
|
||||||
|
adb root
|
||||||
|
adb logcat -c
|
||||||
|
touch emulator.log
|
||||||
|
chmod 777 emulator.log
|
||||||
|
adb logcat >> emulator.log &
|
||||||
|
./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || adb pull storage/emulated/0/Pictures/failure_screenshots
|
||||||
|
|
||||||
|
- name: Upload Failing Test Report Log
|
||||||
|
if: failure()
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: sanity-error-results
|
||||||
|
path: |
|
||||||
|
emulator.log
|
||||||
|
failure_screenshots/
|
||||||
|
|
2
.github/workflows/triage-incoming.yml
vendored
2
.github/workflows/triage-incoming.yml
vendored
|
@ -8,7 +8,7 @@ jobs:
|
||||||
automate-project-columns:
|
automate-project-columns:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: alex-page/github-project-automation-plus@v0.8.1
|
- uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488
|
||||||
with:
|
with:
|
||||||
project: Issue triage
|
project: Issue triage
|
||||||
column: Incoming
|
column: Incoming
|
||||||
|
|
124
.github/workflows/triage-move-labelled.yml
vendored
Normal file
124
.github/workflows/triage-move-labelled.yml
vendored
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
name: Move labelled issues to correct boards and columns
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [labeled]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
move_needs_info_issues:
|
||||||
|
name: Move X-Needs-Info issues to Need info on triage board
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: konradpabjan/move-labeled-or-milestoned-issue@219d384e03fa4b6460cd24f9f37d19eb033a4338
|
||||||
|
with:
|
||||||
|
action-token: "${{ secrets.ELEMENT_BOT_TOKEN }}"
|
||||||
|
project-url: "https://github.com/vector-im/element-android/projects/4"
|
||||||
|
column-name: "Need info"
|
||||||
|
label-name: "X-Needs-Info"
|
||||||
|
|
||||||
|
add_priority_design_issues_to_project:
|
||||||
|
name: Move priority X-Needs-Design issues to Design project board
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >
|
||||||
|
contains(github.event.issue.labels.*.name, 'X-Needs-Design') &&
|
||||||
|
(contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||||
|
contains(github.event.issue.labels.*.name, 'O-Occasional')) &&
|
||||||
|
(contains(github.event.issue.labels.*.name, 'S-Critical') ||
|
||||||
|
contains(github.event.issue.labels.*.name, 'S-Major') ||
|
||||||
|
contains(github.event.issue.labels.*.name, 'S-Minor'))
|
||||||
|
steps:
|
||||||
|
- uses: octokit/graphql-action@v2.x
|
||||||
|
id: add_to_project
|
||||||
|
with:
|
||||||
|
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||||
|
query: |
|
||||||
|
mutation add_to_project($projectid:String!,$contentid:String!) {
|
||||||
|
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
||||||
|
projectNextItem {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
projectid: ${{ env.PROJECT_ID }}
|
||||||
|
contentid: ${{ github.event.issue.node_id }}
|
||||||
|
env:
|
||||||
|
PROJECT_ID: "PN_kwDOAM0swc0sUA"
|
||||||
|
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
|
|
||||||
|
move_spaces_issues:
|
||||||
|
name: Move Spaces issues to 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: konradpabjan/move-labeled-or-milestoned-issue@219d384e03fa4b6460cd24f9f37d19eb033a4338
|
||||||
|
with:
|
||||||
|
action-token: "${{ secrets.ELEMENT_BOT_TOKEN }}"
|
||||||
|
project-url: "https://github.com/orgs/vector-im/projects/6"
|
||||||
|
column-name: "📥 Inbox"
|
||||||
|
label-name: "A-Spaces"
|
||||||
|
- uses: octokit/graphql-action@v2.x
|
||||||
|
id: add_to_delight2
|
||||||
|
with:
|
||||||
|
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||||
|
query: |
|
||||||
|
mutation add_to_project($projectid:String!,$contentid:String!) {
|
||||||
|
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: Move A-Voice Messages to Voice message board
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >
|
||||||
|
contains(github.event.issue.labels.*.name, 'A-Voice Messages')
|
||||||
|
steps:
|
||||||
|
- uses: octokit/graphql-action@v2.x
|
||||||
|
with:
|
||||||
|
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||||
|
query: |
|
||||||
|
mutation add_to_project($projectid:String!,$contentid:String!) {
|
||||||
|
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
||||||
|
projectNextItem {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
projectid: ${{ env.PROJECT_ID }}
|
||||||
|
contentid: ${{ github.event.issue.node_id }}
|
||||||
|
env:
|
||||||
|
PROJECT_ID: "PN_kwDOAM0swc2KCw"
|
||||||
|
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
|
|
||||||
|
move_threads_issues:
|
||||||
|
name: Move A-Threads to Thread board
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >
|
||||||
|
contains(github.event.issue.labels.*.name, 'A-Threads')
|
||||||
|
steps:
|
||||||
|
- uses: octokit/graphql-action@v2.x
|
||||||
|
with:
|
||||||
|
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||||
|
query: |
|
||||||
|
mutation add_to_project($projectid:String!,$contentid:String!) {
|
||||||
|
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
||||||
|
projectNextItem {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
projectid: ${{ env.PROJECT_ID }}
|
||||||
|
contentid: ${{ github.event.issue.node_id }}
|
||||||
|
env:
|
||||||
|
PROJECT_ID: "PN_kwDOAM0swc0rRA"
|
||||||
|
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
35
.github/workflows/triage-move-unlabelled.yml
vendored
Normal file
35
.github/workflows/triage-move-unlabelled.yml
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
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
|
||||||
|
if: >
|
||||||
|
${{
|
||||||
|
!contains(github.event.issue.labels.*.name, 'X-Needs-Info') }}
|
||||||
|
env:
|
||||||
|
BOARD_NAME: "Issue triage"
|
||||||
|
OWNER: ${{ github.repository_owner }}
|
||||||
|
REPO: ${{ github.event.repository.name }}
|
||||||
|
ISSUE: ${{ github.event.issue.number }}
|
||||||
|
steps:
|
||||||
|
- name: Check if issue is already in "${{ env.BOARD_NAME }}"
|
||||||
|
run: |
|
||||||
|
if curl -i -H 'Content-Type: application/json' -H "Authorization: bearer ${{ secrets.GITHUB_TOKEN }}" -X POST -d '{"query": "query($issue: Int!, $owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { issue(number: $issue) { projectCards { nodes { project { name } } } } } } ", "variables" : "{ \"issue\": '${ISSUE}', \"owner\": \"'${OWNER}'\", \"repo\": \"'${REPO}'\" }" }' https://api.github.com/graphql | grep "\b$BOARD_NAME\b"; then
|
||||||
|
echo "Issue is already in Project '$BOARD_NAME', proceeding";
|
||||||
|
echo "ALREADY_IN_BOARD=true" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "Issue is not in project '$BOARD_NAME', cancelling this workflow"
|
||||||
|
echo "ALREADY_IN_BOARD=false" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
- name: Move issue
|
||||||
|
uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488
|
||||||
|
if: ${{ env.ALREADY_IN_BOARD == 'true' }}
|
||||||
|
with:
|
||||||
|
project: Issue triage
|
||||||
|
column: Triaged
|
||||||
|
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
16
.github/workflows/triage-needs-info.yml
vendored
16
.github/workflows/triage-needs-info.yml
vendored
|
@ -1,16 +0,0 @@
|
||||||
name: Move X-Needs-Info into Need info column in the Issue triage board
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [labeled]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Move_Labeled_Issue_On_Project_Board:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: konradpabjan/move-labeled-or-milestoned-issue@v2.0
|
|
||||||
with:
|
|
||||||
action-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
project-url: "https://github.com/vector-im/element-android/projects/4"
|
|
||||||
column-name: "Need info"
|
|
||||||
label-name: "X-Needs-Info"
|
|
55
.github/workflows/triage-priority-bugs.yml
vendored
Normal file
55
.github/workflows/triage-priority-bugs.yml
vendored
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
name: Move P1 issues into the P1 column for the App Team and Crypto team
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [labeled, unlabeled]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
p1_issues_to_team_workboard:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
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') ||
|
||||||
|
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'))
|
||||||
|
steps:
|
||||||
|
- uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488
|
||||||
|
with:
|
||||||
|
project: Android App Team
|
||||||
|
column: P1
|
||||||
|
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
|
|
||||||
|
P1_issues_to_crypto_team_workboard:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
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') ||
|
||||||
|
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'))
|
||||||
|
steps:
|
||||||
|
- uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488
|
||||||
|
with:
|
||||||
|
project: Crypto Team
|
||||||
|
column: Ready
|
||||||
|
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
31
CHANGES.md
31
CHANGES.md
|
@ -1,3 +1,34 @@
|
||||||
|
Changes in Element v1.3.8 (2021-11-17)
|
||||||
|
======================================
|
||||||
|
|
||||||
|
Features ✨
|
||||||
|
----------
|
||||||
|
- Make notification text spoiler aware ([#3477](https://github.com/vector-im/element-android/issues/3477))
|
||||||
|
- Poll Feature - Create Poll Screen (Disabled for now) ([#4367](https://github.com/vector-im/element-android/issues/4367))
|
||||||
|
- Adds support for images inside message notifications ([#4402](https://github.com/vector-im/element-android/issues/4402))
|
||||||
|
|
||||||
|
Bugfixes 🐛
|
||||||
|
----------
|
||||||
|
- Render markdown in room list ([#452](https://github.com/vector-im/element-android/issues/452))
|
||||||
|
- Fix incorrect cropping of conversation icons ([#4424](https://github.com/vector-im/element-android/issues/4424))
|
||||||
|
- Fix potential NullPointerException crashes in Room and User account data sources ([#4428](https://github.com/vector-im/element-android/issues/4428))
|
||||||
|
- Unable to establish Olm outbound session from fallback key ([#4446](https://github.com/vector-im/element-android/issues/4446))
|
||||||
|
- Fixes intermittent crash on sign out due to the session being incorrectly recreated whilst being closed ([#4480](https://github.com/vector-im/element-android/issues/4480))
|
||||||
|
|
||||||
|
SDK API changes ⚠️
|
||||||
|
------------------
|
||||||
|
- Add content scanner API from MSC1453
|
||||||
|
API documentation : https://github.com/matrix-org/matrix-content-scanner#api ([#4392](https://github.com/vector-im/element-android/issues/4392))
|
||||||
|
- Breaking SDK API change to PushRuleListener, the separated callbacks have been merged into one with a data class which includes all the previously separated push information ([#4401](https://github.com/vector-im/element-android/issues/4401))
|
||||||
|
|
||||||
|
Other changes
|
||||||
|
-------------
|
||||||
|
- Finish migration from RxJava to Flow ([#4219](https://github.com/vector-im/element-android/issues/4219))
|
||||||
|
- Remove redundant text in feature request issue form ([#4257](https://github.com/vector-im/element-android/issues/4257))
|
||||||
|
- Add and improve issue triage workflows ([#4435](https://github.com/vector-im/element-android/issues/4435))
|
||||||
|
- Update issue template to bring in line with element-web ([#4452](https://github.com/vector-im/element-android/issues/4452))
|
||||||
|
|
||||||
|
|
||||||
Changes in Element v1.3.7 (2021-11-04)
|
Changes in Element v1.3.7 (2021-11-04)
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package im.vector.lib.attachmentviewer
|
package im.vector.lib.attachmentviewer
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
@ -141,7 +142,12 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||||
// New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
// New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
window.setDecorFitsSystemWindows(false)
|
window.setDecorFitsSystemWindows(false)
|
||||||
// New API instead of SYSTEM_UI_FLAG_IMMERSIVE
|
// New API instead of SYSTEM_UI_FLAG_IMMERSIVE
|
||||||
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
} else {
|
||||||
|
@SuppressLint("WrongConstant")
|
||||||
|
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
||||||
|
}
|
||||||
// New API instead of FLAG_TRANSLUCENT_STATUS
|
// New API instead of FLAG_TRANSLUCENT_STATUS
|
||||||
window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar)
|
window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar)
|
||||||
// new API instead of FLAG_TRANSLUCENT_NAVIGATION
|
// new API instead of FLAG_TRANSLUCENT_NAVIGATION
|
||||||
|
@ -347,7 +353,12 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||||
// new API instead of SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
// new API instead of SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
window.decorView.windowInsetsController?.hide(WindowInsets.Type.navigationBars())
|
window.decorView.windowInsetsController?.hide(WindowInsets.Type.navigationBars())
|
||||||
// New API instead of SYSTEM_UI_FLAG_IMMERSIVE
|
// New API instead of SYSTEM_UI_FLAG_IMMERSIVE
|
||||||
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
} else {
|
||||||
|
@SuppressLint("WrongConstant")
|
||||||
|
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
||||||
|
}
|
||||||
// New API instead of FLAG_TRANSLUCENT_STATUS
|
// New API instead of FLAG_TRANSLUCENT_STATUS
|
||||||
window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar)
|
window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar)
|
||||||
// New API instead of FLAG_TRANSLUCENT_NAVIGATION
|
// New API instead of FLAG_TRANSLUCENT_NAVIGATION
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
ext.versions = [
|
ext.versions = [
|
||||||
|
|
||||||
'minSdk' : 21,
|
'minSdk' : 21,
|
||||||
'compileSdk' : 30,
|
'compileSdk' : 31,
|
||||||
'targetSdk' : 30,
|
'targetSdk' : 31,
|
||||||
'sourceCompat' : JavaVersion.VERSION_11,
|
'sourceCompat' : JavaVersion.VERSION_11,
|
||||||
'targetCompat' : JavaVersion.VERSION_11,
|
'targetCompat' : JavaVersion.VERSION_11,
|
||||||
]
|
]
|
||||||
|
@ -11,13 +11,13 @@ def gradle = "7.0.3"
|
||||||
// Ref: https://kotlinlang.org/releases.html
|
// Ref: https://kotlinlang.org/releases.html
|
||||||
def kotlin = "1.5.31"
|
def kotlin = "1.5.31"
|
||||||
def kotlinCoroutines = "1.5.2"
|
def kotlinCoroutines = "1.5.2"
|
||||||
def dagger = "2.40"
|
def dagger = "2.40.1"
|
||||||
def retrofit = "2.9.0"
|
def retrofit = "2.9.0"
|
||||||
def arrow = "0.8.2"
|
def arrow = "0.8.2"
|
||||||
def markwon = "4.6.2"
|
def markwon = "4.6.2"
|
||||||
def moshi = "1.12.0"
|
def moshi = "1.12.0"
|
||||||
def lifecycle = "2.2.0"
|
def lifecycle = "2.4.0"
|
||||||
def rxBinding = "3.1.0"
|
def flowBinding = "1.2.0"
|
||||||
def epoxy = "4.6.2"
|
def epoxy = "4.6.2"
|
||||||
def mavericks = "2.4.0"
|
def mavericks = "2.4.0"
|
||||||
def glide = "4.12.0"
|
def glide = "4.12.0"
|
||||||
|
@ -26,7 +26,7 @@ def jjwt = "0.11.2"
|
||||||
def vanniktechEmoji = "0.8.0"
|
def vanniktechEmoji = "0.8.0"
|
||||||
|
|
||||||
// Testing
|
// Testing
|
||||||
def mockk = "1.12.0"
|
def mockk = "1.12.1"
|
||||||
def espresso = "3.4.0"
|
def espresso = "3.4.0"
|
||||||
def androidxTest = "1.4.0"
|
def androidxTest = "1.4.0"
|
||||||
|
|
||||||
|
@ -41,22 +41,23 @@ ext.libs = [
|
||||||
jetbrains : [
|
jetbrains : [
|
||||||
'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines",
|
'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines",
|
||||||
'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines",
|
'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines",
|
||||||
'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines"
|
'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines",
|
||||||
|
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
|
||||||
],
|
],
|
||||||
androidx : [
|
androidx : [
|
||||||
'appCompat' : "androidx.appcompat:appcompat:1.3.1",
|
'appCompat' : "androidx.appcompat:appcompat:1.3.1",
|
||||||
'core' : "androidx.core:core-ktx:1.6.0",
|
'core' : "androidx.core:core-ktx:1.7.0",
|
||||||
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
|
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
|
||||||
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3",
|
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3",
|
||||||
'fragmentKtx' : "androidx.fragment:fragment-ktx:1.3.6",
|
'fragmentKtx' : "androidx.fragment:fragment-ktx:1.3.6",
|
||||||
'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.1",
|
'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.1",
|
||||||
'work' : "androidx.work:work-runtime-ktx:2.6.0",
|
'work' : "androidx.work:work-runtime-ktx:2.7.0",
|
||||||
'autoFill' : "androidx.autofill:autofill:1.1.0",
|
'autoFill' : "androidx.autofill:autofill:1.1.0",
|
||||||
'preferenceKtx' : "androidx.preference:preference-ktx:1.1.1",
|
'preferenceKtx' : "androidx.preference:preference-ktx:1.1.1",
|
||||||
'junit' : "androidx.test.ext:junit:1.1.3",
|
'junit' : "androidx.test.ext:junit:1.1.3",
|
||||||
'lifecycleExtensions' : "androidx.lifecycle:lifecycle-extensions:$lifecycle",
|
'lifecycleCommon' : "androidx.lifecycle:lifecycle-common:$lifecycle",
|
||||||
'lifecycleJava8' : "androidx.lifecycle:lifecycle-common-java8:$lifecycle",
|
'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle",
|
||||||
'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1",
|
'lifecycleProcess' : "androidx.lifecycle:lifecycle-process:$lifecycle",
|
||||||
'datastore' : "androidx.datastore:datastore:1.0.0",
|
'datastore' : "androidx.datastore:datastore:1.0.0",
|
||||||
'datastorepreferences' : "androidx.datastore:datastore-preferences:1.0.0",
|
'datastorepreferences' : "androidx.datastore:datastore-preferences:1.0.0",
|
||||||
'pagingRuntimeKtx' : "androidx.paging:paging-runtime-ktx:2.1.2",
|
'pagingRuntimeKtx' : "androidx.paging:paging-runtime-ktx:2.1.2",
|
||||||
|
@ -102,7 +103,6 @@ ext.libs = [
|
||||||
'epoxyProcessor' : "com.airbnb.android:epoxy-processor:$epoxy",
|
'epoxyProcessor' : "com.airbnb.android:epoxy-processor:$epoxy",
|
||||||
'epoxyPaging' : "com.airbnb.android:epoxy-paging:$epoxy",
|
'epoxyPaging' : "com.airbnb.android:epoxy-paging:$epoxy",
|
||||||
'mavericks' : "com.airbnb.android:mavericks:$mavericks",
|
'mavericks' : "com.airbnb.android:mavericks:$mavericks",
|
||||||
'mavericksRx' : "com.airbnb.android:mavericks-rxjava2:$mavericks",
|
|
||||||
'mavericksTesting' : "com.airbnb.android:mavericks-testing:$mavericks"
|
'mavericksTesting' : "com.airbnb.android:mavericks-testing:$mavericks"
|
||||||
],
|
],
|
||||||
mockk : [
|
mockk : [
|
||||||
|
@ -115,13 +115,13 @@ ext.libs = [
|
||||||
'bigImageViewer' : "com.github.piasy:BigImageViewer:$bigImageViewer",
|
'bigImageViewer' : "com.github.piasy:BigImageViewer:$bigImageViewer",
|
||||||
'glideImageLoader' : "com.github.piasy:GlideImageLoader:$bigImageViewer",
|
'glideImageLoader' : "com.github.piasy:GlideImageLoader:$bigImageViewer",
|
||||||
'progressPieIndicator' : "com.github.piasy:ProgressPieIndicator:$bigImageViewer",
|
'progressPieIndicator' : "com.github.piasy:ProgressPieIndicator:$bigImageViewer",
|
||||||
'glideImageViewFactory' : "com.github.piasy:GlideImageViewFactory:$bigImageViewer"
|
'glideImageViewFactory' : "com.github.piasy:GlideImageViewFactory:$bigImageViewer",
|
||||||
|
'flowBinding' : "io.github.reactivecircus.flowbinding:flowbinding-android:$flowBinding",
|
||||||
|
'flowBindingAppcompat' : "io.github.reactivecircus.flowbinding:flowbinding-appcompat:$flowBinding",
|
||||||
|
'flowBindingMaterial' : "io.github.reactivecircus.flowbinding:flowbinding-material:$flowBinding"
|
||||||
],
|
],
|
||||||
jakewharton : [
|
jakewharton : [
|
||||||
'timber' : "com.jakewharton.timber:timber:5.0.1",
|
'timber' : "com.jakewharton.timber:timber:5.0.1"
|
||||||
'rxbinding' : "com.jakewharton.rxbinding3:rxbinding:$rxBinding",
|
|
||||||
'rxbindingAppcompat' : "com.jakewharton.rxbinding3:rxbinding-appcompat:$rxBinding",
|
|
||||||
'rxbindingMaterial' : "com.jakewharton.rxbinding3:rxbinding-material:$rxBinding"
|
|
||||||
],
|
],
|
||||||
jsonwebtoken: [
|
jsonwebtoken: [
|
||||||
'jjwtApi' : "io.jsonwebtoken:jjwt-api:$jjwt",
|
'jjwtApi' : "io.jsonwebtoken:jjwt-api:$jjwt",
|
||||||
|
|
41
docs/rx_flow_migration.md
Normal file
41
docs/rx_flow_migration.md
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
Useful links:
|
||||||
|
- https://github.com/ReactiveCircus/FlowBinding
|
||||||
|
- https://ivanisidrowu.github.io/kotlin/2020/08/09/Kotlin-Flow-Migration-And-Testing.html
|
||||||
|
|
||||||
|
|
||||||
|
Rx is now completely removed from Element dependencies.
|
||||||
|
Some examples of the changes:
|
||||||
|
|
||||||
|
```
|
||||||
|
sharedActionViewModel
|
||||||
|
.observe()
|
||||||
|
.subscribe { handleQuickActions(it) }
|
||||||
|
.disposeOnDestroyView()
|
||||||
|
```
|
||||||
|
|
||||||
|
became
|
||||||
|
|
||||||
|
```
|
||||||
|
sharedActionViewModel
|
||||||
|
.stream()
|
||||||
|
.onEach { handleQuickActions(it) }
|
||||||
|
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside fragment use
|
||||||
|
```
|
||||||
|
launchIn(viewLifecycleOwner.lifecycleScope)
|
||||||
|
```
|
||||||
|
Inside activity use
|
||||||
|
```
|
||||||
|
launchIn(lifecycleScope)
|
||||||
|
```
|
||||||
|
Inside viewModel use
|
||||||
|
```
|
||||||
|
launchIn(viewModelScope)
|
||||||
|
```
|
||||||
|
|
||||||
|
Also be aware that when using these scopes the coroutine is launched on Dispatchers.Main by default.
|
||||||
|
|
||||||
|
|
2
fastlane/metadata/android/cs-CZ/changelogs/40103050.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40103050.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Hlavní změny v této verzi: Přidání podpory přítomnosti pro místnost s přímými zprávami (poznámka: přítomnost je na matrix.org zakázána). Opět přidána podpora Android Auto.
|
||||||
|
Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.5
|
2
fastlane/metadata/android/cs-CZ/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40103060.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Hlavní změny v této verzi: Přidání podpory přítomnosti pro místnost s přímými zprávami (poznámka: přítomnost je na matrix.org zakázána). Opět přidána podpora Android Auto.
|
||||||
|
Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
|
@ -1,2 +1,2 @@
|
||||||
Main changes in this version: Bug fixes mainly regarding the notifications.
|
Main changes in this version: Bug fixes mainly regarding the notifications.
|
||||||
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.7
|
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
2
fastlane/metadata/android/en-US/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: Bug fixes!
|
||||||
|
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
2
fastlane/metadata/android/et/changelogs/40103050.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40103050.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Põhilised muutused selles versioonis: Lisasime otsevestlustele kasutaja võrguolekute toe (matrix.org puhul on välja lülitatud) ja uuesti lisasime Android Auto toe.
|
||||||
|
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.3.5
|
2
fastlane/metadata/android/et/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40103060.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Põhilised muutused selles versioonis: Lisasime otsevestlustele kasutaja võrguolekute toe (matrix.org puhul on välja lülitatud) ja uuesti lisasime Android Auto toe.
|
||||||
|
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
2
fastlane/metadata/android/fr-FR/changelogs/40103050.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40103050.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Principaux changements pour cette version : ajout du support pour les indicateurs de présence, dans les conversations privées (attention : les indicateurs de présence sont désactivés sur matrix.org). Réactivation de la prise en charge de Android Auto.
|
||||||
|
Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.5
|
2
fastlane/metadata/android/fr-FR/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40103060.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Principaux changements pour cette version : ajout du support pour les indicateurs de présence, dans les conversations privées (attention : les indicateurs de présence sont désactivés sur matrix.org). Réactivation de la prise en charge de Android Auto.
|
||||||
|
Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
|
@ -1 +1 @@
|
||||||
Groepsberjochtetsjinst - fersifere berjochten, groeps petearen en fideo skilje
|
Groepsberjochtetsjinst - fersifere berjochten, groepspetearen en fideobelje
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Element - Feilige Berjochtetsjinst
|
Element - Feilige berjochtetsjinst
|
||||||
|
|
2
fastlane/metadata/android/hu-HU/changelogs/40103050.txt
Normal file
2
fastlane/metadata/android/hu-HU/changelogs/40103050.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Fő változás ebben a verzióban: Állapot állítási lehetőség közvetlen beszélgetéseknél (megj.: a matrix.org-on az állapot jelzés ki van kapcsolva). Újra elérhető az Android Auto.
|
||||||
|
Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.3.5
|
2
fastlane/metadata/android/hu-HU/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/hu-HU/changelogs/40103060.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Fő változás ebben a verzióban: Állapot állítási lehetőség közvetlen beszélgetéseknél (megj.: a matrix.org-on az állapot jelzés ki van kapcsolva). Újra elérhető az Android Auto.
|
||||||
|
Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
|
@ -1,2 +1,2 @@
|
||||||
Perubahan utama di versi ini: Menambahkan dukungan presensi, untuk ruangan Pesan Langsung (diingat bahwa presensi dinonaktifkan di matrix.org). Menambahkan lagi dukungan Android Auto.
|
Perubahan utama di versi ini: Tambahkan dukungan presensi, untuk ruangan Pesan Langsung (diingat bahwa presensi dinonaktifkan di matrix.org). Tambahkan lagi dukungan Android Auto.
|
||||||
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.4
|
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.4
|
||||||
|
|
2
fastlane/metadata/android/id/changelogs/40103050.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40103050.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Perubahan utama dalam versi ini: Tambahkan dukungan presensi, untuk ruangan Pesan Langsung (diingat bahwa presensi dinonaktifkan di matrix.org). Tambahkan lagi dukungan Android Auto.
|
||||||
|
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.5
|
2
fastlane/metadata/android/id/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40103060.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Perubahan utama dalam versi ini: Tambahkan dukungan presensi, untuk ruangan Pesan Langsung (catatan: presensi dinonaktifkan di matrix.org). Tambahkan lagi dukungan Android Auto.
|
||||||
|
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
|
@ -1,42 +1,42 @@
|
||||||
Element adalah perpesanan yang aman dan aplikasi kolaborasi tim produktivitas yang ideal untuk obrolan grup saat bekerja jarak jauh. Aplikasi obrolan ini menggunakan enkripsi ujung-ke-ujung untuk memberikan konferensi video, berbagi file, dan panggilan suara.
|
Element adalah perpesanan yang aman dan aplikasi kolaborasi tim produktivitas yang ideal untuk obrolan grup saat bekerja jarak jauh. Aplikasi perpesanan ini menggunakan enkripsi ujung-ke-ujung untuk memberikan konferensi video, pembagian file, dan panggilan suara yang aman.
|
||||||
|
|
||||||
<b>Fitur Element termasuk:</b>
|
<b>Fitur Element termasuk</b>
|
||||||
- Alat komunikasi online yang canggih
|
- Alat komunikasi online yang canggih
|
||||||
- Pesan terenkripsi sepenuhnya untuk memungkinkan komunikasi perusahaan yang lebih aman, bahkan untuk pekerja jarak jauh
|
- Pesan-pesan yang dienkripsi sepenuhnya untuk memungkinkan komunikasi perusahaan yang lebih aman, bahkan untuk pekerja jarak jauh
|
||||||
- Obrolan terdesentralisasi berdasarkan framework sumber-terbuka Matrix
|
- Obrolan terdesentralisasi berdasarkan kerangka Matrix yang sumber terbuka
|
||||||
- Berbagi file dengan aman dengan data terenkripsi saat mengelola proyek
|
- Pembagian file aman dengan data terenkripsi saat mengelola proyek
|
||||||
- Obrolan video dengan VoIP dan berbagi layar
|
- Obrolan video dengan VoIP dan pembagian layar
|
||||||
- Integrasi yang mudah dengan alat kolaborasi online favorit Anda, alat manajemen proyek, layanan VoIP dan aplikasi perpesanan tim lainnya
|
- Integrasi yang mudah dengan alat kolaborasi online favorit Anda, alat manajemen proyek, layanan VoIP dan aplikasi perpesanan tim lainnya
|
||||||
|
|
||||||
Element benar-benar berbeda dari aplikasi perpesanan dan kolaborasi lainnya. Element beroperasi pada Matrix, jaringan terbuka untuk pengiriman pesan yang aman dan komunikasi terdesentralisasi. Matrix memungkinkan hosting sendiri untuk memberi pengguna kepemilikan maksimum dan kontrol data dan pesan mereka.
|
Element benar-benar berbeda dari aplikasi perpesanan dan aplikasi kolaborasi lainnya. Element beroperasi pada Matrix, jaringan terbuka untuk pengiriman pesan yang aman dan komunikasi terdesentralisasi.
|
||||||
|
|
||||||
<b>Pesan privasi dan terenkripsi</b>
|
<b>Perpesanan dengan privasi dan enkripsi</b>
|
||||||
Element melindungi Anda dari iklan yang tidak diinginkan, penambangan data dan taman berdinding. Element juga mengamankan semua data Anda, komunikasi video dan suara satu-ke-satu melalui enkripsi ujung-ke-ujung dan verifikasi perangkat yang ditandatangani secara silang.
|
Element melindungi Anda dari iklan yang tidak diinginkan, penambangan data dan taman berdinding. Element juga mengamankan semua data Anda, komunikasi video dan suara satu-ke-satu dengan enkripsi ujung-ke-ujung dan verifikasi perangkat menggunakan penandatanganan silang.
|
||||||
|
|
||||||
Element memberi Anda kendali atas privasi Anda sambil memungkinkan Anda untuk berkomunikasi dengan aman dengan siapa pun di jaringan Matrix, atau alat kolaborasi bisnis lainnya dengan mengintegrasikan dengan aplikasi seperti Slack.
|
Element memberikan Anda kendali atas privasi Anda sambil memungkinkan Anda untuk berkomunikasi dengan siapa saja secara aman di jaringan Matrix, atau alat kolaborasi bisnis lainnya dengan mengintegrasikan aplikasi-aplikasi seperti Slack.
|
||||||
|
|
||||||
<b>Element dapat dihost sendiri</b>
|
<b>Element dapat dihost sendiri</b>
|
||||||
Untuk memungkinkan lebih banyak kendali atas data dan percakapan sensitif Anda, Element bisa dihost sendiri atau Anda dapat memilih host berbasis Matrix - standar untuk komunikasi terdesentralisasi sumber-terbuka. Element memberi Anda privasi, kepatuhan keamanan, dan fleksibilitas integrasi.
|
Untuk memungkinkan lebih banyak kendali atas data dan pesan-pesan sensitif Anda, Element dapat dihost sendiri atau Anda dapat memilih host berbasis Matrix, standar untuk komunikasi terdesentralisasi sumber terbuka. Element memberi Anda privasi, kepatuhan keamanan, dan fleksibilitas integrasi.
|
||||||
|
|
||||||
<b>Miliki data Anda</b>
|
<b>Miliki data Anda</b>
|
||||||
Anda memutuskan di mana menyimpan data dan pesan Anda. Tanpa risiko penambangan data atau akses dari pihak ketiga.
|
Anda memutuskan di mana untuk menyimpan data dan pesan-pesan Anda, tanpa risiko penambangan data atau akses dari pihak ketiga.
|
||||||
|
|
||||||
Element menempatkan Anda dalam kendali dengan cara yang berbeda:
|
Element menempatkan Anda dalam kendali dengan cara yang berbeda:
|
||||||
1. Dapatkan akun gratis pada server publik matrix.org yang dihost oleh pengembang Matrix, atau memilih dari ribuan server publik yang dihost oleh sukarelawan
|
1. Dapatkan akun gratis pada server publik matrix.org yang dihost oleh pengembang Matrix, atau memilih dari ribuan server publik yang dihost oleh sukarelawan
|
||||||
2. Host sendiri akun Anda dengan menjalankan server pada infrastruktur IT Anda sendiri
|
2. Host sendiri akun Anda dengan menjalankan server pada infrastruktur IT Anda sendiri
|
||||||
3. Daftar untuk akun di server khusus dengan hanya berlangganan platform hosting Element Matrix Services
|
3. Daftar untuk akun di server khusus dengan berlangganan platform hosting Layanan Matrix Element
|
||||||
|
|
||||||
<b>Pesan terbuka dan kolaborasi</b>
|
<b>Pesan terbuka dan kolaborasi</b>
|
||||||
Anda dapat mengobrol dengan siapa saja di jaringan Matrix, apakah mereka menggunakan Element, aplikasi Matrix lain atau bahkan jika mereka menggunakan aplikasi perpesanan yang berbeda.
|
Anda dapat mengobrol dengan siapa saja di jaringan Matrix, jika mereka menggunakan Element, aplikasi Matrix lain atau bahkan menggunakan aplikasi perpesanan yang berbeda.
|
||||||
|
|
||||||
<b>Sangat aman</b>
|
<b>Sangat aman</b>
|
||||||
Enkripsi ujung-ke-ujung yang nyata (hanya mereka yang dalam percakapan dapat mendekripsi pesan), dan verifikasi perangkat menggunakan penandatanganan-silang.
|
Enkripsi ujung-ke-ujung yang nyata (hanya mereka yang dalam obrolan dapat mendekripsi pesan), dan verifikasi perangkat menggunakan penandatanganan silang.
|
||||||
|
|
||||||
<b>Komunikasi dan integrasi lengkap</b>
|
<b>Komunikasi dan integrasi lengkap</b>
|
||||||
Perpesanan, panggilan suara dan video, berbagi file, berbagi layar dan banyak integrasi, bot dan widget. Buat ruangan, komunitas, tetap terhubung dan selesaikan hal-hal.
|
Perpesanan, panggilan suara dan video, pembagian file, pembagian layar dan banyak integrasi bot dan widget. Buat ruangan dan komunitas, tetap terhubung dan selesaikan hal-hal penting.
|
||||||
|
|
||||||
<b>Ambil di mana Anda tinggalkan</b>
|
<b>Ambil di mana Anda tinggalkan</b>
|
||||||
Tetap terhubung di mana pun Anda berada dengan riwayat pesan yang sepenuhnya disinkronkan di semua perangkat Anda dan di web di https://app.element.io
|
Tetap terhubung di mana Anda berada, dengan riwayat pesan yang disinkronkan di semua perangkat Anda dan web di https://app.element.io
|
||||||
|
|
||||||
<b>Open source</b>
|
<b>Sumber terbuka</b>
|
||||||
Element Android adalah proyek sumber terbuka, di-host oleh GitHub. Silakan melaporkan bug dan/atau membuat kontribusi ke pengembangannya di https://github.com/vector-im/element-android
|
Element Android adalah proyek sumber terbuka, dihost oleh GitHub. Silakan laporkan masalah yang Anda temukan, atau membuat kontribusi ke pengembangannya di https://github.com/vector-im/element-android
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Perpesanan grup - pesan terenkripsi, panggilan grup dan video
|
Perpesanan grup - perpesanan, panggilan suara dan video grup terenkripsi
|
||||||
|
|
2
fastlane/metadata/android/it-IT/changelogs/40103050.txt
Normal file
2
fastlane/metadata/android/it-IT/changelogs/40103050.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Modifiche principali in questa versione: aggiunto supporto alla presenza per messaggi diretti (nota: la presenza è disattivata su matrix.org). Aggiunto di nuovo il supporto ad Android Auto.
|
||||||
|
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.5
|
2
fastlane/metadata/android/it-IT/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/it-IT/changelogs/40103060.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Modifiche principali in questa versione: aggiunto supporto alla presenza per i messaggi diretti (nota: la presenza è disattivata su matrix.org). Aggiunto di nuovo il supporto a Android Auto.
|
||||||
|
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
2
fastlane/metadata/android/pt-BR/changelogs/40103050.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40103050.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Principais mudanças nesta versão: Adicionar suporte a Presença, para sala de Mensagem Direta (nota: presença está desabilitada em matrix.org). Adicionar de novo suporte a Android Auto.
|
||||||
|
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.3.5
|
2
fastlane/metadata/android/pt-BR/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40103060.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Principais mudanças nesta versão: Adicionar suporte a Presença, para sala de Mensagem Direta (nota: presença está desabilitada em matrix.org). Adicionar de novo suporte a Android Auto.
|
||||||
|
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
|
@ -1,2 +1,2 @@
|
||||||
Ndryshimet kryesore në këtë version: Shtim mbulimi për Prani, për dhomë Mesazh i Drejtpërdrejtë (shënim: në matrix.org prania është e çaktivizuar. Shtim sërish i mbulimit për Android Auto.
|
Ndryshimet kryesore në këtë version: Shtim mbulimi për Prani, për dhomë Mesazh i Drejtpërdrejtë (shënim: në matrix.org prania është e çaktivizuar). Shtim sërish i mbulimit për Android Auto.
|
||||||
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.3.4
|
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.3.4
|
||||||
|
|
2
fastlane/metadata/android/sq/changelogs/40103050.txt
Normal file
2
fastlane/metadata/android/sq/changelogs/40103050.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Ndryshimet kryesore në këtë version: Shtim mbulimi për Prani, për dhomën Mesazh i Drejtpërdrejtë (shënim: prania është e çaktivizuar në matrix.org). Shtim sërish i mbulimit për Android Auto.
|
||||||
|
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.3.5
|
2
fastlane/metadata/android/sq/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/sq/changelogs/40103060.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Ndryshimet kryesore në këtë version: Shtim mbulimi për Prani, për dhomën Mesazh i Drejtpërdrejtë (shënim: prania është e çaktivizuar në matrix.org). Shtim sërish i mbulimit për Android Auto.
|
||||||
|
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
2
fastlane/metadata/android/sv-SE/changelogs/40103050.txt
Normal file
2
fastlane/metadata/android/sv-SE/changelogs/40103050.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Huvudsakliga ändringar i den här versionen: Lägg till närvarostöd för direktmeddelanden (obs: närvaro är inaktiverat på matrix.org). Lägg till stöd för Android Auto igen.
|
||||||
|
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.5
|
2
fastlane/metadata/android/sv-SE/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/sv-SE/changelogs/40103060.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Huvudsakliga ändringar i den här versionen: Lägg till närvarostöd för direktmeddelanden (obs: närvaro är inaktiverat på matrix.org). Lägg till stöd för Android Auto igen.
|
||||||
|
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
|
@ -1,2 +1,2 @@
|
||||||
Основні зміни в цій версії: Додано підтримку присутності для кімнати особистих повідомлень (примітка: присутність вимкнено на matrix.org. Знову додано підтримку Android Auto.
|
Основні зміни в цій версії: Додано підтримку присутності для кімнати особистих повідомлень (примітка: присутність вимкнено на matrix.org). Знову додано підтримку Android Auto.
|
||||||
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.4
|
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.4
|
||||||
|
|
2
fastlane/metadata/android/uk/changelogs/40103050.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40103050.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Основні зміни у цій версії: Додано підтримку присутності для кімнати особистих повідомлень (примітка: присутність вимкнена на matrix.org). Знову додано підтримку Android Auto.
|
||||||
|
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.5
|
2
fastlane/metadata/android/uk/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40103060.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Основні зміни у цій версії: Додано підтримку присутності для кімнати особистих повідомлень (примітка: присутність вимкнена на matrix.org). Знову додано підтримку Android Auto.
|
||||||
|
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
|
@ -1,2 +1,2 @@
|
||||||
此版本主要变化:为 Direct Message 聊天室添加 Presence 支持 (注意:Presence 在matrix.org 上是禁用的。再次添加 Android Auto 支持。
|
此版本主要变化:为 Direct Message 聊天室添加 Presence 支持 (注意:presence 在 matrix.org 上是禁用的)。再次添加 Android Auto 支持。
|
||||||
完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.4
|
完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.4
|
||||||
|
|
2
fastlane/metadata/android/zh-CN/changelogs/40103050.txt
Normal file
2
fastlane/metadata/android/zh-CN/changelogs/40103050.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
此版本的主要变化:为私信聊天室添加 Presence 支持 (注意:在 matrix.org 上 Presence 是禁用的)。再次添加 Android Auto 支持。
|
||||||
|
完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.5
|
2
fastlane/metadata/android/zh-CN/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/zh-CN/changelogs/40103060.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
此版本的主要变化:为私信聊天室添加 Presence 支持(注意:在 matrix.org 上 Presence 是禁用的)。再次添加 Android Auto 支持。
|
||||||
|
完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
2
fastlane/metadata/android/zh-TW/changelogs/40103050.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40103050.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
此版本中的主要變動:為直接訊息聊天室新增 Presence 支援(請注意:此功能在 matrix.org 上停用)。加回 Android Auto 支援。
|
||||||
|
完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.3.5
|
2
fastlane/metadata/android/zh-TW/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40103060.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
此版本中的主要變動:為直接訊息聊天室新增 Presence 支援(請注意:此功能在 matrix.org 上停用)。加回 Android Auto 支援。
|
||||||
|
完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=a8da5b02437a60819cad23e10fc7e9cf32bcb57029d9cb277e26eeff76ce014b
|
distributionSha256Sum=00b273629df4ce46e68df232161d5a7c4e495b9a029ce6e0420f071e21316867
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="?vctr_content_tertiary" android:state_enabled="false" />
|
||||||
|
<item android:color="?vctr_content_tertiary" android:state_focused="false" />
|
||||||
|
<item android:color="?colorPrimary" />
|
||||||
|
</selector>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="?colorPrimary" android:state_focused="true"/>
|
||||||
|
<item android:color="?colorPrimary" android:state_hovered="true"/>
|
||||||
|
<item android:color="?colorPrimary" android:state_enabled="false"/>
|
||||||
|
<item android:color="?vctr_content_quinary"/>
|
||||||
|
</selector>
|
|
@ -129,9 +129,9 @@
|
||||||
<color name="vctr_chat_effect_snow_background_light">@color/black_alpha</color>
|
<color name="vctr_chat_effect_snow_background_light">@color/black_alpha</color>
|
||||||
<color name="vctr_chat_effect_snow_background_dark">@android:color/transparent</color>
|
<color name="vctr_chat_effect_snow_background_dark">@android:color/transparent</color>
|
||||||
|
|
||||||
<attr name="vctr_voice_message_toast_background" format="color" />
|
<attr name="vctr_toast_background" format="color" />
|
||||||
<color name="vctr_voice_message_toast_background_light">@color/palette_black_900</color>
|
<color name="vctr_toast_background_light">@color/palette_black_900</color>
|
||||||
<color name="vctr_voice_message_toast_background_dark">@color/palette_gray_400</color>
|
<color name="vctr_toast_background_dark">@color/palette_gray_400</color>
|
||||||
|
|
||||||
<!-- Presence Indicator colors -->
|
<!-- Presence Indicator colors -->
|
||||||
<attr name="vctr_presence_indicator_offline" format="color" />
|
<attr name="vctr_presence_indicator_offline" format="color" />
|
||||||
|
|
|
@ -10,6 +10,11 @@
|
||||||
<item name="lineHeight">24sp</item>
|
<item name="lineHeight">24sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Vector.Button.CallToAction" parent="Widget.Vector.Button">
|
||||||
|
<item name="android:backgroundTint">@color/button_background_tint_selector</item>
|
||||||
|
<item name="android:textColor">@android:color/white</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="Widget.Vector.Button.Destructive">
|
<style name="Widget.Vector.Button.Destructive">
|
||||||
<item name="android:minWidth">94dp</item>
|
<item name="android:minWidth">94dp</item>
|
||||||
<item name="materialThemeOverlay">@style/VectorMaterialThemeOverlayDestructive</item>
|
<item name="materialThemeOverlay">@style/VectorMaterialThemeOverlayDestructive</item>
|
||||||
|
|
|
@ -1,14 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<!-- Default style for TextInputLayout -->
|
|
||||||
<style name="Widget.Vector.TextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox" />
|
|
||||||
|
|
||||||
<style name="Widget.Vector.TextInputLayout.Password">
|
|
||||||
<item name="endIconMode">password_toggle</item>
|
|
||||||
<item name="endIconTint">?vctr_content_secondary</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="Widget.Vector.EditText.Composer" parent="Widget.AppCompat.EditText">
|
<style name="Widget.Vector.EditText.Composer" parent="Widget.AppCompat.EditText">
|
||||||
<item name="android:background">@android:color/transparent</item>
|
<item name="android:background">@android:color/transparent</item>
|
||||||
<item name="android:inputType">textCapSentences|textMultiLine</item>
|
<item name="android:inputType">textCapSentences|textMultiLine</item>
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Default style for TextInputLayout -->
|
||||||
|
<style name="Widget.Vector.TextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox" />
|
||||||
|
|
||||||
|
<style name="Widget.Vector.TextInputLayout.Password">
|
||||||
|
<item name="endIconMode">password_toggle</item>
|
||||||
|
<item name="endIconTint">?vctr_content_secondary</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Vector.TextInputLayout.Form">
|
||||||
|
<item name="boxStrokeColor">@color/form_edit_text_stroke_color_selector</item>
|
||||||
|
<item name="android:textColorHint">@color/form_edit_text_hint_color_selector</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
15
library/ui-styles/src/main/res/values/styles_toast.xml
Normal file
15
library/ui-styles/src/main/res/values/styles_toast.xml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<style name="Widget.Vector.TextView.Caption.Toast">
|
||||||
|
<item name="android:paddingTop">8dp</item>
|
||||||
|
<item name="android:paddingBottom">8dp</item>
|
||||||
|
<item name="android:paddingStart">12dp</item>
|
||||||
|
<item name="android:paddingEnd">12dp</item>
|
||||||
|
<item name="android:background">@drawable/bg_round_corner_8dp</item>
|
||||||
|
<item name="android:backgroundTint">?vctr_toast_background</item>
|
||||||
|
<item name="android:textColor">@color/palette_white</item>
|
||||||
|
<item name="android:gravity">center</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
|
@ -12,15 +12,4 @@
|
||||||
<item name="direction">rightToLeft</item>
|
<item name="direction">rightToLeft</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.Vector.TextView.Caption.Toast">
|
|
||||||
<item name="android:paddingTop">8dp</item>
|
|
||||||
<item name="android:paddingBottom">8dp</item>
|
|
||||||
<item name="android:paddingStart">12dp</item>
|
|
||||||
<item name="android:paddingEnd">12dp</item>
|
|
||||||
<item name="android:background">@drawable/bg_round_corner_8dp</item>
|
|
||||||
<item name="android:backgroundTint">?vctr_voice_message_toast_background</item>
|
|
||||||
<item name="android:textColor">@color/palette_white</item>
|
|
||||||
<item name="android:gravity">center</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -140,8 +140,7 @@
|
||||||
<!-- Keywords -->
|
<!-- Keywords -->
|
||||||
<item name="vctr_keyword_style">@style/Widget.Vector.Keyword</item>
|
<item name="vctr_keyword_style">@style/Widget.Vector.Keyword</item>
|
||||||
|
|
||||||
<!-- Voice Message -->
|
<item name="vctr_toast_background">@color/vctr_toast_background_dark</item>
|
||||||
<item name="vctr_voice_message_toast_background">@color/vctr_voice_message_toast_background_dark</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Vector.Dark" parent="Base.Theme.Vector.Dark" />
|
<style name="Theme.Vector.Dark" parent="Base.Theme.Vector.Dark" />
|
||||||
|
|
|
@ -143,8 +143,7 @@
|
||||||
<!-- Keywords -->
|
<!-- Keywords -->
|
||||||
<item name="vctr_keyword_style">@style/Widget.Vector.Keyword</item>
|
<item name="vctr_keyword_style">@style/Widget.Vector.Keyword</item>
|
||||||
|
|
||||||
<!-- Voice Message -->
|
<item name="vctr_toast_background">@color/vctr_toast_background_light</item>
|
||||||
<item name="vctr_voice_message_toast_background">@color/vctr_voice_message_toast_background_light</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Vector.Light" parent="Base.Theme.Vector.Light" />
|
<style name="Theme.Vector.Light" parent="Base.Theme.Vector.Light" />
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
||||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||||
import org.matrix.android.sdk.api.session.pushers.Pusher
|
import org.matrix.android.sdk.api.session.pushers.Pusher
|
||||||
|
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
||||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent
|
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
|
@ -44,10 +45,10 @@ import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
||||||
|
|
||||||
class FlowSession(private val session: Session) {
|
class FlowSession(private val session: Session) {
|
||||||
|
|
||||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Flow<List<RoomSummary>> {
|
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams, sortOrder: RoomSortOrder = RoomSortOrder.NONE): Flow<List<RoomSummary>> {
|
||||||
return session.getRoomSummariesLive(queryParams).asFlow()
|
return session.getRoomSummariesLive(queryParams, sortOrder).asFlow()
|
||||||
.startWith(session.coroutineDispatchers.io) {
|
.startWith(session.coroutineDispatchers.io) {
|
||||||
session.getRoomSummaries(queryParams)
|
session.getRoomSummaries(queryParams, sortOrder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ buildscript {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "io.realm:realm-gradle-plugin:10.8.0"
|
classpath "io.realm:realm-gradle-plugin:10.8.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ android {
|
||||||
// that the app's state is completely cleared between tests.
|
// that the app's state is completely cleared between tests.
|
||||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||||
|
|
||||||
buildConfigField "String", "SDK_VERSION", "\"1.3.7\""
|
buildConfigField "String", "SDK_VERSION", "\"1.3.8\""
|
||||||
|
|
||||||
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||||
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
||||||
|
@ -44,6 +44,7 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
testOptions {
|
testOptions {
|
||||||
|
// Comment to run on Android 12
|
||||||
execution 'ANDROIDX_TEST_ORCHESTRATOR'
|
execution 'ANDROIDX_TEST_ORCHESTRATOR'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,8 +107,9 @@ dependencies {
|
||||||
implementation libs.androidx.appCompat
|
implementation libs.androidx.appCompat
|
||||||
implementation libs.androidx.core
|
implementation libs.androidx.core
|
||||||
|
|
||||||
implementation libs.androidx.lifecycleExtensions
|
// Lifecycle
|
||||||
implementation libs.androidx.lifecycleJava8
|
implementation libs.androidx.lifecycleCommon
|
||||||
|
implementation libs.androidx.lifecycleProcess
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
implementation libs.squareup.retrofit
|
implementation libs.squareup.retrofit
|
||||||
|
@ -156,10 +158,10 @@ dependencies {
|
||||||
implementation libs.apache.commonsImaging
|
implementation libs.apache.commonsImaging
|
||||||
|
|
||||||
// Phone number https://github.com/google/libphonenumber
|
// Phone number https://github.com/google/libphonenumber
|
||||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.36'
|
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.37'
|
||||||
|
|
||||||
testImplementation libs.tests.junit
|
testImplementation libs.tests.junit
|
||||||
testImplementation 'org.robolectric:robolectric:4.6.1'
|
testImplementation 'org.robolectric:robolectric:4.7'
|
||||||
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
||||||
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
||||||
testImplementation libs.mockk.mockk
|
testImplementation libs.mockk.mockk
|
||||||
|
|
|
@ -20,7 +20,18 @@ package org.matrix.android.sdk.api
|
||||||
* This class contains pattern to match Matrix Url, aka mxc urls
|
* This class contains pattern to match Matrix Url, aka mxc urls
|
||||||
*/
|
*/
|
||||||
object MatrixUrls {
|
object MatrixUrls {
|
||||||
|
/**
|
||||||
|
* "mxc" scheme, including "://". So "mxc://"
|
||||||
|
*/
|
||||||
const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
|
const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the String starts with "mxc://"
|
||||||
|
*/
|
||||||
fun String.isMxcUrl() = startsWith(MATRIX_CONTENT_URI_SCHEME)
|
fun String.isMxcUrl() = startsWith(MATRIX_CONTENT_URI_SCHEME)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the "mxc://" prefix. No op if the String is not a Mxc URL
|
||||||
|
*/
|
||||||
|
fun String.removeMxcPrefix() = removePrefix(MATRIX_CONTENT_URI_SCHEME)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ package org.matrix.android.sdk.api.failure
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ContentScannerError
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ScanFailure
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
@ -100,3 +102,19 @@ fun Throwable.isRegistrationAvailabilityError(): Boolean {
|
||||||
error.code == MatrixError.M_INVALID_USERNAME ||
|
error.code == MatrixError.M_INVALID_USERNAME ||
|
||||||
error.code == MatrixError.M_EXCLUSIVE)
|
error.code == MatrixError.M_EXCLUSIVE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to convert to a ScanFailure. Return null in the cases it's not possible
|
||||||
|
*/
|
||||||
|
fun Throwable.toScanFailure(): ScanFailure? {
|
||||||
|
return if (this is Failure.OtherServerError) {
|
||||||
|
tryOrNull {
|
||||||
|
MoshiProvider.providesMoshi()
|
||||||
|
.adapter(ContentScannerError::class.java)
|
||||||
|
.fromJson(errorBody)
|
||||||
|
}
|
||||||
|
?.let { ScanFailure(it, httpCode, this) }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* 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.pushrules
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.pushrules.rest.PushRule
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
|
||||||
|
data class PushEvents(
|
||||||
|
val matchedEvents: List<Pair<Event, PushRule>>,
|
||||||
|
val roomsJoined: Collection<String>,
|
||||||
|
val roomsLeft: Collection<String>,
|
||||||
|
val redactedEventIds: List<String>
|
||||||
|
)
|
|
@ -51,11 +51,7 @@ interface PushRuleService {
|
||||||
// fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule?
|
// fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule?
|
||||||
|
|
||||||
interface PushRuleListener {
|
interface PushRuleListener {
|
||||||
fun onMatchRule(event: Event, actions: List<Action>)
|
fun onEvents(pushEvents: PushEvents)
|
||||||
fun onRoomJoined(roomId: String)
|
|
||||||
fun onRoomLeft(roomId: String)
|
|
||||||
fun onEventRedacted(redactedEventId: String)
|
|
||||||
fun batchFinish()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getKeywords(): LiveData<Set<String>>
|
fun getKeywords(): LiveData<Set<String>>
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.matrix.android.sdk.api.session.cache.CacheService
|
||||||
import org.matrix.android.sdk.api.session.call.CallSignalingService
|
import org.matrix.android.sdk.api.session.call.CallSignalingService
|
||||||
import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
|
import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
|
||||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.events.EventService
|
import org.matrix.android.sdk.api.session.events.EventService
|
||||||
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
|
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
|
||||||
|
@ -192,6 +193,11 @@ interface Session :
|
||||||
*/
|
*/
|
||||||
fun cryptoService(): CryptoService
|
fun cryptoService(): CryptoService
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ContentScannerService associated with the session
|
||||||
|
*/
|
||||||
|
fun contentScannerService(): ContentScannerService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the identity service associated with the session
|
* Returns the identity service associated with the session
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.content
|
package org.matrix.android.sdk.api.session.content
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods for accessing content from the current session.
|
* This interface defines methods for accessing content from the current session.
|
||||||
*/
|
*/
|
||||||
|
@ -39,6 +41,15 @@ interface ContentUrlResolver {
|
||||||
*/
|
*/
|
||||||
fun resolveFullSize(contentUrl: String?): String?
|
fun resolveFullSize(contentUrl: String?): String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ResolvedMethod to download a URL
|
||||||
|
*
|
||||||
|
* @param contentUrl the Matrix media content URI (in the form of "mxc://...").
|
||||||
|
* @param elementToDecrypt Encryption data may be required if you use a content scanner
|
||||||
|
* @return the Method to access resource, or null if invalid
|
||||||
|
*/
|
||||||
|
fun resolveForDownload(contentUrl: String?, elementToDecrypt: ElementToDecrypt? = null): ResolvedMethod?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the actual URL for accessing the thumbnail image of a given Matrix media content URI.
|
* Get the actual URL for accessing the thumbnail image of a given Matrix media content URI.
|
||||||
*
|
*
|
||||||
|
@ -49,4 +60,9 @@ interface ContentUrlResolver {
|
||||||
* @return the URL to access the described resource, or null if the url is invalid.
|
* @return the URL to access the described resource, or null if the url is invalid.
|
||||||
*/
|
*/
|
||||||
fun resolveThumbnail(contentUrl: String?, width: Int, height: Int, method: ThumbnailMethod): String?
|
fun resolveThumbnail(contentUrl: String?, width: Int, height: Int, method: ThumbnailMethod): String?
|
||||||
|
|
||||||
|
sealed class ResolvedMethod {
|
||||||
|
data class GET(val url: String) : ResolvedMethod()
|
||||||
|
data class POST(val url: String, val jsonBody: String) : ResolvedMethod()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* 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.contentscanner
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class ContentScannerError(
|
||||||
|
@Json(name = "info") val info: String? = null,
|
||||||
|
@Json(name = "reason") val reason: String? = null
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
// 502 The server failed to request media from the media repo.
|
||||||
|
const val REASON_MCS_MEDIA_REQUEST_FAILED = "MCS_MEDIA_REQUEST_FAILED"
|
||||||
|
|
||||||
|
/* 400 The server failed to decrypt the encrypted media downloaded from the media repo.*/
|
||||||
|
const val REASON_MCS_MEDIA_FAILED_TO_DECRYPT = "MCS_MEDIA_FAILED_TO_DECRYPT"
|
||||||
|
|
||||||
|
/* 403 The server scanned the downloaded media but the antivirus script returned a non-zero exit code.*/
|
||||||
|
const val REASON_MCS_MEDIA_NOT_CLEAN = "MCS_MEDIA_NOT_CLEAN"
|
||||||
|
|
||||||
|
/* 403 The provided encrypted_body could not be decrypted. The client should request the public key of the server and then retry (once).*/
|
||||||
|
const val REASON_MCS_BAD_DECRYPTION = "MCS_BAD_DECRYPTION"
|
||||||
|
|
||||||
|
/* 400 The request body contains malformed JSON.*/
|
||||||
|
const val REASON_MCS_MALFORMED_JSON = "MCS_MALFORMED_JSON"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScanFailure(val error: ContentScannerError, val httpCode: Int, cause: Throwable? = null) : Throwable(cause = cause)
|
||||||
|
|
||||||
|
// For Glide, which deals with Exception and not with Throwable
|
||||||
|
fun ScanFailure.toException() = Exception(this)
|
||||||
|
fun Throwable.toScanFailure() = this.cause as? ScanFailure
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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.contentscanner
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||||
|
|
||||||
|
interface ContentScannerService {
|
||||||
|
|
||||||
|
val serverPublicKey: String?
|
||||||
|
|
||||||
|
fun getContentScannerServer(): String?
|
||||||
|
fun setScannerUrl(url: String?)
|
||||||
|
fun enableScanner(enabled: Boolean)
|
||||||
|
fun isScannerEnabled(): Boolean
|
||||||
|
fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean = true, fileInfo: ElementToDecrypt? = null): LiveData<Optional<ScanStatusInfo>>
|
||||||
|
fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current public curve25519 key that the AV server is advertising.
|
||||||
|
* @param callback on success callback containing the server public key
|
||||||
|
*/
|
||||||
|
suspend fun getServerPublicKey(forceDownload: Boolean = false): String?
|
||||||
|
suspend fun getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt? = null): ScanStatusInfo
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,18 +14,17 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.core.utils
|
package org.matrix.android.sdk.api.session.contentscanner
|
||||||
|
|
||||||
import io.reactivex.Completable
|
enum class ScanState {
|
||||||
import io.reactivex.Single
|
TRUSTED,
|
||||||
import io.reactivex.disposables.Disposable
|
INFECTED,
|
||||||
import io.reactivex.internal.functions.Functions
|
UNKNOWN,
|
||||||
import timber.log.Timber
|
IN_PROGRESS
|
||||||
|
|
||||||
fun <T> Single<T>.subscribeLogError(): Disposable {
|
|
||||||
return subscribe(Functions.emptyConsumer(), { Timber.e(it) })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Completable.subscribeLogError(): Disposable {
|
data class ScanStatusInfo(
|
||||||
return subscribe({}, { Timber.e(it) })
|
val state: ScanState,
|
||||||
}
|
val scanDateTimestamp: Long?,
|
||||||
|
val humanReadableMessage: String?
|
||||||
|
)
|
|
@ -102,6 +102,9 @@ object EventType {
|
||||||
// Relation Events
|
// Relation Events
|
||||||
const val REACTION = "m.reaction"
|
const val REACTION = "m.reaction"
|
||||||
|
|
||||||
|
// Poll
|
||||||
|
const val POLL_START = "org.matrix.msc3381.poll.start"
|
||||||
|
|
||||||
// Unwedging
|
// Unwedging
|
||||||
internal const val DUMMY = "m.dummy"
|
internal const val DUMMY = "m.dummy"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* 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.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MessagePollContent(
|
||||||
|
@Json(name = "org.matrix.msc3381.poll.start") val pollCreationInfo: PollCreationInfo? = null
|
||||||
|
)
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* 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.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class PollAnswer(
|
||||||
|
@Json(name = "id") val id: String? = null,
|
||||||
|
@Json(name = "org.matrix.msc1767.text") val answer: String? = null
|
||||||
|
)
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class PollCreationInfo(
|
||||||
|
@Json(name = "question") val question: PollQuestion? = null,
|
||||||
|
@Json(name = "kind") val kind: String? = "org.matrix.msc3381.poll.disclosed",
|
||||||
|
@Json(name = "max_selections") val maxSelections: Int = 1,
|
||||||
|
@Json(name = "answers") val answers: List<PollAnswer>? = null
|
||||||
|
)
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* 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.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class PollQuestion(
|
||||||
|
@Json(name = "org.matrix.msc1767.text") val question: String? = null
|
||||||
|
)
|
|
@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.OptionItem
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
|
|
||||||
|
@ -84,10 +83,10 @@ interface SendService {
|
||||||
/**
|
/**
|
||||||
* Send a poll to the room.
|
* Send a poll to the room.
|
||||||
* @param question the question
|
* @param question the question
|
||||||
* @param options list of (label, value)
|
* @param options list of options
|
||||||
* @return a [Cancelable]
|
* @return a [Cancelable]
|
||||||
*/
|
*/
|
||||||
fun sendPoll(question: String, options: List<OptionItem>): Cancelable
|
fun sendPoll(question: String, options: List<String>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to send a poll response.
|
* Method to send a poll response.
|
||||||
|
|
|
@ -28,8 +28,10 @@ import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
|
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||||
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||||
|
import org.matrix.android.sdk.api.util.ContentUtils
|
||||||
import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply
|
import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,20 +133,6 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get last Message body, after a possible edition
|
|
||||||
*/
|
|
||||||
fun TimelineEvent.getLastMessageBody(): String? {
|
|
||||||
val lastMessageContent = getLastMessageContent()
|
|
||||||
|
|
||||||
if (lastMessageContent != null) {
|
|
||||||
return lastMessageContent.newContent?.toModel<MessageContent>()?.body
|
|
||||||
?: lastMessageContent.body
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if it's a reply
|
* Returns true if it's a reply
|
||||||
*/
|
*/
|
||||||
|
@ -156,11 +144,25 @@ fun TimelineEvent.isEdition(): Boolean {
|
||||||
return root.isEdition()
|
return root.isEdition()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TimelineEvent.getTextEditableContent(): String? {
|
/**
|
||||||
val lastContent = getLastMessageContent()
|
* Get the latest message body, after a possible edition, stripping the reply prefix if necessary
|
||||||
|
*/
|
||||||
|
fun TimelineEvent.getTextEditableContent(): String {
|
||||||
|
val lastContentBody = getLastMessageContent()?.body ?: return ""
|
||||||
return if (isReply()) {
|
return if (isReply()) {
|
||||||
return extractUsefulTextFromReply(lastContent?.body ?: "")
|
extractUsefulTextFromReply(lastContentBody)
|
||||||
} else {
|
} else {
|
||||||
lastContent?.body ?: ""
|
lastContentBody
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the latest displayable content.
|
||||||
|
* Will take care to hide spoiler text
|
||||||
|
*/
|
||||||
|
fun MessageContent.getTextDisplayableContent(): String {
|
||||||
|
return newContent?.toModel<MessageTextContent>()?.matrixFormattedBody?.let { ContentUtils.formatSpoilerTextFromHtml(it) }
|
||||||
|
?: newContent?.toModel<MessageContent>()?.body
|
||||||
|
?: (this as MessageTextContent?)?.matrixFormattedBody?.let { ContentUtils.formatSpoilerTextFromHtml(it) }
|
||||||
|
?: body
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package org.matrix.android.sdk.api.util
|
package org.matrix.android.sdk.api.util
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.internal.util.unescapeHtml
|
||||||
|
|
||||||
object ContentUtils {
|
object ContentUtils {
|
||||||
fun extractUsefulTextFromReply(repliedBody: String): String {
|
fun extractUsefulTextFromReply(repliedBody: String): String {
|
||||||
val lines = repliedBody.lines()
|
val lines = repliedBody.lines()
|
||||||
|
@ -44,4 +46,15 @@ object ContentUtils {
|
||||||
}
|
}
|
||||||
return repliedBody
|
return repliedBody
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("RegExpRedundantEscape")
|
||||||
|
fun formatSpoilerTextFromHtml(formattedBody: String): String {
|
||||||
|
// var reason = "",
|
||||||
|
// can capture the spoiler reason for better formatting? ex. { reason = it.value; ">"}
|
||||||
|
return formattedBody.replace("(?<=<span data-mx-spoiler)=\\\".+?\\\">".toRegex(), ">")
|
||||||
|
.replace("(?<=<span data-mx-spoiler>).+?(?=</span>)".toRegex()) { SPOILER_CHAR.repeat(it.value.length) }
|
||||||
|
.unescapeHtml()
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val SPOILER_CHAR = "█"
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,11 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun stopSession(sessionId: String) {
|
||||||
|
val sessionComponent = sessionComponents[sessionId] ?: throw RuntimeException("You don't have a session for id $sessionId")
|
||||||
|
sessionComponent.session().stopSync()
|
||||||
|
}
|
||||||
|
|
||||||
fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
|
fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
|
||||||
return sessionComponents.getOrPut(sessionParams.credentials.sessionId()) {
|
return sessionComponents.getOrPut(sessionParams.credentials.sessionId()) {
|
||||||
DaggerSessionComponent
|
DaggerSessionComponent
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistration
|
||||||
import org.matrix.android.sdk.internal.auth.registration.RegisterAddThreePidTask
|
import org.matrix.android.sdk.internal.auth.registration.RegisterAddThreePidTask
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.session.content.DefaultContentUrlResolver
|
import org.matrix.android.sdk.internal.session.content.DefaultContentUrlResolver
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.DisabledContentScannerService
|
||||||
|
|
||||||
internal class DefaultLoginWizard(
|
internal class DefaultLoginWizard(
|
||||||
private val authAPI: AuthAPI,
|
private val authAPI: AuthAPI,
|
||||||
|
@ -44,7 +45,7 @@ internal class DefaultLoginWizard(
|
||||||
|
|
||||||
private val getProfileTask: GetProfileTask = DefaultGetProfileTask(
|
private val getProfileTask: GetProfileTask = DefaultGetProfileTask(
|
||||||
authAPI,
|
authAPI,
|
||||||
DefaultContentUrlResolver(pendingSessionData.homeServerConnectionConfig)
|
DefaultContentUrlResolver(pendingSessionData.homeServerConnectionConfig, DisabledContentScannerService())
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun getProfileInfo(matrixId: String): LoginProfileInfo {
|
override suspend fun getProfileInfo(matrixId: String): LoginProfileInfo {
|
||||||
|
|
|
@ -38,14 +38,22 @@ data class MXKey(
|
||||||
/**
|
/**
|
||||||
* signature user Id to [deviceid][signature]
|
* signature user Id to [deviceid][signature]
|
||||||
*/
|
*/
|
||||||
private val signatures: Map<String, Map<String, String>>
|
private val signatures: Map<String, Map<String, String>>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We have to store the original json because it can contain other fields
|
||||||
|
* that we don't support yet but they would be needed to check signatures
|
||||||
|
*/
|
||||||
|
private val rawMap: JsonDict
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the signed data map
|
* @return the signed data map
|
||||||
*/
|
*/
|
||||||
fun signalableJSONDictionary(): Map<String, Any> {
|
fun signalableJSONDictionary(): Map<String, Any> {
|
||||||
return mapOf("key" to value)
|
return rawMap.filter {
|
||||||
|
it.key != "signatures" && it.key != "unsigned"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,6 +90,7 @@ data class MXKey(
|
||||||
* <pre>
|
* <pre>
|
||||||
* "signed_curve25519:AAAAFw": {
|
* "signed_curve25519:AAAAFw": {
|
||||||
* "key": "IjwIcskng7YjYcn0tS8TUOT2OHHtBSfMpcfIczCgXj4",
|
* "key": "IjwIcskng7YjYcn0tS8TUOT2OHHtBSfMpcfIczCgXj4",
|
||||||
|
* "fallback" : true|false
|
||||||
* "signatures": {
|
* "signatures": {
|
||||||
* "@userId:matrix.org": {
|
* "@userId:matrix.org": {
|
||||||
* "ed25519:GMJRREOASV": "EUjp6pXzK9u3SDFR\/qLbzpOi3bEREeI6qMnKzXu992HsfuDDZftfJfiUXv9b\/Hqq1og4qM\/vCQJGTHAWMmgkCg"
|
* "ed25519:GMJRREOASV": "EUjp6pXzK9u3SDFR\/qLbzpOi3bEREeI6qMnKzXu992HsfuDDZftfJfiUXv9b\/Hqq1og4qM\/vCQJGTHAWMmgkCg"
|
||||||
|
@ -107,7 +116,8 @@ data class MXKey(
|
||||||
type = components[0],
|
type = components[0],
|
||||||
keyId = components[1],
|
keyId = components[1],
|
||||||
value = params["key"] as String,
|
value = params["key"] as String,
|
||||||
signatures = params["signatures"] as Map<String, Map<String, String>>
|
signatures = params["signatures"] as Map<String, Map<String, String>>,
|
||||||
|
rawMap = params
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,3 +37,7 @@ internal annotation class CryptoDatabase
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
internal annotation class IdentityDatabase
|
internal annotation class IdentityDatabase
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
internal annotation class ContentScannerDatabase
|
||||||
|
|
|
@ -38,6 +38,9 @@ internal object NetworkConstants {
|
||||||
// Integration
|
// Integration
|
||||||
const val URI_INTEGRATION_MANAGER_PATH = "_matrix/integrations/v1/"
|
const val URI_INTEGRATION_MANAGER_PATH = "_matrix/integrations/v1/"
|
||||||
|
|
||||||
|
// Content scanner
|
||||||
|
const val URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE = "_matrix/media_proxy/unstable/"
|
||||||
|
|
||||||
// Federation
|
// Federation
|
||||||
const val URI_FEDERATION_PATH = "_matrix/federation/v1/"
|
const val URI_FEDERATION_PATH = "_matrix/federation/v1/"
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,10 @@ import androidx.core.content.FileProvider
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.completeWith
|
import kotlinx.coroutines.completeWith
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||||
|
@ -118,12 +120,24 @@ internal class DefaultFileService @Inject constructor(
|
||||||
val cachedFiles = getFiles(url, fileName, mimeType, elementToDecrypt != null)
|
val cachedFiles = getFiles(url, fileName, mimeType, elementToDecrypt != null)
|
||||||
|
|
||||||
if (!cachedFiles.file.exists()) {
|
if (!cachedFiles.file.exists()) {
|
||||||
val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: throw IllegalArgumentException("url is null")
|
val resolvedUrl = contentUrlResolver.resolveForDownload(url, elementToDecrypt) ?: throw IllegalArgumentException("url is null")
|
||||||
|
|
||||||
val request = Request.Builder()
|
val request = when (resolvedUrl) {
|
||||||
.url(resolvedUrl)
|
is ContentUrlResolver.ResolvedMethod.GET -> {
|
||||||
.header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
|
Request.Builder()
|
||||||
.build()
|
.url(resolvedUrl.url)
|
||||||
|
.header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
is ContentUrlResolver.ResolvedMethod.POST -> {
|
||||||
|
Request.Builder()
|
||||||
|
.url(resolvedUrl.url)
|
||||||
|
.header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
|
||||||
|
.post(resolvedUrl.jsonBody.toRequestBody("application/json".toMediaType()))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val response = try {
|
val response = try {
|
||||||
okHttpClient.newCall(request).execute()
|
okHttpClient.newCall(request).execute()
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.cache.CacheService
|
||||||
import org.matrix.android.sdk.api.session.call.CallSignalingService
|
import org.matrix.android.sdk.api.session.call.CallSignalingService
|
||||||
import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
|
import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
|
||||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.events.EventService
|
import org.matrix.android.sdk.api.session.events.EventService
|
||||||
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
|
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
|
||||||
|
@ -124,6 +125,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
||||||
private val accountService: Lazy<AccountService>,
|
private val accountService: Lazy<AccountService>,
|
||||||
private val eventService: Lazy<EventService>,
|
private val eventService: Lazy<EventService>,
|
||||||
|
private val contentScannerService: Lazy<ContentScannerService>,
|
||||||
private val identityService: IdentityService,
|
private val identityService: IdentityService,
|
||||||
private val integrationManagerService: IntegrationManagerService,
|
private val integrationManagerService: IntegrationManagerService,
|
||||||
private val thirdPartyService: Lazy<ThirdPartyService>,
|
private val thirdPartyService: Lazy<ThirdPartyService>,
|
||||||
|
@ -174,8 +176,8 @@ internal class DefaultSession @Inject constructor(
|
||||||
lifecycleObservers.forEach {
|
lifecycleObservers.forEach {
|
||||||
it.onSessionStarted(this)
|
it.onSessionStarted(this)
|
||||||
}
|
}
|
||||||
sessionListeners.dispatch { _, listener ->
|
dispatchTo(sessionListeners) { session, listener ->
|
||||||
listener.onSessionStarted(this)
|
listener.onSessionStarted(session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,8 +219,8 @@ internal class DefaultSession @Inject constructor(
|
||||||
// timelineEventDecryptor.destroy()
|
// timelineEventDecryptor.destroy()
|
||||||
uiHandler.post {
|
uiHandler.post {
|
||||||
lifecycleObservers.forEach { it.onSessionStopped(this) }
|
lifecycleObservers.forEach { it.onSessionStopped(this) }
|
||||||
sessionListeners.dispatch { _, listener ->
|
dispatchTo(sessionListeners) { session, listener ->
|
||||||
listener.onSessionStopped(this)
|
listener.onSessionStopped(session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cryptoService.get().close()
|
cryptoService.get().close()
|
||||||
|
@ -249,8 +251,8 @@ internal class DefaultSession @Inject constructor(
|
||||||
lifecycleObservers.forEach {
|
lifecycleObservers.forEach {
|
||||||
it.onClearCache(this)
|
it.onClearCache(this)
|
||||||
}
|
}
|
||||||
sessionListeners.dispatch { _, listener ->
|
dispatchTo(sessionListeners) { session, listener ->
|
||||||
listener.onClearCache(this)
|
listener.onClearCache(session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
withContext(NonCancellable) {
|
withContext(NonCancellable) {
|
||||||
|
@ -260,8 +262,8 @@ internal class DefaultSession @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGlobalError(globalError: GlobalError) {
|
override fun onGlobalError(globalError: GlobalError) {
|
||||||
sessionListeners.dispatch { _, listener ->
|
dispatchTo(sessionListeners) { session, listener ->
|
||||||
listener.onGlobalError(this, globalError)
|
listener.onGlobalError(session, globalError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,6 +277,8 @@ internal class DefaultSession @Inject constructor(
|
||||||
|
|
||||||
override fun cryptoService(): CryptoService = cryptoService.get()
|
override fun cryptoService(): CryptoService = cryptoService.get()
|
||||||
|
|
||||||
|
override fun contentScannerService(): ContentScannerService = contentScannerService.get()
|
||||||
|
|
||||||
override fun identityService() = identityService
|
override fun identityService() = identityService
|
||||||
|
|
||||||
override fun fileService(): FileService = defaultFileService.get()
|
override fun fileService(): FileService = defaultFileService.get()
|
||||||
|
|
|
@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.session.cache.CacheModule
|
||||||
import org.matrix.android.sdk.internal.session.call.CallModule
|
import org.matrix.android.sdk.internal.session.call.CallModule
|
||||||
import org.matrix.android.sdk.internal.session.content.ContentModule
|
import org.matrix.android.sdk.internal.session.content.ContentModule
|
||||||
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
|
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerModule
|
||||||
import org.matrix.android.sdk.internal.session.filter.FilterModule
|
import org.matrix.android.sdk.internal.session.filter.FilterModule
|
||||||
import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
|
import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
|
||||||
import org.matrix.android.sdk.internal.session.group.GroupModule
|
import org.matrix.android.sdk.internal.session.group.GroupModule
|
||||||
|
@ -94,6 +95,7 @@ import org.matrix.android.sdk.internal.util.system.SystemModule
|
||||||
AccountModule::class,
|
AccountModule::class,
|
||||||
FederationModule::class,
|
FederationModule::class,
|
||||||
CallModule::class,
|
CallModule::class,
|
||||||
|
ContentScannerModule::class,
|
||||||
SearchModule::class,
|
SearchModule::class,
|
||||||
ThirdPartyModule::class,
|
ThirdPartyModule::class,
|
||||||
SpaceModule::class,
|
SpaceModule::class,
|
||||||
|
|
|
@ -18,15 +18,11 @@ package org.matrix.android.sdk.internal.session
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.internal.SessionManager
|
|
||||||
import org.matrix.android.sdk.internal.di.SessionId
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class SessionListeners @Inject constructor(
|
internal class SessionListeners @Inject constructor() {
|
||||||
@SessionId private val sessionId: String,
|
|
||||||
private val sessionManager: SessionManager) {
|
|
||||||
|
|
||||||
private val listeners = mutableSetOf<Session.Listener>()
|
private val listeners = mutableSetOf<Session.Listener>()
|
||||||
|
|
||||||
|
@ -42,18 +38,19 @@ internal class SessionListeners @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dispatch(block: (Session, Session.Listener) -> Unit) {
|
fun dispatch(session: Session, block: (Session, Session.Listener) -> Unit) {
|
||||||
synchronized(listeners) {
|
synchronized(listeners) {
|
||||||
val session = getSession() ?: return Unit.also {
|
|
||||||
Timber.w("You don't have any attached session")
|
|
||||||
}
|
|
||||||
listeners.forEach {
|
listeners.forEach {
|
||||||
tryOrNull { block(session, it) }
|
tryOrNull { block(session, it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private fun getSession(): Session? {
|
|
||||||
return sessionManager.getSessionComponent(sessionId)?.session()
|
internal fun Session?.dispatchTo(sessionListeners: SessionListeners, block: (Session, Session.Listener) -> Unit) {
|
||||||
}
|
if (this == null) {
|
||||||
|
Timber.w("You don't have any attached session")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sessionListeners.dispatch(this, block)
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
|
||||||
|
|
||||||
override suspend fun execute(params: DeactivateAccountTask.Params) {
|
override suspend fun execute(params: DeactivateAccountTask.Params) {
|
||||||
val deactivateAccountParams = DeactivateAccountParams.create(params.userAuthParam, params.eraseAllData)
|
val deactivateAccountParams = DeactivateAccountParams.create(params.userAuthParam, params.eraseAllData)
|
||||||
|
cleanupSession.stopActiveTasks()
|
||||||
val canCleanup = try {
|
val canCleanup = try {
|
||||||
executeRequest(globalErrorReceiver) {
|
executeRequest(globalErrorReceiver) {
|
||||||
accountAPI.deactivate(deactivateAccountParams)
|
accountAPI.deactivate(deactivateAccountParams)
|
||||||
|
@ -71,7 +71,7 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
|
||||||
runCatching { identityDisconnectTask.execute(Unit) }
|
runCatching { identityDisconnectTask.execute(Unit) }
|
||||||
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
|
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
|
||||||
|
|
||||||
cleanupSession.handle()
|
cleanupSession.cleanup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,20 +50,26 @@ internal class CleanupSession @Inject constructor(
|
||||||
@CryptoDatabase private val realmCryptoConfiguration: RealmConfiguration,
|
@CryptoDatabase private val realmCryptoConfiguration: RealmConfiguration,
|
||||||
@UserMd5 private val userMd5: String
|
@UserMd5 private val userMd5: String
|
||||||
) {
|
) {
|
||||||
suspend fun handle() {
|
|
||||||
|
fun stopActiveTasks() {
|
||||||
|
Timber.d("Cleanup: cancel pending works...")
|
||||||
|
workManagerProvider.cancelAllWorks()
|
||||||
|
|
||||||
|
Timber.d("Cleanup: stop session...")
|
||||||
|
sessionManager.stopSession(sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun cleanup() {
|
||||||
val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration)
|
val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration)
|
||||||
val cryptoRealmCount = Realm.getGlobalInstanceCount(realmCryptoConfiguration)
|
val cryptoRealmCount = Realm.getGlobalInstanceCount(realmCryptoConfiguration)
|
||||||
Timber.d("Realm instance ($sessionRealmCount - $cryptoRealmCount)")
|
Timber.d("Realm instance ($sessionRealmCount - $cryptoRealmCount)")
|
||||||
|
|
||||||
Timber.d("Cleanup: delete session params...")
|
|
||||||
sessionParamsStore.delete(sessionId)
|
|
||||||
|
|
||||||
Timber.d("Cleanup: cancel pending works...")
|
|
||||||
workManagerProvider.cancelAllWorks()
|
|
||||||
|
|
||||||
Timber.d("Cleanup: release session...")
|
Timber.d("Cleanup: release session...")
|
||||||
sessionManager.releaseSession(sessionId)
|
sessionManager.releaseSession(sessionId)
|
||||||
|
|
||||||
|
Timber.d("Cleanup: delete session params...")
|
||||||
|
sessionParamsStore.delete(sessionId)
|
||||||
|
|
||||||
Timber.d("Cleanup: clear session data...")
|
Timber.d("Cleanup: clear session data...")
|
||||||
clearSessionDataTask.execute(Unit)
|
clearSessionDataTask.execute(Unit)
|
||||||
|
|
||||||
|
|
|
@ -16,20 +16,45 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.content
|
package org.matrix.android.sdk.internal.session.content
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixUrls
|
|
||||||
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
|
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
|
||||||
|
import org.matrix.android.sdk.api.MatrixUrls.removeMxcPrefix
|
||||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
|
||||||
|
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||||
import org.matrix.android.sdk.internal.network.NetworkConstants
|
import org.matrix.android.sdk.internal.network.NetworkConstants
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.ScanEncryptorUtils
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.model.toJson
|
||||||
import org.matrix.android.sdk.internal.util.ensureTrailingSlash
|
import org.matrix.android.sdk.internal.util.ensureTrailingSlash
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver {
|
internal class DefaultContentUrlResolver @Inject constructor(
|
||||||
|
homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
|
private val scannerService: ContentScannerService
|
||||||
|
) : ContentUrlResolver {
|
||||||
|
|
||||||
private val baseUrl = homeServerConnectionConfig.homeServerUriBase.toString().ensureTrailingSlash()
|
private val baseUrl = homeServerConnectionConfig.homeServerUriBase.toString().ensureTrailingSlash()
|
||||||
|
|
||||||
override val uploadUrl = baseUrl + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload"
|
override val uploadUrl = baseUrl + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload"
|
||||||
|
|
||||||
|
override fun resolveForDownload(contentUrl: String?, elementToDecrypt: ElementToDecrypt?): ContentUrlResolver.ResolvedMethod? {
|
||||||
|
return if (scannerService.isScannerEnabled() && elementToDecrypt != null) {
|
||||||
|
val baseUrl = scannerService.getContentScannerServer()
|
||||||
|
val sep = if (baseUrl?.endsWith("/") == true) "" else "/"
|
||||||
|
|
||||||
|
val url = baseUrl + sep + NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE + "download_encrypted"
|
||||||
|
|
||||||
|
ContentUrlResolver.ResolvedMethod.POST(
|
||||||
|
url = url,
|
||||||
|
jsonBody = ScanEncryptorUtils
|
||||||
|
.getDownloadBodyAndEncryptIfNeeded(scannerService.serverPublicKey, contentUrl ?: "", elementToDecrypt)
|
||||||
|
.toJson()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
resolveFullSize(contentUrl)?.let { ContentUrlResolver.ResolvedMethod.GET(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun resolveFullSize(contentUrl: String?): String? {
|
override fun resolveFullSize(contentUrl: String?): String? {
|
||||||
return contentUrl
|
return contentUrl
|
||||||
// do not allow non-mxc content URLs
|
// do not allow non-mxc content URLs
|
||||||
|
@ -37,7 +62,7 @@ internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectio
|
||||||
?.let {
|
?.let {
|
||||||
resolve(
|
resolve(
|
||||||
contentUrl = it,
|
contentUrl = it,
|
||||||
prefix = NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "download/"
|
toThumbnail = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,16 +74,27 @@ internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectio
|
||||||
?.let {
|
?.let {
|
||||||
resolve(
|
resolve(
|
||||||
contentUrl = it,
|
contentUrl = it,
|
||||||
prefix = NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "thumbnail/",
|
toThumbnail = true,
|
||||||
params = "?width=$width&height=$height&method=${method.value}"
|
params = "?width=$width&height=$height&method=${method.value}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolve(contentUrl: String,
|
private fun resolve(contentUrl: String,
|
||||||
prefix: String,
|
toThumbnail: Boolean,
|
||||||
params: String = ""): String? {
|
params: String = ""): String {
|
||||||
var serverAndMediaId = contentUrl.removePrefix(MatrixUrls.MATRIX_CONTENT_URI_SCHEME)
|
var serverAndMediaId = contentUrl.removeMxcPrefix()
|
||||||
|
|
||||||
|
val apiPath = if (scannerService.isScannerEnabled()) {
|
||||||
|
NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE
|
||||||
|
} else {
|
||||||
|
NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0
|
||||||
|
}
|
||||||
|
val prefix = if (toThumbnail) {
|
||||||
|
apiPath + "thumbnail/"
|
||||||
|
} else {
|
||||||
|
apiPath + "download/"
|
||||||
|
}
|
||||||
val fragmentOffset = serverAndMediaId.indexOf("#")
|
val fragmentOffset = serverAndMediaId.indexOf("#")
|
||||||
var fragment = ""
|
var fragment = ""
|
||||||
if (fragmentOffset >= 0) {
|
if (fragmentOffset >= 0) {
|
||||||
|
@ -66,6 +102,11 @@ internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectio
|
||||||
serverAndMediaId = serverAndMediaId.substring(0, fragmentOffset)
|
serverAndMediaId = serverAndMediaId.substring(0, fragmentOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseUrl + prefix + serverAndMediaId + params + fragment
|
val resolvedUrl = if (scannerService.isScannerEnabled()) {
|
||||||
|
scannerService.getContentScannerServer()!!.ensureTrailingSlash()
|
||||||
|
} else {
|
||||||
|
baseUrl
|
||||||
|
}
|
||||||
|
return resolvedUrl + prefix + serverAndMediaId + params + fragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.session.contentscanner
|
||||||
|
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import org.matrix.android.sdk.internal.network.NetworkConstants
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.model.DownloadBody
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.model.ScanResponse
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.model.ServerPublicKeyResponse
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://github.com/matrix-org/matrix-content-scanner
|
||||||
|
*/
|
||||||
|
internal interface ContentScannerApi {
|
||||||
|
|
||||||
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE + "download_encrypted")
|
||||||
|
suspend fun downloadEncrypted(@Body info: DownloadBody): ResponseBody
|
||||||
|
|
||||||
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE + "scan_encrypted")
|
||||||
|
suspend fun scanFile(@Body info: DownloadBody): ScanResponse
|
||||||
|
|
||||||
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE + "public_key")
|
||||||
|
suspend fun getServerPublicKey(): ServerPublicKeyResponse
|
||||||
|
|
||||||
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE + "scan/{domain}/{mediaId}")
|
||||||
|
suspend fun scanMedia(@Path(value = "domain") domain: String, @Path(value = "mediaId") mediaId: String): ScanResponse
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.session.contentscanner
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@SessionScope
|
||||||
|
internal class ContentScannerApiProvider @Inject constructor() {
|
||||||
|
var contentScannerApi: ContentScannerApi? = null
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.session.contentscanner
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmKeysUtils
|
||||||
|
import org.matrix.android.sdk.internal.di.ContentScannerDatabase
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
||||||
|
import org.matrix.android.sdk.internal.di.UserMd5
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionModule
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.db.ContentScannerRealmModule
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.db.RealmContentScannerStore
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.tasks.DefaultDownloadEncryptedTask
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.tasks.DefaultGetServerPublicKeyTask
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.tasks.DefaultScanEncryptedTask
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.tasks.DefaultScanMediaTask
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.tasks.DownloadEncryptedTask
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.tasks.GetServerPublicKeyTask
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanEncryptedTask
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanMediaTask
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@Module
|
||||||
|
internal abstract class ContentScannerModule {
|
||||||
|
@Module
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
@ContentScannerDatabase
|
||||||
|
@SessionScope
|
||||||
|
fun providesContentScannerRealmConfiguration(realmKeysUtils: RealmKeysUtils,
|
||||||
|
@SessionFilesDirectory directory: File,
|
||||||
|
@UserMd5 userMd5: String): RealmConfiguration {
|
||||||
|
return RealmConfiguration.Builder()
|
||||||
|
.directory(directory)
|
||||||
|
.name("matrix-sdk-content-scanning.realm")
|
||||||
|
.apply {
|
||||||
|
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||||
|
}
|
||||||
|
.allowWritesOnUiThread(true)
|
||||||
|
.modules(ContentScannerRealmModule())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindContentScannerService(service: DisabledContentScannerService): ContentScannerService
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindContentScannerStore(store: RealmContentScannerStore): ContentScannerStore
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindDownloadEncryptedTask(task: DefaultDownloadEncryptedTask): DownloadEncryptedTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetServerPublicKeyTask(task: DefaultGetServerPublicKeyTask): GetServerPublicKeyTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindScanMediaTask(task: DefaultScanMediaTask): ScanMediaTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindScanEncryptedTask(task: DefaultScanEncryptedTask): ScanEncryptedTask
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.session.contentscanner
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import dagger.Lazy
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ScanState
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
|
||||||
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||||
|
import org.matrix.android.sdk.internal.di.Unauthenticated
|
||||||
|
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.tasks.GetServerPublicKeyTask
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanEncryptedTask
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanMediaTask
|
||||||
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@SessionScope
|
||||||
|
internal class DefaultContentScannerService @Inject constructor(
|
||||||
|
private val retrofitFactory: RetrofitFactory,
|
||||||
|
@Unauthenticated
|
||||||
|
private val okHttpClient: Lazy<OkHttpClient>,
|
||||||
|
private val contentScannerApiProvider: ContentScannerApiProvider,
|
||||||
|
private val contentScannerStore: ContentScannerStore,
|
||||||
|
private val getServerPublicKeyTask: GetServerPublicKeyTask,
|
||||||
|
private val scanEncryptedTask: ScanEncryptedTask,
|
||||||
|
private val scanMediaTask: ScanMediaTask,
|
||||||
|
private val taskExecutor: TaskExecutor
|
||||||
|
) : ContentScannerService {
|
||||||
|
|
||||||
|
// Cache public key in memory
|
||||||
|
override var serverPublicKey: String? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
override fun getContentScannerServer(): String? {
|
||||||
|
return contentScannerStore.getScannerUrl()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getServerPublicKey(forceDownload: Boolean): String? {
|
||||||
|
val api = contentScannerApiProvider.contentScannerApi ?: throw IllegalArgumentException("No content scanner define")
|
||||||
|
|
||||||
|
if (!forceDownload && serverPublicKey != null) {
|
||||||
|
return serverPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return getServerPublicKeyTask.execute(GetServerPublicKeyTask.Params(api)).also {
|
||||||
|
serverPublicKey = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt?): ScanStatusInfo {
|
||||||
|
val result = if (fileInfo != null) {
|
||||||
|
scanEncryptedTask.execute(ScanEncryptedTask.Params(
|
||||||
|
mxcUrl = mxcUrl,
|
||||||
|
publicServerKey = getServerPublicKey(false),
|
||||||
|
encryptedInfo = fileInfo
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
scanMediaTask.execute(ScanMediaTask.Params(mxcUrl))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScanStatusInfo(
|
||||||
|
state = if (result.clean) ScanState.TRUSTED else ScanState.INFECTED,
|
||||||
|
humanReadableMessage = result.info,
|
||||||
|
scanDateTimestamp = System.currentTimeMillis()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setScannerUrl(url: String?) = contentScannerStore.setScannerUrl(url).also {
|
||||||
|
if (url == null) {
|
||||||
|
contentScannerApiProvider.contentScannerApi = null
|
||||||
|
serverPublicKey = null
|
||||||
|
} else {
|
||||||
|
val api = retrofitFactory
|
||||||
|
.create(okHttpClient, url)
|
||||||
|
.create(ContentScannerApi::class.java)
|
||||||
|
contentScannerApiProvider.contentScannerApi = api
|
||||||
|
|
||||||
|
taskExecutor.executorScope.launch {
|
||||||
|
try {
|
||||||
|
getServerPublicKey(true)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e("Failed to get public server api")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enableScanner(enabled: Boolean) = contentScannerStore.enableScanner(enabled)
|
||||||
|
|
||||||
|
override fun isScannerEnabled(): Boolean = contentScannerStore.isScanEnabled()
|
||||||
|
|
||||||
|
override fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo? {
|
||||||
|
return contentScannerStore.getScanResult(mxcUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean, fileInfo: ElementToDecrypt?): LiveData<Optional<ScanStatusInfo>> {
|
||||||
|
val data = contentScannerStore.getLiveScanResult(mxcUrl)
|
||||||
|
if (fetchIfNeeded && !contentScannerStore.isScanResultKnownOrInProgress(mxcUrl, getContentScannerServer())) {
|
||||||
|
taskExecutor.executorScope.launch {
|
||||||
|
try {
|
||||||
|
getScanResultForAttachment(mxcUrl, fileInfo)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e("Failed to get file status : ${failure.localizedMessage}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.session.contentscanner
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
|
||||||
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created to by-pass ProfileTask execution in LoginWizard.
|
||||||
|
*/
|
||||||
|
@SessionScope
|
||||||
|
internal class DisabledContentScannerService @Inject constructor() : ContentScannerService {
|
||||||
|
|
||||||
|
override val serverPublicKey: String?
|
||||||
|
get() = null
|
||||||
|
|
||||||
|
override fun getContentScannerServer(): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getServerPublicKey(forceDownload: Boolean): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt?): ScanStatusInfo {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setScannerUrl(url: String?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enableScanner(enabled: Boolean) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isScannerEnabled(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean, fileInfo: ElementToDecrypt?): LiveData<Optional<ScanStatusInfo>> {
|
||||||
|
return MutableLiveData()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.session.contentscanner
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileKey
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tools.withOlmEncryption
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.model.DownloadBody
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.model.EncryptedBody
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.model.toCanonicalJson
|
||||||
|
|
||||||
|
internal object ScanEncryptorUtils {
|
||||||
|
|
||||||
|
fun getDownloadBodyAndEncryptIfNeeded(publicServerKey: String?, mxcUrl: String, elementToDecrypt: ElementToDecrypt): DownloadBody {
|
||||||
|
// TODO, upstream refactoring changed the object model here...
|
||||||
|
// it's bad we have to recreate and use hardcoded values
|
||||||
|
val encryptedInfo = EncryptedFileInfo(
|
||||||
|
url = mxcUrl,
|
||||||
|
iv = elementToDecrypt.iv,
|
||||||
|
hashes = mapOf("sha256" to elementToDecrypt.sha256),
|
||||||
|
key = EncryptedFileKey(
|
||||||
|
k = elementToDecrypt.k,
|
||||||
|
alg = "A256CTR",
|
||||||
|
keyOps = listOf("encrypt", "decrypt"),
|
||||||
|
kty = "oct",
|
||||||
|
ext = true
|
||||||
|
),
|
||||||
|
v = "v2"
|
||||||
|
)
|
||||||
|
return if (publicServerKey != null) {
|
||||||
|
// We should encrypt
|
||||||
|
withOlmEncryption { olm ->
|
||||||
|
olm.setRecipientKey(publicServerKey)
|
||||||
|
|
||||||
|
val olmResult = olm.encrypt(DownloadBody(encryptedInfo).toCanonicalJson())
|
||||||
|
DownloadBody(
|
||||||
|
encryptedBody = EncryptedBody(
|
||||||
|
cipherText = olmResult.mCipherText,
|
||||||
|
ephemeral = olmResult.mEphemeralKey,
|
||||||
|
mac = olmResult.mMac
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DownloadBody(encryptedInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.session.contentscanner.data
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ScanState
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
|
||||||
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
|
||||||
|
internal interface ContentScannerStore {
|
||||||
|
|
||||||
|
fun getScannerUrl(): String?
|
||||||
|
|
||||||
|
fun setScannerUrl(url: String?)
|
||||||
|
|
||||||
|
fun enableScanner(enabled: Boolean)
|
||||||
|
|
||||||
|
fun isScanEnabled(): Boolean
|
||||||
|
|
||||||
|
fun getScanResult(mxcUrl: String): ScanStatusInfo?
|
||||||
|
fun getLiveScanResult(mxcUrl: String): LiveData<Optional<ScanStatusInfo>>
|
||||||
|
fun isScanResultKnownOrInProgress(mxcUrl: String, scannerUrl: String?): Boolean
|
||||||
|
|
||||||
|
fun updateStateForContent(mxcUrl: String, state: ScanState, scannerUrl: String?)
|
||||||
|
fun updateScanResultForContent(mxcUrl: String, scannerUrl: String?, state: ScanState, humanReadable: String)
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.session.contentscanner.db
|
||||||
|
|
||||||
|
import io.realm.RealmObject
|
||||||
|
import io.realm.annotations.Index
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ScanState
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
|
||||||
|
|
||||||
|
internal open class ContentScanResultEntity(
|
||||||
|
@Index
|
||||||
|
var mediaUrl: String? = null,
|
||||||
|
var scanStatusString: String? = null,
|
||||||
|
var humanReadableMessage: String? = null,
|
||||||
|
var scanDateTimestamp: Long? = null,
|
||||||
|
var scannerUrl: String? = null
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
var scanResult: ScanState
|
||||||
|
get() {
|
||||||
|
return scanStatusString
|
||||||
|
?.let {
|
||||||
|
tryOrNull { ScanState.valueOf(it) }
|
||||||
|
}
|
||||||
|
?: ScanState.UNKNOWN
|
||||||
|
}
|
||||||
|
set(result) {
|
||||||
|
scanStatusString = result.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toModel(): ScanStatusInfo {
|
||||||
|
return ScanStatusInfo(
|
||||||
|
state = this.scanResult,
|
||||||
|
humanReadableMessage = humanReadableMessage,
|
||||||
|
scanDateTimestamp = scanDateTimestamp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.session.contentscanner.db
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
|
internal fun ContentScanResultEntity.Companion.get(realm: Realm, attachmentUrl: String, contentScannerUrl: String?): ContentScanResultEntity? {
|
||||||
|
return realm.where<ContentScanResultEntity>()
|
||||||
|
.equalTo(ContentScanResultEntityFields.MEDIA_URL, attachmentUrl)
|
||||||
|
.apply {
|
||||||
|
contentScannerUrl?.let {
|
||||||
|
equalTo(ContentScanResultEntityFields.SCANNER_URL, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.findFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ContentScanResultEntity.Companion.getOrCreate(realm: Realm, attachmentUrl: String, contentScannerUrl: String?): ContentScanResultEntity {
|
||||||
|
return ContentScanResultEntity.get(realm, attachmentUrl, contentScannerUrl)
|
||||||
|
?: realm.createObject<ContentScanResultEntity>().also {
|
||||||
|
it.mediaUrl = attachmentUrl
|
||||||
|
it.scanDateTimestamp = System.currentTimeMillis()
|
||||||
|
it.scannerUrl = contentScannerUrl
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.session.contentscanner.db
|
||||||
|
|
||||||
|
import io.realm.RealmObject
|
||||||
|
|
||||||
|
internal open class ContentScannerInfoEntity(
|
||||||
|
var serverUrl: String? = null,
|
||||||
|
var enabled: Boolean? = null
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
companion object
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.session.contentscanner.db
|
||||||
|
|
||||||
|
import io.realm.annotations.RealmModule
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Realm module for content scanner classes
|
||||||
|
*/
|
||||||
|
@RealmModule(library = true,
|
||||||
|
classes = [
|
||||||
|
ContentScannerInfoEntity::class,
|
||||||
|
ContentScanResultEntity::class
|
||||||
|
])
|
||||||
|
internal class ContentScannerRealmModule
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.session.contentscanner.db
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.Transformations
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ScanState
|
||||||
|
import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
|
||||||
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
import org.matrix.android.sdk.api.util.toOptional
|
||||||
|
import org.matrix.android.sdk.internal.di.ContentScannerDatabase
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
|
import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
|
||||||
|
import org.matrix.android.sdk.internal.util.isValidUrl
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@SessionScope
|
||||||
|
internal class RealmContentScannerStore @Inject constructor(
|
||||||
|
@ContentScannerDatabase
|
||||||
|
private val realmConfiguration: RealmConfiguration
|
||||||
|
) : ContentScannerStore {
|
||||||
|
|
||||||
|
private val monarchy = Monarchy.Builder()
|
||||||
|
.setRealmConfiguration(realmConfiguration)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
override fun getScannerUrl(): String? {
|
||||||
|
return monarchy.fetchAllMappedSync(
|
||||||
|
{ realm ->
|
||||||
|
realm.where<ContentScannerInfoEntity>()
|
||||||
|
}, {
|
||||||
|
it.serverUrl
|
||||||
|
}
|
||||||
|
).firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setScannerUrl(url: String?) {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val info = realm.where<ContentScannerInfoEntity>().findFirst()
|
||||||
|
?: realm.createObject()
|
||||||
|
info.serverUrl = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enableScanner(enabled: Boolean) {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val info = realm.where<ContentScannerInfoEntity>().findFirst()
|
||||||
|
?: realm.createObject()
|
||||||
|
info.enabled = enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isScanEnabled(): Boolean {
|
||||||
|
return monarchy.fetchAllMappedSync(
|
||||||
|
{ realm ->
|
||||||
|
realm.where<ContentScannerInfoEntity>()
|
||||||
|
}, {
|
||||||
|
it.enabled.orFalse() && it.serverUrl?.isValidUrl().orFalse()
|
||||||
|
}
|
||||||
|
).firstOrNull().orFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateStateForContent(mxcUrl: String, state: ScanState, scannerUrl: String?) {
|
||||||
|
monarchy.runTransactionSync {
|
||||||
|
ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl).scanResult = state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateScanResultForContent(mxcUrl: String, scannerUrl: String?, state: ScanState, humanReadable: String) {
|
||||||
|
monarchy.runTransactionSync {
|
||||||
|
ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl).apply {
|
||||||
|
scanResult = state
|
||||||
|
scanDateTimestamp = System.currentTimeMillis()
|
||||||
|
humanReadableMessage = humanReadable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isScanResultKnownOrInProgress(mxcUrl: String, scannerUrl: String?): Boolean {
|
||||||
|
var isKnown = false
|
||||||
|
monarchy.runTransactionSync {
|
||||||
|
val info = ContentScanResultEntity.get(it, mxcUrl, scannerUrl)?.scanResult
|
||||||
|
isKnown = when (info) {
|
||||||
|
ScanState.IN_PROGRESS,
|
||||||
|
ScanState.TRUSTED,
|
||||||
|
ScanState.INFECTED -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isKnown
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getScanResult(mxcUrl: String): ScanStatusInfo? {
|
||||||
|
return monarchy.fetchAllMappedSync({ realm ->
|
||||||
|
realm.where<ContentScanResultEntity>()
|
||||||
|
.equalTo(ContentScanResultEntityFields.MEDIA_URL, mxcUrl)
|
||||||
|
.apply {
|
||||||
|
getScannerUrl()?.let {
|
||||||
|
equalTo(ContentScanResultEntityFields.SCANNER_URL, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
it.toModel()
|
||||||
|
})
|
||||||
|
.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLiveScanResult(mxcUrl: String): LiveData<Optional<ScanStatusInfo>> {
|
||||||
|
val liveData = monarchy.findAllMappedWithChanges(
|
||||||
|
{ realm: Realm ->
|
||||||
|
realm.where<ContentScanResultEntity>()
|
||||||
|
.equalTo(ContentScanResultEntityFields.MEDIA_URL, mxcUrl)
|
||||||
|
.equalTo(ContentScanResultEntityFields.SCANNER_URL, getScannerUrl())
|
||||||
|
},
|
||||||
|
{ entity ->
|
||||||
|
entity.toModel()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return Transformations.map(liveData) {
|
||||||
|
it.firstOrNull().toOptional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue