mirror of
https://github.com/ArcaneChat/android.git
synced 2026-07-03 14:05:24 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d2b379e5a | |||
| d2c3ed8be3 |
@@ -0,0 +1,20 @@
|
|||||||
|
name: add artifact links to pull request
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["Upload Preview APK"]
|
||||||
|
types: [completed]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
artifacts-url-comments:
|
||||||
|
name: add artifact links to pull request
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||||
|
steps:
|
||||||
|
- name: add artifact links to pull request
|
||||||
|
uses: tonyhallett/artifacts-url-comments@v1.1.0
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
prefix: "**To test the changes in this pull request, install this apk:**"
|
||||||
|
format: "[📦 {name}]({url})"
|
||||||
|
addTo: pull
|
||||||
@@ -10,24 +10,21 @@ on:
|
|||||||
- 'jni/dc_wrapper.c'
|
- 'jni/dc_wrapper.c'
|
||||||
- 'scripts/ndk-make.sh'
|
- 'scripts/ndk-make.sh'
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
|
- uses: android-actions/setup-android@v3
|
||||||
- uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0
|
- uses: nttld/setup-ndk@v1
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
with:
|
with:
|
||||||
ndk-version: "r29"
|
ndk-version: r27
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: jni/deltachat-core-rust
|
workspaces: jni/deltachat-core-rust
|
||||||
|
|
||||||
@@ -37,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache compiled core
|
- name: Cache compiled core
|
||||||
id: cache-core
|
id: cache-core
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
jni/arm64-v8a
|
jni/arm64-v8a
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ on:
|
|||||||
branches: [main]
|
branches: [main]
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||||
@@ -18,16 +16,12 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@v5
|
||||||
with:
|
- uses: actions/setup-java@v5
|
||||||
persist-credentials: false
|
|
||||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
- name: Restore Gradle cache
|
- uses: actions/cache@v4
|
||||||
id: gradle-cache
|
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@@ -36,14 +30,6 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-gradle-
|
${{ runner.os }}-gradle-
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/actions/wrapper-validation@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
uses: gradle/actions/wrapper-validation@v4
|
||||||
- name: Check formatting
|
- name: Check formatting
|
||||||
run: ./gradlew spotlessCheck
|
run: ./gradlew spotlessCheck
|
||||||
- name: Save Gradle cache
|
|
||||||
if: github.event_name == 'push' && steps.gradle-cache.outputs.cache-hit != 'true'
|
|
||||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.gradle/caches
|
|
||||||
~/.gradle/wrapper
|
|
||||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
|
||||||
|
|||||||
@@ -6,30 +6,24 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Upload Preview APK
|
name: Upload Preview APK
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Validate Fastlane Metadata
|
- name: Validate Fastlane Metadata
|
||||||
uses: ashutoshgngwr/validate-fastlane-supply-metadata@c8857fdbbd3e00f9a5cbe8604bcecfa95ce8fef8 # v2.1.0
|
uses: ashutoshgngwr/validate-fastlane-supply-metadata@v2
|
||||||
|
|
||||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
- uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
|
|
||||||
- uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@@ -39,15 +33,15 @@ jobs:
|
|||||||
${{ runner.os }}-gradle-
|
${{ runner.os }}-gradle-
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/actions/wrapper-validation@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
uses: gradle/actions/wrapper-validation@v4
|
||||||
|
|
||||||
- uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
|
- uses: android-actions/setup-android@v3
|
||||||
- uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0
|
- uses: nttld/setup-ndk@v1
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
with:
|
with:
|
||||||
ndk-version: "r29"
|
ndk-version: r27
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: jni/deltachat-core-rust
|
workspaces: jni/deltachat-core-rust
|
||||||
|
|
||||||
@@ -57,7 +51,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Restore compiled core
|
- name: Restore compiled core
|
||||||
id: core-cache
|
id: core-cache
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
jni/arm64-v8a
|
jni/arm64-v8a
|
||||||
@@ -76,24 +70,7 @@ jobs:
|
|||||||
run: ./gradlew --no-daemon -PABI_FILTER=arm64-v8a assembleFossDebug
|
run: ./gradlew --no-daemon -PABI_FILTER=arm64-v8a assembleFossDebug
|
||||||
|
|
||||||
- name: Upload APK
|
- name: Upload APK
|
||||||
id: upload
|
uses: actions/upload-artifact@v4
|
||||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
||||||
with:
|
with:
|
||||||
name: app-preview.apk
|
name: app-preview.apk
|
||||||
path: 'build/outputs/apk/foss/debug/*.apk'
|
path: 'build/outputs/apk/foss/debug/*.apk'
|
||||||
|
|
||||||
- name: Add artifact links to PR
|
|
||||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
||||||
env:
|
|
||||||
ARTIFACT_URL: ${{ steps.upload.outputs.artifact-url }}
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const url = process.env.ARTIFACT_URL;
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: context.issue.number,
|
|
||||||
body: `**To test the changes in this pull request, install this apk:**\n\n[📦 app-preview.apk](${url})`,
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -23,18 +23,18 @@ jobs:
|
|||||||
name: Upload Release APK
|
name: Upload Release APK
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
working-directory: jni/deltachat-core-rust
|
working-directory: jni/deltachat-core-rust
|
||||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
- uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
|
- uses: android-actions/setup-android@v3
|
||||||
- uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0
|
- uses: nttld/setup-ndk@v1
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
with:
|
with:
|
||||||
ndk-version: r27
|
ndk-version: r27
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
mv build/outputs/mapping/gplayRelease/mapping.txt build/outputs/mapping/fossRelease/mapping-gplay.txt
|
mv build/outputs/mapping/gplayRelease/mapping.txt build/outputs/mapping/fossRelease/mapping-gplay.txt
|
||||||
|
|
||||||
- name: Release on GitHub
|
- name: Release on GitHub
|
||||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
token: "${{ secrets.GITHUB_TOKEN }}"
|
token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
body: '[<img src="store/get-it-on-gplay.png" alt="Get it on Google Play" height="48">](https://play.google.com/store/apps/details?id=com.github.arcanechat) [<img src="store/get-it-on-fdroid.png" alt="Get it on F-Droid" height="48">](https://f-droid.org/packages/chat.delta.lite) [<img src="store/get-it-on-github.png" alt="Get it on GitHub" height="48">](https://github.com/ArcaneChat/android/releases/latest/download/ArcaneChat-gplay.apk)'
|
body: '[<img src="store/get-it-on-gplay.png" alt="Get it on Google Play" height="48">](https://play.google.com/store/apps/details?id=com.github.arcanechat) [<img src="store/get-it-on-fdroid.png" alt="Get it on F-Droid" height="48">](https://f-droid.org/packages/chat.delta.lite) [<img src="store/get-it-on-github.png" alt="Get it on GitHub" height="48">](https://github.com/ArcaneChat/android/releases/latest/download/ArcaneChat-gplay.apk)'
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
name: GitHub Actions Security Analysis with zizmor
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ["main"]
|
|
||||||
pull_request:
|
|
||||||
branches: ["**"]
|
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
zizmor:
|
|
||||||
name: Run zizmor
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
|
|
||||||
contents: read
|
|
||||||
actions: read
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Run zizmor
|
|
||||||
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
rules:
|
|
||||||
unpinned-uses:
|
|
||||||
config:
|
|
||||||
policies:
|
|
||||||
actions/*: ref-pin
|
|
||||||
dependabot/*: ref-pin
|
|
||||||
+6
-7
@@ -47,7 +47,7 @@ Nix development environment contains Rust with cross-compilation toolchains and
|
|||||||
|
|
||||||
To [build an APK](https://developer.android.com/studio/build/building-cmdline) run the following 2 steps.
|
To [build an APK](https://developer.android.com/studio/build/building-cmdline) run the following 2 steps.
|
||||||
Note that the first step may take some time to build for all architectures. You can optionally read
|
Note that the first step may take some time to build for all architectures. You can optionally read
|
||||||
[the first comment block in the `ndk-make.sh` script](./scripts/ndk-make.sh)
|
[the first comment block in the `ndk-make.sh` script](https://github.com/deltachat/deltachat-android/blob/master/scripts/ndk-make.sh)
|
||||||
for pointers on how to build for a specific architecture.
|
for pointers on how to build for a specific architecture.
|
||||||
```
|
```
|
||||||
$ scripts/ndk-make.sh
|
$ scripts/ndk-make.sh
|
||||||
@@ -56,7 +56,7 @@ $ ./gradlew assembleDebug
|
|||||||
|
|
||||||
Resulting APK files can be found in
|
Resulting APK files can be found in
|
||||||
`build/outputs/apk/gplay/debug/` and
|
`build/outputs/apk/gplay/debug/` and
|
||||||
`build/outputs/apk/foss/debug/`.
|
`build/outputs/apk/fat/debug/`.
|
||||||
|
|
||||||
## Build Using Dockerfile
|
## Build Using Dockerfile
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ deltachat@6012dcb974fe:/home/app$ ./gradlew assembleDebug
|
|||||||
|
|
||||||
In /etc/containers/storage.conf, replace the line: `driver = ""` with: `driver = "overlay"`.
|
In /etc/containers/storage.conf, replace the line: `driver = ""` with: `driver = "overlay"`.
|
||||||
You can also set the `driver` option to something else, you just need to set it to _something_.
|
You can also set the `driver` option to something else, you just need to set it to _something_.
|
||||||
[Read about possible options here](https://github.com/containers/storage/blob/main/docs/containers-storage.conf.5.md#storage-table).
|
[Read about possible options here](https://github.com/containers/storage/blob/master/docs/containers-storage.conf.5.md#storage-table).
|
||||||
|
|
||||||
## <a name="setup-podman"></a>Setup Podman
|
## <a name="setup-podman"></a>Setup Podman
|
||||||
|
|
||||||
@@ -135,8 +135,8 @@ See https://wiki.archlinux.org/index.php/Podman#Rootless_Podman for more informa
|
|||||||
|
|
||||||
To setup build environment manually:
|
To setup build environment manually:
|
||||||
- _Either_, in Android Studio, go to "Tools / SDK Manager / SDK Tools", enable "Show Package Details",
|
- _Either_, in Android Studio, go to "Tools / SDK Manager / SDK Tools", enable "Show Package Details",
|
||||||
select "CMake" and the desired NDK (install the same NDK version as the [Dockerfile](./Dockerfile)), hit "Apply".
|
select "CMake" and the desired NDK (install the same NDK version as the [Dockerfile](https://github.com/deltachat/deltachat-android/blob/master/Dockerfile)), hit "Apply".
|
||||||
- _Or_ read [Dockerfile](./Dockerfile) and mimic what it does.
|
- _Or_ read [Dockerfile](https://github.com/deltachat/deltachat-android/blob/master/Dockerfile) and mimic what it does.
|
||||||
|
|
||||||
Then, in both cases, install Rust using [rustup](https://rustup.rs/)
|
Then, in both cases, install Rust using [rustup](https://rustup.rs/)
|
||||||
and Rust toolchains for cross-compilation by executing `scripts/install-toolchains.sh`.
|
and Rust toolchains for cross-compilation by executing `scripts/install-toolchains.sh`.
|
||||||
@@ -244,8 +244,7 @@ $ANDROID_NDK_ROOT/ndk-stack --sym obj/local/armeabi-v7a --dump crash.txt > decod
|
|||||||
|
|
||||||
Replace `armeabi-v7a` by the correct architecture the logs come from (can be guessed by trial and error)
|
Replace `armeabi-v7a` by the correct architecture the logs come from (can be guessed by trial and error)
|
||||||
|
|
||||||
|
### Deobfuscating Java/Kotlin Stack Traces
|
||||||
### Deobfuscating Java Stack Traces
|
|
||||||
|
|
||||||
Because the app uses code minification (ProGuard/R8), Java stack traces in crash reports are obfuscated.
|
Because the app uses code minification (ProGuard/R8), Java stack traces in crash reports are obfuscated.
|
||||||
To decode them, use `retrace` with the `mapping.txt` file that is included in the symbols zip:
|
To decode them, use `retrace` with the `mapping.txt` file that is included in the symbols zip:
|
||||||
|
|||||||
+1
-58
@@ -1,69 +1,12 @@
|
|||||||
# Delta Chat Android Changelog
|
# Delta Chat Android Changelog
|
||||||
|
|
||||||
## v2.53.0
|
## Unreleased
|
||||||
2026-06
|
|
||||||
|
|
||||||
* Use message style notifications for longer message previews
|
|
||||||
* Remove notification after audio playback ends
|
|
||||||
* Fix: do not allow blocked contacts to use our invite links
|
|
||||||
* Fix sending mini-app that was used/prepared before sending
|
|
||||||
* Some more small fixes and updated translations
|
|
||||||
* Update to core 2.53.0
|
|
||||||
|
|
||||||
## v2.52.0
|
|
||||||
2026-06
|
|
||||||
|
|
||||||
* Fix: avoid crashes in Media preview sometimes
|
|
||||||
* Fix: Incorrect total time when attaching audio files as draft
|
|
||||||
* Fix: Audio files in draft showing total time from wrong file
|
|
||||||
* Fix: Update the channel title after joining if the QR code had an outdated title
|
|
||||||
* Voice recording will be automatically saved as draft when interrupted
|
|
||||||
* Update to core 2.52.0
|
|
||||||
|
|
||||||
## v2.51.0
|
|
||||||
2026-06
|
|
||||||
|
|
||||||
* Better incoming call system integration
|
* Better incoming call system integration
|
||||||
* Calls are not experimental anymore and don't need to be manually enabled
|
* Calls are not experimental anymore and don't need to be manually enabled
|
||||||
* Calls can be answered by tapping messages
|
|
||||||
* Notify the user when they try to make a call while the device is offline
|
|
||||||
* Channels are no longer experimental and are available by default
|
|
||||||
* Display a permanent notification when doing location streaming and get rid of dangerous "Access Location in Background" permission
|
* Display a permanent notification when doing location streaming and get rid of dangerous "Access Location in Background" permission
|
||||||
* Autoplay all voice messages in a chat
|
|
||||||
* Allow to share location for 24 hours
|
|
||||||
* Allow mini-apps to play audio without user interaction
|
* Allow mini-apps to play audio without user interaction
|
||||||
* Allow to paste and open invitation links from search
|
|
||||||
* Mark chats as unread (long tap a chat and select the corresponding option from the three-dot-menu)
|
* Mark chats as unread (long tap a chat and select the corresponding option from the three-dot-menu)
|
||||||
* Add "Mark all as read" option to profile menu in the profile switcher
|
|
||||||
* Fix process of upgrading from a very old version of the app
|
|
||||||
* Show more recent added stickers at the top of the sticker picker
|
|
||||||
* Allow to open links in messages via actions in TalkBack menu
|
|
||||||
* Allow to open map if user clicks "Location streaming enabled" system message
|
|
||||||
* Allow to disable incoming calls notifications
|
|
||||||
* Add an option to process unencrypted messages; by default, only encrypted messages can be sent or received
|
|
||||||
* Fix: do not accidentally set draft in chats that don't allow sending messages
|
|
||||||
* Fix swipe navigation between tabs in RTL languages
|
|
||||||
* Remove "Move to DeltaChat folder", in case you are using the option, a device message shows how to proceed
|
|
||||||
* Remove "Only fetch from DeltaChat folder" option, the functionality is preserved for existing profiles
|
|
||||||
* Remove "Delete Messages from Server" option, this is now up to the server:
|
|
||||||
Chatmail handles that automatically, classic email servers used as relay often have lots of storage or options themselves
|
|
||||||
* Remove "Show Email" options, all messages are shown by default, shared usage of email account is not supported
|
|
||||||
* Allow otherwise invalid TLS connections if the key is unchanged
|
|
||||||
* Adapt quota warning to automatic cleanup.
|
|
||||||
* Don't show non-delivery-notfications in broadcast channels
|
|
||||||
* Resend the last 10 messages to new broadcast channel member
|
|
||||||
* Enable PQC (Post-Quantum Cryptography) support. We do not generate PQC keys yet, this step is needed for forward compatibility
|
|
||||||
* Improve avatar quality
|
|
||||||
* Add new webxdc.isAppSender and webxdc.isBroadcast APIs for mini-apps
|
|
||||||
* Fix: avoid invalid empty "~" notifications when some peer is streaming location
|
|
||||||
* Fix: Improve detection of stickers
|
|
||||||
* Fix text direction issues for RTL languages in "Show full message" view
|
|
||||||
* Fix: Reconnect when removing a relay
|
|
||||||
* Fix: Location streaming now works correctly with multiple accounts
|
|
||||||
* Fix debouncing in chatlist and search
|
|
||||||
* Fix sharing contact across profiles
|
|
||||||
* Fix: Reset scroll location after switching account
|
|
||||||
* Update to core 2.51.0
|
|
||||||
|
|
||||||
## v2.49.0
|
## v2.49.0
|
||||||
2026-04
|
2026-04
|
||||||
|
|||||||
+2
-2
@@ -34,8 +34,8 @@ android {
|
|||||||
useLibrary 'org.apache.http.legacy'
|
useLibrary 'org.apache.http.legacy'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
versionCode 30000746
|
versionCode 30000742
|
||||||
versionName "2.53.0"
|
versionName "2.49.0"
|
||||||
|
|
||||||
applicationId "chat.delta.lite"
|
applicationId "chat.delta.lite"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|||||||
@@ -35,7 +35,6 @@
|
|||||||
buildInputs = [
|
buildInputs = [
|
||||||
android-sdk
|
android-sdk
|
||||||
pkgs.openjdk17
|
pkgs.openjdk17
|
||||||
pkgs.perl
|
|
||||||
(pkgs.buildPackages.rust-bin.stable."${rust-version}".minimal.override {
|
(pkgs.buildPackages.rust-bin.stable."${rust-version}".minimal.override {
|
||||||
targets = [
|
targets = [
|
||||||
"armv7-linux-androideabi"
|
"armv7-linux-androideabi"
|
||||||
|
|||||||
@@ -1643,6 +1643,12 @@ JNIEXPORT void Java_com_b44t_messenger_DcMsg_setHtml(JNIEnv *env, jobject obj, j
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
JNIEXPORT void Java_com_b44t_messenger_DcMsg_forceSticker(JNIEnv *env, jobject obj)
|
||||||
|
{
|
||||||
|
dc_msg_force_sticker(get_dc_msg(env, obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
JNIEXPORT jstring Java_com_b44t_messenger_DcMsg_getPOILocation(JNIEnv *env, jobject obj)
|
JNIEXPORT jstring Java_com_b44t_messenger_DcMsg_getPOILocation(JNIEnv *env, jobject obj)
|
||||||
{
|
{
|
||||||
char* temp = dc_msg_get_poi_location(get_dc_msg(env, obj));
|
char* temp = dc_msg_get_poi_location(get_dc_msg(env, obj));
|
||||||
|
|||||||
+1
-1
Submodule jni/deltachat-core-rust updated: 9435042594...570119830f
@@ -39,7 +39,7 @@ public class EnterChatsBenchmark {
|
|||||||
// PLEASE BACKUP YOUR ACCOUNT BEFORE RUNNING THIS!
|
// PLEASE BACKUP YOUR ACCOUNT BEFORE RUNNING THIS!
|
||||||
// ==============================================================================================
|
// ==============================================================================================
|
||||||
|
|
||||||
private static final String TAG = "EnterChatsBenchmark";
|
private static final String TAG = EnterChatsBenchmark.class.getSimpleName();
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public ActivityScenarioRule<ConversationListActivity> activityRule =
|
public ActivityScenarioRule<ConversationListActivity> activityRule =
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import android.util.Log;
|
|||||||
/** Non-GMS, always uses the platform LocationManager. */
|
/** Non-GMS, always uses the platform LocationManager. */
|
||||||
public final class LocationSourceFactory {
|
public final class LocationSourceFactory {
|
||||||
|
|
||||||
private static final String TAG = "LocationSourceFactory";
|
private static final String TAG = LocationSourceFactory.class.getSimpleName();
|
||||||
|
|
||||||
private LocationSourceFactory() {}
|
private LocationSourceFactory() {}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import com.google.android.gms.location.Priority;
|
|||||||
|
|
||||||
public class GmsLocationSource implements LocationSource {
|
public class GmsLocationSource implements LocationSource {
|
||||||
|
|
||||||
private static final String TAG = "GmsLocationSource";
|
private static final String TAG = GmsLocationSource.class.getSimpleName();
|
||||||
private static final long UPDATE_INTERVAL_MS = 3_000;
|
private static final long UPDATE_INTERVAL_MS = 3_000;
|
||||||
private static final long FASTEST_INTERVAL_MS = 1_000;
|
private static final long FASTEST_INTERVAL_MS = 1_000;
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import com.google.android.gms.common.GoogleApiAvailability;
|
|||||||
*/
|
*/
|
||||||
public final class LocationSourceFactory {
|
public final class LocationSourceFactory {
|
||||||
|
|
||||||
private static final String TAG = "LocationSourceFactory";
|
private static final String TAG = LocationSourceFactory.class.getSimpleName();
|
||||||
|
|
||||||
private LocationSourceFactory() {}
|
private LocationSourceFactory() {}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.service.FetchForegroundService;
|
|||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
public class FcmReceiveService extends FirebaseMessagingService {
|
public class FcmReceiveService extends FirebaseMessagingService {
|
||||||
private static final String TAG = "FcmReceiveService";
|
private static final String TAG = FcmReceiveService.class.getSimpleName();
|
||||||
private static final Object INIT_LOCK = new Object();
|
private static final Object INIT_LOCK = new Object();
|
||||||
private static boolean initialized;
|
private static boolean initialized;
|
||||||
private static volatile boolean triedRegistering;
|
private static volatile boolean triedRegistering;
|
||||||
|
|||||||
@@ -71,7 +71,6 @@
|
|||||||
android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
|
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
|
||||||
|
|
||||||
@@ -491,12 +490,7 @@
|
|||||||
android:name=".calls.CallService"
|
android:name=".calls.CallService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:foregroundServiceType="camera|microphone|phoneCall" />
|
android:foregroundServiceType="camera|microphone" />
|
||||||
|
|
||||||
<receiver
|
|
||||||
android:name=".calls.CallActionReceiver"
|
|
||||||
android:enabled="true"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".notifications.MarkReadReceiver"
|
android:name=".notifications.MarkReadReceiver"
|
||||||
|
|||||||
@@ -29,21 +29,6 @@
|
|||||||
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#channels">Channels</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#subscribe-to-a-channel">Subscribe to a channel</a></li>
|
|
||||||
<li><a href="#create-a-channel">Create a channel</a></li>
|
|
||||||
<li><a href="#how-many-subscribers-can-a-channel-have">How many subscribers can a channel have?</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#calls">Calls</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#place-a-call">Place a call</a></li>
|
|
||||||
<li><a href="#accept-or-reject-a-call">Accept or reject a call</a></li>
|
|
||||||
<li><a href="#during-a-call">During a call</a></li>
|
|
||||||
<li><a href="#missed-calls-and-notifications">Missed calls and notifications</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#webxdc">In-chat apps</a>
|
<li><a href="#webxdc">In-chat apps</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
||||||
@@ -643,197 +628,6 @@ but more than 150 is not recommended.</p>
|
|||||||
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
||||||
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
||||||
|
|
||||||
<h2 id="channels">
|
|
||||||
|
|
||||||
|
|
||||||
Channels <a href="#channels" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Channels are a one-to-many tool for broadcasting messages.</p>
|
|
||||||
|
|
||||||
<h3 id="subscribe-to-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Subscribe to a channel <a href="#subscribe-to-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Scan the <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>QR code</strong>
|
|
||||||
or tap the <strong>invite link</strong> you got from the channel owner.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>That’s all!
|
|
||||||
You will receive a few of the messages from the channel history
|
|
||||||
and, from that point on, all new messages from the channel.</p>
|
|
||||||
|
|
||||||
<p><strong>Don’t worry,</strong> if that does not happen immediately.
|
|
||||||
Once the channel owner comes online, your join request will be processed.</p>
|
|
||||||
|
|
||||||
<p>As all of Delta Chat, also Channels are private and decentralized,
|
|
||||||
there is no public discovery.</p>
|
|
||||||
|
|
||||||
<p>Other channel subscribers will not see that you subscribed and cannot message you.
|
|
||||||
The channel owner, however, can message you.
|
|
||||||
They will also see that you read a message unless you have read receipts disabled.</p>
|
|
||||||
|
|
||||||
<p>If you do not want to share your main profile,
|
|
||||||
you can also create a <a href="#multiple-accounts">dedicated profile</a> for joining a channel.</p>
|
|
||||||
|
|
||||||
<h3 id="create-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Create a channel <a href="#create-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>New Chat</strong> and choose <strong>New Channel</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Enter a <strong>name</strong>, optionally set an <strong>image</strong> and <strong>description</strong>, and hit the <strong>Create</strong> button.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can now send and manage messages as usual.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>From the channel’s profile, <strong>share the QR code or invite link with others</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Subscribers will receive your messages,
|
|
||||||
but they cannot send messages in your channel.
|
|
||||||
When subscribing, they will receive <strong>a few of the latest messages of the channel history</strong>.</p>
|
|
||||||
|
|
||||||
<p>You can see the <strong>view count</strong> beside each message.
|
|
||||||
Note that this only counts subscribers who have read receipts enabled,
|
|
||||||
so the real view count may be larger.</p>
|
|
||||||
|
|
||||||
<h3 id="how-many-subscribers-can-a-channel-have">
|
|
||||||
|
|
||||||
|
|
||||||
How many subscribers can a channel have? <a href="#how-many-subscribers-can-a-channel-have" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>Channels are designed for much larger audiences than <a href="#groups">groups</a>.</p>
|
|
||||||
|
|
||||||
<p>The practical limit depends on the used <a href="#relays">relay</a>,
|
|
||||||
so there is no single fixed number that applies everywhere.</p>
|
|
||||||
|
|
||||||
<p>For really large channels with several tens of thousands of subscribers,
|
|
||||||
we recommend using a <a href="#multiple-accounts">dedicated profile</a> for the channel
|
|
||||||
and checking whether the relay is suitable.</p>
|
|
||||||
|
|
||||||
<p>But don’t be too hesitant: Delta Chat is designed to be relay-agnostic,
|
|
||||||
so you can change your relay at any point easily -
|
|
||||||
your existing subscribers will not even notice.
|
|
||||||
You only have to update the invite link you share with new subscribers in that case.</p>
|
|
||||||
|
|
||||||
<h2 id="calls">
|
|
||||||
|
|
||||||
|
|
||||||
Calls <a href="#calls" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Delta Chat supports one-to-one <strong>audio calls</strong> and <strong>video calls</strong>.</p>
|
|
||||||
|
|
||||||
<p>Calls are supported on Desktop, Ubuntu Touch, iOS and Android 8 and newer.</p>
|
|
||||||
|
|
||||||
<h3 id="place-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Place a call <a href="#place-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>In a one-to-one chat, tap the 📞 <strong>call icon</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>This opens a small menu
|
|
||||||
where you can choose whether to place an <strong>Audio Call</strong> or a <strong>Video Call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="accept-or-reject-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Accept or reject a call <a href="#accept-or-reject-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>When someone calls you,
|
|
||||||
Delta Chat shows an <strong>incoming call screen</strong> or notification.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>Accept</strong> to answer
|
|
||||||
or <strong>Decline</strong> to reject the call.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="during-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
During a call <a href="#during-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>mute</strong> your microphone.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>enable or disable your camera</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>On mobile, you can <strong>switch between front and back cameras</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Depending on the device, you can also select the audio output or use picture-in-picture.
|
|
||||||
On desktop, the call is using a dedicated window
|
|
||||||
and you can continue using the main Delta Chat window as usual.</p>
|
|
||||||
|
|
||||||
<h3 id="missed-calls-and-notifications">
|
|
||||||
|
|
||||||
|
|
||||||
Missed calls and notifications <a href="#missed-calls-and-notifications" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>If you do not answer, do not hear the ringing, or do not have your device at hand,
|
|
||||||
the call appears as a <strong>missed call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Only your accepted contacts</strong> can make your device ring.
|
|
||||||
Contact requests will appear as usual and will not ring.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>At <strong>Settings → Notifications → Calls</strong>,
|
|
||||||
you can disable the special call ringing screen completely.
|
|
||||||
If you do so, you will not be disturbed by any ringing notification,
|
|
||||||
you can still pick up the call by tapping the incoming call message bubble in its chat.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="webxdc">
|
<h2 id="webxdc">
|
||||||
|
|
||||||
|
|
||||||
@@ -1631,20 +1425,23 @@ can not be identified easily.</p>
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
|
||||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
as well as sometimes your contact’s devices if you have a <a href="#experiments">call</a>
|
||||||
or use <a href="#webxdc">apps</a> together.</p>
|
or use <a href="#webxdc">apps</a> together.</p>
|
||||||
|
|
||||||
<p>IP Addresses are needed for connectivity and efficiency.
|
<p>IP Addresses are needed for connectivity and efficiency.
|
||||||
Delta Chat neither persists nor exposes them.
|
They are neither persisted nor exposed.
|
||||||
Note that IP Addresses
|
Note that the IP Address
|
||||||
are not like an address you give to a delivery service,
|
is not like a detailed address you give to a delivery service,
|
||||||
but typically less precise, often defining city or region only.</p>
|
but much more coarse, often defining region or country only.</p>
|
||||||
|
|
||||||
<p>If you see your IP Address as a risk,
|
<p>As this is just how the internet and other messengers work by default,
|
||||||
we recommend to use a VPN for the whole system.
|
we do not offer options here or ask upfront questions.</p>
|
||||||
Per-app options leave gaps across your system.
|
|
||||||
For example, tapping a link can expose IP Addresses to unknown parties, which is by far the larger risk.</p>
|
<p>If you see your IP Address as a security or privacy risk,
|
||||||
|
we recommend to use a VPN, in combination with system lockdown mode.
|
||||||
|
Hunting down options in all apps on your system will leave gaps.
|
||||||
|
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
|
||||||
|
|
||||||
<h3 id="sealedsender">
|
<h3 id="sealedsender">
|
||||||
|
|
||||||
|
|||||||
@@ -29,21 +29,6 @@
|
|||||||
<li><a href="#wie-viele-mitglieder-können-in-einer-einzelnen-gruppe-sein">Wie viele Mitglieder können in einer einzelnen Gruppe sein?</a></li>
|
<li><a href="#wie-viele-mitglieder-können-in-einer-einzelnen-gruppe-sein">Wie viele Mitglieder können in einer einzelnen Gruppe sein?</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#channels">Kanäle</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#einem-kanal-beitreten">Einem Kanal beitreten</a></li>
|
|
||||||
<li><a href="#einen-kanal-erstellen">Einen Kanal erstellen</a></li>
|
|
||||||
<li><a href="#wie-viele-empfänger-kann-ein-kanal-haben">Wie viele Empfänger kann ein Kanal haben?</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#calls">Anrufe</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#jemanden-anrufen">Jemanden anrufen</a></li>
|
|
||||||
<li><a href="#einen-anruf-annehmen-oder-ablehnen">Einen Anruf annehmen oder ablehnen</a></li>
|
|
||||||
<li><a href="#während-des-anrufs">Während des Anrufs</a></li>
|
|
||||||
<li><a href="#verpasste-anrufe-und-benachrichtigungen">Verpasste Anrufe und Benachrichtigungen</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#webxdc">In-Chat-Apps</a>
|
<li><a href="#webxdc">In-Chat-Apps</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#wo-bekomme-ich-in-chat-apps">Wo bekomme ich In-Chat-Apps?</a></li>
|
<li><a href="#wo-bekomme-ich-in-chat-apps">Wo bekomme ich In-Chat-Apps?</a></li>
|
||||||
@@ -599,197 +584,6 @@ aber mehr als 150 sind nicht empfohlen.</p>
|
|||||||
|
|
||||||
<p>Wenn Gruppen größer werden, können sie sozial instabil werden und benötigen möglicherweise eine Hierarchie - und Delta Chat ist ein privater Messenger für Chats mit <a href="#groups">gleichen Rechten</a>. Vgl. <a href="https://de.wikipedia.org/wiki/Dunbar-Zahl">Dunbar-Zahl</a>.</p>
|
<p>Wenn Gruppen größer werden, können sie sozial instabil werden und benötigen möglicherweise eine Hierarchie - und Delta Chat ist ein privater Messenger für Chats mit <a href="#groups">gleichen Rechten</a>. Vgl. <a href="https://de.wikipedia.org/wiki/Dunbar-Zahl">Dunbar-Zahl</a>.</p>
|
||||||
|
|
||||||
<h2 id="channels">
|
|
||||||
|
|
||||||
|
|
||||||
Kanäle <a href="#channels" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Kanäle dienen der Verbreitung von Nachrichten an viele Empfänger.</p>
|
|
||||||
|
|
||||||
<h3 id="einem-kanal-beitreten">
|
|
||||||
|
|
||||||
|
|
||||||
Einem Kanal beitreten <a href="#einem-kanal-beitreten" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Scan the <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>QR code</strong>
|
|
||||||
or tap the <strong>invite link</strong> you got from the channel owner.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>That’s all!
|
|
||||||
You will receive a few of the messages from the channel history
|
|
||||||
and, from that point on, all new messages from the channel.</p>
|
|
||||||
|
|
||||||
<p><strong>Don’t worry,</strong> if that does not happen immediately.
|
|
||||||
Once the channel owner comes online, your join request will be processed.</p>
|
|
||||||
|
|
||||||
<p>As all of Delta Chat, also Channels are private and decentralized,
|
|
||||||
there is no public discovery.</p>
|
|
||||||
|
|
||||||
<p>Other channel subscribers will not see that you subscribed and cannot message you.
|
|
||||||
The channel owner, however, can message you.
|
|
||||||
They will also see that you read a message unless you have read receipts disabled.</p>
|
|
||||||
|
|
||||||
<p>If you do not want to share your main profile,
|
|
||||||
you can also create a <a href="#multiple-accounts">dedicated profile</a> for joining a channel.</p>
|
|
||||||
|
|
||||||
<h3 id="einen-kanal-erstellen">
|
|
||||||
|
|
||||||
|
|
||||||
Einen Kanal erstellen <a href="#einen-kanal-erstellen" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>New Chat</strong> and choose <strong>New Channel</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Enter a <strong>name</strong>, optionally set an <strong>image</strong> and <strong>description</strong>, and hit the <strong>Create</strong> button.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can now send and manage messages as usual.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>From the channel’s profile, <strong>share the QR code or invite link with others</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Subscribers will receive your messages,
|
|
||||||
but they cannot send messages in your channel.
|
|
||||||
When subscribing, they will receive <strong>a few of the latest messages of the channel history</strong>.</p>
|
|
||||||
|
|
||||||
<p>You can see the <strong>view count</strong> beside each message.
|
|
||||||
Note that this only counts subscribers who have read receipts enabled,
|
|
||||||
so the real view count may be larger.</p>
|
|
||||||
|
|
||||||
<h3 id="wie-viele-empfänger-kann-ein-kanal-haben">
|
|
||||||
|
|
||||||
|
|
||||||
Wie viele Empfänger kann ein Kanal haben? <a href="#wie-viele-empfänger-kann-ein-kanal-haben" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>Channels are designed for much larger audiences than <a href="#groups">groups</a>.</p>
|
|
||||||
|
|
||||||
<p>The practical limit depends on the used <a href="#relays">relay</a>,
|
|
||||||
so there is no single fixed number that applies everywhere.</p>
|
|
||||||
|
|
||||||
<p>For really large channels with several tens of thousands of subscribers,
|
|
||||||
we recommend using a <a href="#multiple-accounts">dedicated profile</a> for the channel
|
|
||||||
and checking whether the relay is suitable.</p>
|
|
||||||
|
|
||||||
<p>But don’t be too hesitant: Delta Chat is designed to be relay-agnostic,
|
|
||||||
so you can change your relay at any point easily -
|
|
||||||
your existing subscribers will not even notice.
|
|
||||||
You only have to update the invite link you share with new subscribers in that case.</p>
|
|
||||||
|
|
||||||
<h2 id="calls">
|
|
||||||
|
|
||||||
|
|
||||||
Anrufe <a href="#calls" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Delta Chat supports one-to-one <strong>audio calls</strong> and <strong>video calls</strong>.</p>
|
|
||||||
|
|
||||||
<p>Calls are supported on Desktop, Ubuntu Touch, iOS and Android 8 and newer.</p>
|
|
||||||
|
|
||||||
<h3 id="jemanden-anrufen">
|
|
||||||
|
|
||||||
|
|
||||||
Jemanden anrufen <a href="#jemanden-anrufen" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>In a one-to-one chat, tap the 📞 <strong>call icon</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>This opens a small menu
|
|
||||||
where you can choose whether to place an <strong>Audio Call</strong> or a <strong>Video Call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="einen-anruf-annehmen-oder-ablehnen">
|
|
||||||
|
|
||||||
|
|
||||||
Einen Anruf annehmen oder ablehnen <a href="#einen-anruf-annehmen-oder-ablehnen" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>When someone calls you,
|
|
||||||
Delta Chat shows an <strong>incoming call screen</strong> or notification.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>Accept</strong> to answer
|
|
||||||
or <strong>Decline</strong> to reject the call.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="während-des-anrufs">
|
|
||||||
|
|
||||||
|
|
||||||
Während des Anrufs <a href="#während-des-anrufs" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>mute</strong> your microphone.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>enable or disable your camera</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>On mobile, you can <strong>switch between front and back cameras</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Depending on the device, you can also select the audio output or use picture-in-picture.
|
|
||||||
On desktop, the call is using a dedicated window
|
|
||||||
and you can continue using the main Delta Chat window as usual.</p>
|
|
||||||
|
|
||||||
<h3 id="verpasste-anrufe-und-benachrichtigungen">
|
|
||||||
|
|
||||||
|
|
||||||
Verpasste Anrufe und Benachrichtigungen <a href="#verpasste-anrufe-und-benachrichtigungen" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>If you do not answer, do not hear the ringing, or do not have your device at hand,
|
|
||||||
the call appears as a <strong>missed call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Only your accepted contacts</strong> can make your device ring.
|
|
||||||
Contact requests will appear as usual and will not ring.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>At <strong>Settings → Notifications → Calls</strong>,
|
|
||||||
you can disable the special call ringing screen completely.
|
|
||||||
If you do so, you will not be disturbed by any ringing notification,
|
|
||||||
you can still pick up the call by tapping the incoming call message bubble in its chat.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="webxdc">
|
<h2 id="webxdc">
|
||||||
|
|
||||||
|
|
||||||
@@ -1535,16 +1329,23 @@ im Falle einer Beschlagnahmung des Geräts nicht ohne Weiteres identifiziert wer
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Die verwendeten <a href="#relays">Relays</a> müssen deine IP-Adresse kennen,
|
<p>Das verwendete <a href="#relays">Relay</a> muss Ihre IP-Adresse kennen,
|
||||||
sowie manchmal auch die Geräte deiner Kontakte, wenn du einen <a href="#calls">Anruf</a> tätigst
|
sowie manchmal auch die Geräte Ihrer Kontakte, wenn Sie einen <a href="#experiments">Anruf</a> tätigen
|
||||||
oder ihr gemeinsam <a href="#webxdc">Apps</a> verwendet.</p>
|
oder gemeinsam <a href="#webxdc">Apps</a> verwenden.</p>
|
||||||
|
|
||||||
<p>IP-Adressen sind für Verbindungen und für Effizienz erforderlich.
|
<p>IP-Adressen sind für Verbindungen und für Effizienz erforderlich.
|
||||||
Sie werden von Delta Chat weder gespeichert noch offengelegt.
|
Sie werden weder gespeichert noch offengelegt.
|
||||||
IP-Adressen sind nicht mit einer Adresse, die du einem Lieferdienst gibst, vergleichbar - sondern viel gröber und oft nur die Stadt oder die Region beschreibend.</p>
|
Beachten Sie, dass die IP-Adresse
|
||||||
|
nicht mit einer Adresse, die Sie einem Lieferdienst geben, vergleichbar ist -
|
||||||
|
sondern viel gröber ist und oft nur die Region oder das Land angibt.</p>
|
||||||
|
|
||||||
<p>Wenn du deine IP-Adresse als Risiko betrachtest, empfehlen wir, ein VPN für das gesamte System zu verwenden.
|
<p>Da dies die Standardfunktion des Internets und anderer Messenger ist,
|
||||||
Einstellungen auf App-Ebene hinterlassen Lücken überall im System. Wenn man beispielsweise auf einen Link tippt, können IP-Adressen an Unbekannte weitergegeben werden, was bei weitem das größere Risiko darstellt</p>
|
bieten wir hier keine Optionen an und stellen auch keine Fragen im Voraus.</p>
|
||||||
|
|
||||||
|
<p>Wenn Sie Ihre IP-Adresse als Sicherheits- oder Datenschutzrisiko betrachten,
|
||||||
|
empfehlen wir Ihnen, ein VPN in Kombination mit dem System-Lockdown-Modus zu verwenden.
|
||||||
|
Alle einzelnen Apps auf Ihrem System nach IP-Optionen abzusuchen wird nicht zufriedenstellen sein;
|
||||||
|
beispielsweise legt das Antippen eines Links IP-Adressen gegenüber unbekannten Parteien offen und stellt hier das weitaus größere Risiko dar.</p>
|
||||||
|
|
||||||
<h3 id="sealedsender">
|
<h3 id="sealedsender">
|
||||||
|
|
||||||
|
|||||||
@@ -29,21 +29,6 @@
|
|||||||
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#channels">Channels</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#subscribe-to-a-channel">Subscribe to a channel</a></li>
|
|
||||||
<li><a href="#create-a-channel">Create a channel</a></li>
|
|
||||||
<li><a href="#how-many-subscribers-can-a-channel-have">How many subscribers can a channel have?</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#calls">Calls</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#place-a-call">Place a call</a></li>
|
|
||||||
<li><a href="#accept-or-reject-a-call">Accept or reject a call</a></li>
|
|
||||||
<li><a href="#during-a-call">During a call</a></li>
|
|
||||||
<li><a href="#missed-calls-and-notifications">Missed calls and notifications</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#webxdc">In-chat apps</a>
|
<li><a href="#webxdc">In-chat apps</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
||||||
@@ -642,197 +627,6 @@ but more than 150 is not recommended.</p>
|
|||||||
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
||||||
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
||||||
|
|
||||||
<h2 id="channels">
|
|
||||||
|
|
||||||
|
|
||||||
Channels <a href="#channels" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Channels are a one-to-many tool for broadcasting messages.</p>
|
|
||||||
|
|
||||||
<h3 id="subscribe-to-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Subscribe to a channel <a href="#subscribe-to-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Scan the <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>QR code</strong>
|
|
||||||
or tap the <strong>invite link</strong> you got from the channel owner.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>That’s all!
|
|
||||||
You will receive a few of the messages from the channel history
|
|
||||||
and, from that point on, all new messages from the channel.</p>
|
|
||||||
|
|
||||||
<p><strong>Don’t worry,</strong> if that does not happen immediately.
|
|
||||||
Once the channel owner comes online, your join request will be processed.</p>
|
|
||||||
|
|
||||||
<p>As all of Delta Chat, also Channels are private and decentralized,
|
|
||||||
there is no public discovery.</p>
|
|
||||||
|
|
||||||
<p>Other channel subscribers will not see that you subscribed and cannot message you.
|
|
||||||
The channel owner, however, can message you.
|
|
||||||
They will also see that you read a message unless you have read receipts disabled.</p>
|
|
||||||
|
|
||||||
<p>If you do not want to share your main profile,
|
|
||||||
you can also create a <a href="#multiple-accounts">dedicated profile</a> for joining a channel.</p>
|
|
||||||
|
|
||||||
<h3 id="create-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Create a channel <a href="#create-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>New Chat</strong> and choose <strong>New Channel</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Enter a <strong>name</strong>, optionally set an <strong>image</strong> and <strong>description</strong>, and hit the <strong>Create</strong> button.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can now send and manage messages as usual.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>From the channel’s profile, <strong>share the QR code or invite link with others</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Subscribers will receive your messages,
|
|
||||||
but they cannot send messages in your channel.
|
|
||||||
When subscribing, they will receive <strong>a few of the latest messages of the channel history</strong>.</p>
|
|
||||||
|
|
||||||
<p>You can see the <strong>view count</strong> beside each message.
|
|
||||||
Note that this only counts subscribers who have read receipts enabled,
|
|
||||||
so the real view count may be larger.</p>
|
|
||||||
|
|
||||||
<h3 id="how-many-subscribers-can-a-channel-have">
|
|
||||||
|
|
||||||
|
|
||||||
How many subscribers can a channel have? <a href="#how-many-subscribers-can-a-channel-have" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>Channels are designed for much larger audiences than <a href="#groups">groups</a>.</p>
|
|
||||||
|
|
||||||
<p>The practical limit depends on the used <a href="#relays">relay</a>,
|
|
||||||
so there is no single fixed number that applies everywhere.</p>
|
|
||||||
|
|
||||||
<p>For really large channels with several tens of thousands of subscribers,
|
|
||||||
we recommend using a <a href="#multiple-accounts">dedicated profile</a> for the channel
|
|
||||||
and checking whether the relay is suitable.</p>
|
|
||||||
|
|
||||||
<p>But don’t be too hesitant: Delta Chat is designed to be relay-agnostic,
|
|
||||||
so you can change your relay at any point easily -
|
|
||||||
your existing subscribers will not even notice.
|
|
||||||
You only have to update the invite link you share with new subscribers in that case.</p>
|
|
||||||
|
|
||||||
<h2 id="calls">
|
|
||||||
|
|
||||||
|
|
||||||
Calls <a href="#calls" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Delta Chat supports one-to-one <strong>audio calls</strong> and <strong>video calls</strong>.</p>
|
|
||||||
|
|
||||||
<p>Calls are supported on Desktop, Ubuntu Touch, iOS and Android 8 and newer.</p>
|
|
||||||
|
|
||||||
<h3 id="place-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Place a call <a href="#place-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>In a one-to-one chat, tap the 📞 <strong>call icon</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>This opens a small menu
|
|
||||||
where you can choose whether to place an <strong>Audio Call</strong> or a <strong>Video Call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="accept-or-reject-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Accept or reject a call <a href="#accept-or-reject-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>When someone calls you,
|
|
||||||
Delta Chat shows an <strong>incoming call screen</strong> or notification.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>Accept</strong> to answer
|
|
||||||
or <strong>Decline</strong> to reject the call.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="during-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
During a call <a href="#during-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>mute</strong> your microphone.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>enable or disable your camera</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>On mobile, you can <strong>switch between front and back cameras</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Depending on the device, you can also select the audio output or use picture-in-picture.
|
|
||||||
On desktop, the call is using a dedicated window
|
|
||||||
and you can continue using the main Delta Chat window as usual.</p>
|
|
||||||
|
|
||||||
<h3 id="missed-calls-and-notifications">
|
|
||||||
|
|
||||||
|
|
||||||
Missed calls and notifications <a href="#missed-calls-and-notifications" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>If you do not answer, do not hear the ringing, or do not have your device at hand,
|
|
||||||
the call appears as a <strong>missed call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Only your accepted contacts</strong> can make your device ring.
|
|
||||||
Contact requests will appear as usual and will not ring.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>At <strong>Settings → Notifications → Calls</strong>,
|
|
||||||
you can disable the special call ringing screen completely.
|
|
||||||
If you do so, you will not be disturbed by any ringing notification,
|
|
||||||
you can still pick up the call by tapping the incoming call message bubble in its chat.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="webxdc">
|
<h2 id="webxdc">
|
||||||
|
|
||||||
|
|
||||||
@@ -1631,20 +1425,23 @@ can not be identified easily.</p>
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
|
||||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
as well as sometimes your contact’s devices if you have a <a href="#experiments">call</a>
|
||||||
or use <a href="#webxdc">apps</a> together.</p>
|
or use <a href="#webxdc">apps</a> together.</p>
|
||||||
|
|
||||||
<p>IP Addresses are needed for connectivity and efficiency.
|
<p>IP Addresses are needed for connectivity and efficiency.
|
||||||
Delta Chat neither persists nor exposes them.
|
They are neither persisted nor exposed.
|
||||||
Note that IP Addresses
|
Note that the IP Address
|
||||||
are not like an address you give to a delivery service,
|
is not like a detailed address you give to a delivery service,
|
||||||
but typically less precise, often defining city or region only.</p>
|
but much more coarse, often defining region or country only.</p>
|
||||||
|
|
||||||
<p>If you see your IP Address as a risk,
|
<p>As this is just how the internet and other messengers work by default,
|
||||||
we recommend to use a VPN for the whole system.
|
we do not offer options here or ask upfront questions.</p>
|
||||||
Per-app options leave gaps across your system.
|
|
||||||
For example, tapping a link can expose IP Addresses to unknown parties, which is by far the larger risk.</p>
|
<p>If you see your IP Address as a security or privacy risk,
|
||||||
|
we recommend to use a VPN, in combination with system lockdown mode.
|
||||||
|
Hunting down options in all apps on your system will leave gaps.
|
||||||
|
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
|
||||||
|
|
||||||
<h3 id="sealedsender">
|
<h3 id="sealedsender">
|
||||||
|
|
||||||
|
|||||||
+221
-421
File diff suppressed because it is too large
Load Diff
@@ -29,21 +29,6 @@
|
|||||||
<li><a href="#zenbat-kide-izan-ditzake-talde-batek">Zenbat kide izan ditzake talde batek?</a></li>
|
<li><a href="#zenbat-kide-izan-ditzake-talde-batek">Zenbat kide izan ditzake talde batek?</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#channels">Channels</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#subscribe-to-a-channel">Subscribe to a channel</a></li>
|
|
||||||
<li><a href="#create-a-channel">Create a channel</a></li>
|
|
||||||
<li><a href="#how-many-subscribers-can-a-channel-have">How many subscribers can a channel have?</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#calls">Calls</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#place-a-call">Place a call</a></li>
|
|
||||||
<li><a href="#accept-or-reject-a-call">Accept or reject a call</a></li>
|
|
||||||
<li><a href="#during-a-call">During a call</a></li>
|
|
||||||
<li><a href="#missed-calls-and-notifications">Missed calls and notifications</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#webxdc">Txat barruko aplikazioak</a>
|
<li><a href="#webxdc">Txat barruko aplikazioak</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#non-lortu-ditzaket-txat-barruko-aplikazioak">Non lortu ditzaket txat barruko aplikazioak?</a></li>
|
<li><a href="#non-lortu-ditzaket-txat-barruko-aplikazioak">Non lortu ditzaket txat barruko aplikazioak?</a></li>
|
||||||
@@ -639,197 +624,6 @@ baina ez da komeni 150 baino gehiago izatea.</p>
|
|||||||
Delta Chat <a href="#groups">eskubide berberekin</a> txateatzeko aukera ematen duen mezularitza pribatuko zerbitzu bat da.
|
Delta Chat <a href="#groups">eskubide berberekin</a> txateatzeko aukera ematen duen mezularitza pribatuko zerbitzu bat da.
|
||||||
Informazio gehiago nahi baduzu, kontsultatu <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbarren zenbakia</a>.</p>
|
Informazio gehiago nahi baduzu, kontsultatu <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbarren zenbakia</a>.</p>
|
||||||
|
|
||||||
<h2 id="channels">
|
|
||||||
|
|
||||||
|
|
||||||
Channels <a href="#channels" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Channels are a one-to-many tool for broadcasting messages.</p>
|
|
||||||
|
|
||||||
<h3 id="subscribe-to-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Subscribe to a channel <a href="#subscribe-to-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Scan the <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>QR code</strong>
|
|
||||||
or tap the <strong>invite link</strong> you got from the channel owner.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>That’s all!
|
|
||||||
You will receive a few of the messages from the channel history
|
|
||||||
and, from that point on, all new messages from the channel.</p>
|
|
||||||
|
|
||||||
<p><strong>Don’t worry,</strong> if that does not happen immediately.
|
|
||||||
Once the channel owner comes online, your join request will be processed.</p>
|
|
||||||
|
|
||||||
<p>As all of Delta Chat, also Channels are private and decentralized,
|
|
||||||
there is no public discovery.</p>
|
|
||||||
|
|
||||||
<p>Other channel subscribers will not see that you subscribed and cannot message you.
|
|
||||||
The channel owner, however, can message you.
|
|
||||||
They will also see that you read a message unless you have read receipts disabled.</p>
|
|
||||||
|
|
||||||
<p>If you do not want to share your main profile,
|
|
||||||
you can also create a <a href="#multiple-accounts">dedicated profile</a> for joining a channel.</p>
|
|
||||||
|
|
||||||
<h3 id="create-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Create a channel <a href="#create-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>New Chat</strong> and choose <strong>New Channel</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Enter a <strong>name</strong>, optionally set an <strong>image</strong> and <strong>description</strong>, and hit the <strong>Create</strong> button.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can now send and manage messages as usual.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>From the channel’s profile, <strong>share the QR code or invite link with others</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Subscribers will receive your messages,
|
|
||||||
but they cannot send messages in your channel.
|
|
||||||
When subscribing, they will receive <strong>a few of the latest messages of the channel history</strong>.</p>
|
|
||||||
|
|
||||||
<p>You can see the <strong>view count</strong> beside each message.
|
|
||||||
Note that this only counts subscribers who have read receipts enabled,
|
|
||||||
so the real view count may be larger.</p>
|
|
||||||
|
|
||||||
<h3 id="how-many-subscribers-can-a-channel-have">
|
|
||||||
|
|
||||||
|
|
||||||
How many subscribers can a channel have? <a href="#how-many-subscribers-can-a-channel-have" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>Channels are designed for much larger audiences than <a href="#groups">groups</a>.</p>
|
|
||||||
|
|
||||||
<p>The practical limit depends on the used <a href="#relays">relay</a>,
|
|
||||||
so there is no single fixed number that applies everywhere.</p>
|
|
||||||
|
|
||||||
<p>For really large channels with several tens of thousands of subscribers,
|
|
||||||
we recommend using a <a href="#multiple-accounts">dedicated profile</a> for the channel
|
|
||||||
and checking whether the relay is suitable.</p>
|
|
||||||
|
|
||||||
<p>But don’t be too hesitant: Delta Chat is designed to be relay-agnostic,
|
|
||||||
so you can change your relay at any point easily -
|
|
||||||
your existing subscribers will not even notice.
|
|
||||||
You only have to update the invite link you share with new subscribers in that case.</p>
|
|
||||||
|
|
||||||
<h2 id="calls">
|
|
||||||
|
|
||||||
|
|
||||||
Calls <a href="#calls" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Delta Chat supports one-to-one <strong>audio calls</strong> and <strong>video calls</strong>.</p>
|
|
||||||
|
|
||||||
<p>Calls are supported on Desktop, Ubuntu Touch, iOS and Android 8 and newer.</p>
|
|
||||||
|
|
||||||
<h3 id="place-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Place a call <a href="#place-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>In a one-to-one chat, tap the 📞 <strong>call icon</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>This opens a small menu
|
|
||||||
where you can choose whether to place an <strong>Audio Call</strong> or a <strong>Video Call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="accept-or-reject-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Accept or reject a call <a href="#accept-or-reject-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>When someone calls you,
|
|
||||||
Delta Chat shows an <strong>incoming call screen</strong> or notification.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>Accept</strong> to answer
|
|
||||||
or <strong>Decline</strong> to reject the call.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="during-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
During a call <a href="#during-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>mute</strong> your microphone.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>enable or disable your camera</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>On mobile, you can <strong>switch between front and back cameras</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Depending on the device, you can also select the audio output or use picture-in-picture.
|
|
||||||
On desktop, the call is using a dedicated window
|
|
||||||
and you can continue using the main Delta Chat window as usual.</p>
|
|
||||||
|
|
||||||
<h3 id="missed-calls-and-notifications">
|
|
||||||
|
|
||||||
|
|
||||||
Missed calls and notifications <a href="#missed-calls-and-notifications" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>If you do not answer, do not hear the ringing, or do not have your device at hand,
|
|
||||||
the call appears as a <strong>missed call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Only your accepted contacts</strong> can make your device ring.
|
|
||||||
Contact requests will appear as usual and will not ring.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>At <strong>Settings → Notifications → Calls</strong>,
|
|
||||||
you can disable the special call ringing screen completely.
|
|
||||||
If you do so, you will not be disturbed by any ringing notification,
|
|
||||||
you can still pick up the call by tapping the incoming call message bubble in its chat.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="webxdc">
|
<h2 id="webxdc">
|
||||||
|
|
||||||
|
|
||||||
@@ -1637,20 +1431,26 @@ txat-kontaktuak ezin izango dira erraz identifikatu.</p>
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
<p>Erabiltzen duzun <a href="#relays">erreleak</a> zure IP helbidea jakin behar du,
|
||||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
eta, batzuetan, baita zure kontaktuen gailuena ere; adibidez,
|
||||||
or use <a href="#webxdc">apps</a> together.</p>
|
kontaktu horiekin <a href="#experiments">deiak</a> egiten badituzu edo
|
||||||
|
<a href="#webxdc">aplikazioak</a> erabiltzen badituzu.</p>
|
||||||
|
|
||||||
<p>IP Addresses are needed for connectivity and efficiency.
|
<p>IP helbideak ezinbestekoak dira konektibitaterako eta eraginkortasunerako.
|
||||||
Delta Chat neither persists nor exposes them.
|
Ez dira gordetzen, ez argitara ematen.
|
||||||
Note that IP Addresses
|
Kontuan izan IP helbidea ez dela mezularitzako zerbitzuei-eta eman ohi zaiena
|
||||||
are not like an address you give to a delivery service,
|
bezalako helbide zehatz bat; askoz orokorragoa da, eta askotan eskualdea edo
|
||||||
but typically less precise, often defining city or region only.</p>
|
herrialdea baizik ez du identifikatzen.</p>
|
||||||
|
|
||||||
<p>If you see your IP Address as a risk,
|
<p>Internetek eta beste mezularitza-aplikazioek horrela funtzionatzen dutenez,
|
||||||
we recommend to use a VPN for the whole system.
|
ez dugu horren gaineko bestelako aukerarik eskaintzen, ez galderarik egiten.</p>
|
||||||
Per-app options leave gaps across your system.
|
|
||||||
For example, tapping a link can expose IP Addresses to unknown parties, which is by far the larger risk.</p>
|
<p>Uste baduzu zure IP helbidea jakiteak zure segurtasuna edo pribatutasuna
|
||||||
|
arriskuan jar ditzakeela, zera gomendatuko genizuke, VPN bat erabiltzea,
|
||||||
|
sistema blokeatzeko moduarekin batera. Zure sistemako aplikazio guztietan
|
||||||
|
alternatibak bilatzeak segurtasun-zuloak utziko ditu; adibidez, esteka batean klik egindakoan,
|
||||||
|
IP helbideak agerian geratzen zaizkie hirugarren ezezagunei, eta
|
||||||
|
hori askoz arriskutsuagoa da.</p>
|
||||||
|
|
||||||
<h3 id="sealedsender">
|
<h3 id="sealedsender">
|
||||||
|
|
||||||
|
|||||||
@@ -29,21 +29,6 @@
|
|||||||
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#channels">Channels</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#subscribe-to-a-channel">Subscribe to a channel</a></li>
|
|
||||||
<li><a href="#create-a-channel">Create a channel</a></li>
|
|
||||||
<li><a href="#how-many-subscribers-can-a-channel-have">How many subscribers can a channel have?</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#calls">Calls</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#place-a-call">Place a call</a></li>
|
|
||||||
<li><a href="#accept-or-reject-a-call">Accept or reject a call</a></li>
|
|
||||||
<li><a href="#during-a-call">During a call</a></li>
|
|
||||||
<li><a href="#missed-calls-and-notifications">Missed calls and notifications</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#webxdc">In-chat apps</a>
|
<li><a href="#webxdc">In-chat apps</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
||||||
@@ -562,197 +547,6 @@ but more than 150 is not recommended.</p>
|
|||||||
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
||||||
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
||||||
|
|
||||||
<h2 id="channels">
|
|
||||||
|
|
||||||
|
|
||||||
Channels <a href="#channels" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Channels are a one-to-many tool for broadcasting messages.</p>
|
|
||||||
|
|
||||||
<h3 id="subscribe-to-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Subscribe to a channel <a href="#subscribe-to-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Scan the <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>QR code</strong>
|
|
||||||
or tap the <strong>invite link</strong> you got from the channel owner.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>That’s all!
|
|
||||||
You will receive a few of the messages from the channel history
|
|
||||||
and, from that point on, all new messages from the channel.</p>
|
|
||||||
|
|
||||||
<p><strong>Don’t worry,</strong> if that does not happen immediately.
|
|
||||||
Once the channel owner comes online, your join request will be processed.</p>
|
|
||||||
|
|
||||||
<p>As all of Delta Chat, also Channels are private and decentralized,
|
|
||||||
there is no public discovery.</p>
|
|
||||||
|
|
||||||
<p>Other channel subscribers will not see that you subscribed and cannot message you.
|
|
||||||
The channel owner, however, can message you.
|
|
||||||
They will also see that you read a message unless you have read receipts disabled.</p>
|
|
||||||
|
|
||||||
<p>If you do not want to share your main profile,
|
|
||||||
you can also create a <a href="#multiple-accounts">dedicated profile</a> for joining a channel.</p>
|
|
||||||
|
|
||||||
<h3 id="create-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Create a channel <a href="#create-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>New Chat</strong> and choose <strong>New Channel</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Enter a <strong>name</strong>, optionally set an <strong>image</strong> and <strong>description</strong>, and hit the <strong>Create</strong> button.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can now send and manage messages as usual.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>From the channel’s profile, <strong>share the QR code or invite link with others</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Subscribers will receive your messages,
|
|
||||||
but they cannot send messages in your channel.
|
|
||||||
When subscribing, they will receive <strong>a few of the latest messages of the channel history</strong>.</p>
|
|
||||||
|
|
||||||
<p>You can see the <strong>view count</strong> beside each message.
|
|
||||||
Note that this only counts subscribers who have read receipts enabled,
|
|
||||||
so the real view count may be larger.</p>
|
|
||||||
|
|
||||||
<h3 id="how-many-subscribers-can-a-channel-have">
|
|
||||||
|
|
||||||
|
|
||||||
How many subscribers can a channel have? <a href="#how-many-subscribers-can-a-channel-have" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>Channels are designed for much larger audiences than <a href="#groups">groups</a>.</p>
|
|
||||||
|
|
||||||
<p>The practical limit depends on the used <a href="#relays">relay</a>,
|
|
||||||
so there is no single fixed number that applies everywhere.</p>
|
|
||||||
|
|
||||||
<p>For really large channels with several tens of thousands of subscribers,
|
|
||||||
we recommend using a <a href="#multiple-accounts">dedicated profile</a> for the channel
|
|
||||||
and checking whether the relay is suitable.</p>
|
|
||||||
|
|
||||||
<p>But don’t be too hesitant: Delta Chat is designed to be relay-agnostic,
|
|
||||||
so you can change your relay at any point easily -
|
|
||||||
your existing subscribers will not even notice.
|
|
||||||
You only have to update the invite link you share with new subscribers in that case.</p>
|
|
||||||
|
|
||||||
<h2 id="calls">
|
|
||||||
|
|
||||||
|
|
||||||
Calls <a href="#calls" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Delta Chat supports one-to-one <strong>audio calls</strong> and <strong>video calls</strong>.</p>
|
|
||||||
|
|
||||||
<p>Calls are supported on Desktop, Ubuntu Touch, iOS and Android 8 and newer.</p>
|
|
||||||
|
|
||||||
<h3 id="place-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Place a call <a href="#place-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>In a one-to-one chat, tap the 📞 <strong>call icon</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>This opens a small menu
|
|
||||||
where you can choose whether to place an <strong>Audio Call</strong> or a <strong>Video Call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="accept-or-reject-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Accept or reject a call <a href="#accept-or-reject-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>When someone calls you,
|
|
||||||
Delta Chat shows an <strong>incoming call screen</strong> or notification.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>Accept</strong> to answer
|
|
||||||
or <strong>Decline</strong> to reject the call.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="during-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
During a call <a href="#during-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>mute</strong> your microphone.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>enable or disable your camera</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>On mobile, you can <strong>switch between front and back cameras</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Depending on the device, you can also select the audio output or use picture-in-picture.
|
|
||||||
On desktop, the call is using a dedicated window
|
|
||||||
and you can continue using the main Delta Chat window as usual.</p>
|
|
||||||
|
|
||||||
<h3 id="missed-calls-and-notifications">
|
|
||||||
|
|
||||||
|
|
||||||
Missed calls and notifications <a href="#missed-calls-and-notifications" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>If you do not answer, do not hear the ringing, or do not have your device at hand,
|
|
||||||
the call appears as a <strong>missed call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Only your accepted contacts</strong> can make your device ring.
|
|
||||||
Contact requests will appear as usual and will not ring.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>At <strong>Settings → Notifications → Calls</strong>,
|
|
||||||
you can disable the special call ringing screen completely.
|
|
||||||
If you do so, you will not be disturbed by any ringing notification,
|
|
||||||
you can still pick up the call by tapping the incoming call message bubble in its chat.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="webxdc">
|
<h2 id="webxdc">
|
||||||
|
|
||||||
|
|
||||||
@@ -1544,20 +1338,23 @@ can not be identified easily.</p>
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
|
||||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
as well as sometimes your contact’s devices if you have a <a href="#experiments">call</a>
|
||||||
or use <a href="#webxdc">apps</a> together.</p>
|
or use <a href="#webxdc">apps</a> together.</p>
|
||||||
|
|
||||||
<p>IP Addresses are needed for connectivity and efficiency.
|
<p>IP Addresses are needed for connectivity and efficiency.
|
||||||
Delta Chat neither persists nor exposes them.
|
They are neither persisted nor exposed.
|
||||||
Note that IP Addresses
|
Note that the IP Address
|
||||||
are not like an address you give to a delivery service,
|
is not like a detailed address you give to a delivery service,
|
||||||
but typically less precise, often defining city or region only.</p>
|
but much more coarse, often defining region or country only.</p>
|
||||||
|
|
||||||
<p>If you see your IP Address as a risk,
|
<p>As this is just how the internet and other messengers work by default,
|
||||||
we recommend to use a VPN for the whole system.
|
we do not offer options here or ask upfront questions.</p>
|
||||||
Per-app options leave gaps across your system.
|
|
||||||
For example, tapping a link can expose IP Addresses to unknown parties, which is by far the larger risk.</p>
|
<p>If you see your IP Address as a security or privacy risk,
|
||||||
|
we recommend to use a VPN, in combination with system lockdown mode.
|
||||||
|
Hunting down options in all apps on your system will leave gaps.
|
||||||
|
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
|
||||||
|
|
||||||
<h3 id="sealedsender">
|
<h3 id="sealedsender">
|
||||||
|
|
||||||
|
|||||||
@@ -29,21 +29,6 @@
|
|||||||
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#channels">Channels</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#subscribe-to-a-channel">Subscribe to a channel</a></li>
|
|
||||||
<li><a href="#create-a-channel">Create a channel</a></li>
|
|
||||||
<li><a href="#how-many-subscribers-can-a-channel-have">How many subscribers can a channel have?</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#calls">Calls</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#place-a-call">Place a call</a></li>
|
|
||||||
<li><a href="#accept-or-reject-a-call">Accept or reject a call</a></li>
|
|
||||||
<li><a href="#during-a-call">During a call</a></li>
|
|
||||||
<li><a href="#missed-calls-and-notifications">Missed calls and notifications</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#webxdc">In-chat apps</a>
|
<li><a href="#webxdc">In-chat apps</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
||||||
@@ -642,197 +627,6 @@ but more than 150 is not recommended.</p>
|
|||||||
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
||||||
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
||||||
|
|
||||||
<h2 id="channels">
|
|
||||||
|
|
||||||
|
|
||||||
Channels <a href="#channels" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Channels are a one-to-many tool for broadcasting messages.</p>
|
|
||||||
|
|
||||||
<h3 id="subscribe-to-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Subscribe to a channel <a href="#subscribe-to-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Scan the <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>QR code</strong>
|
|
||||||
or tap the <strong>invite link</strong> you got from the channel owner.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>That’s all!
|
|
||||||
You will receive a few of the messages from the channel history
|
|
||||||
and, from that point on, all new messages from the channel.</p>
|
|
||||||
|
|
||||||
<p><strong>Don’t worry,</strong> if that does not happen immediately.
|
|
||||||
Once the channel owner comes online, your join request will be processed.</p>
|
|
||||||
|
|
||||||
<p>As all of Delta Chat, also Channels are private and decentralized,
|
|
||||||
there is no public discovery.</p>
|
|
||||||
|
|
||||||
<p>Other channel subscribers will not see that you subscribed and cannot message you.
|
|
||||||
The channel owner, however, can message you.
|
|
||||||
They will also see that you read a message unless you have read receipts disabled.</p>
|
|
||||||
|
|
||||||
<p>If you do not want to share your main profile,
|
|
||||||
you can also create a <a href="#multiple-accounts">dedicated profile</a> for joining a channel.</p>
|
|
||||||
|
|
||||||
<h3 id="create-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Create a channel <a href="#create-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>New Chat</strong> and choose <strong>New Channel</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Enter a <strong>name</strong>, optionally set an <strong>image</strong> and <strong>description</strong>, and hit the <strong>Create</strong> button.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can now send and manage messages as usual.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>From the channel’s profile, <strong>share the QR code or invite link with others</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Subscribers will receive your messages,
|
|
||||||
but they cannot send messages in your channel.
|
|
||||||
When subscribing, they will receive <strong>a few of the latest messages of the channel history</strong>.</p>
|
|
||||||
|
|
||||||
<p>You can see the <strong>view count</strong> beside each message.
|
|
||||||
Note that this only counts subscribers who have read receipts enabled,
|
|
||||||
so the real view count may be larger.</p>
|
|
||||||
|
|
||||||
<h3 id="how-many-subscribers-can-a-channel-have">
|
|
||||||
|
|
||||||
|
|
||||||
How many subscribers can a channel have? <a href="#how-many-subscribers-can-a-channel-have" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>Channels are designed for much larger audiences than <a href="#groups">groups</a>.</p>
|
|
||||||
|
|
||||||
<p>The practical limit depends on the used <a href="#relays">relay</a>,
|
|
||||||
so there is no single fixed number that applies everywhere.</p>
|
|
||||||
|
|
||||||
<p>For really large channels with several tens of thousands of subscribers,
|
|
||||||
we recommend using a <a href="#multiple-accounts">dedicated profile</a> for the channel
|
|
||||||
and checking whether the relay is suitable.</p>
|
|
||||||
|
|
||||||
<p>But don’t be too hesitant: Delta Chat is designed to be relay-agnostic,
|
|
||||||
so you can change your relay at any point easily -
|
|
||||||
your existing subscribers will not even notice.
|
|
||||||
You only have to update the invite link you share with new subscribers in that case.</p>
|
|
||||||
|
|
||||||
<h2 id="calls">
|
|
||||||
|
|
||||||
|
|
||||||
Calls <a href="#calls" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Delta Chat supports one-to-one <strong>audio calls</strong> and <strong>video calls</strong>.</p>
|
|
||||||
|
|
||||||
<p>Calls are supported on Desktop, Ubuntu Touch, iOS and Android 8 and newer.</p>
|
|
||||||
|
|
||||||
<h3 id="place-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Place a call <a href="#place-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>In a one-to-one chat, tap the 📞 <strong>call icon</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>This opens a small menu
|
|
||||||
where you can choose whether to place an <strong>Audio Call</strong> or a <strong>Video Call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="accept-or-reject-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Accept or reject a call <a href="#accept-or-reject-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>When someone calls you,
|
|
||||||
Delta Chat shows an <strong>incoming call screen</strong> or notification.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>Accept</strong> to answer
|
|
||||||
or <strong>Decline</strong> to reject the call.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="during-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
During a call <a href="#during-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>mute</strong> your microphone.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>enable or disable your camera</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>On mobile, you can <strong>switch between front and back cameras</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Depending on the device, you can also select the audio output or use picture-in-picture.
|
|
||||||
On desktop, the call is using a dedicated window
|
|
||||||
and you can continue using the main Delta Chat window as usual.</p>
|
|
||||||
|
|
||||||
<h3 id="missed-calls-and-notifications">
|
|
||||||
|
|
||||||
|
|
||||||
Missed calls and notifications <a href="#missed-calls-and-notifications" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>If you do not answer, do not hear the ringing, or do not have your device at hand,
|
|
||||||
the call appears as a <strong>missed call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Only your accepted contacts</strong> can make your device ring.
|
|
||||||
Contact requests will appear as usual and will not ring.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>At <strong>Settings → Notifications → Calls</strong>,
|
|
||||||
you can disable the special call ringing screen completely.
|
|
||||||
If you do so, you will not be disturbed by any ringing notification,
|
|
||||||
you can still pick up the call by tapping the incoming call message bubble in its chat.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="webxdc">
|
<h2 id="webxdc">
|
||||||
|
|
||||||
|
|
||||||
@@ -1631,20 +1425,23 @@ can not be identified easily.</p>
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
|
||||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
as well as sometimes your contact’s devices if you have a <a href="#experiments">call</a>
|
||||||
or use <a href="#webxdc">apps</a> together.</p>
|
or use <a href="#webxdc">apps</a> together.</p>
|
||||||
|
|
||||||
<p>IP Addresses are needed for connectivity and efficiency.
|
<p>IP Addresses are needed for connectivity and efficiency.
|
||||||
Delta Chat neither persists nor exposes them.
|
They are neither persisted nor exposed.
|
||||||
Note that IP Addresses
|
Note that the IP Address
|
||||||
are not like an address you give to a delivery service,
|
is not like a detailed address you give to a delivery service,
|
||||||
but typically less precise, often defining city or region only.</p>
|
but much more coarse, often defining region or country only.</p>
|
||||||
|
|
||||||
<p>If you see your IP Address as a risk,
|
<p>As this is just how the internet and other messengers work by default,
|
||||||
we recommend to use a VPN for the whole system.
|
we do not offer options here or ask upfront questions.</p>
|
||||||
Per-app options leave gaps across your system.
|
|
||||||
For example, tapping a link can expose IP Addresses to unknown parties, which is by far the larger risk.</p>
|
<p>If you see your IP Address as a security or privacy risk,
|
||||||
|
we recommend to use a VPN, in combination with system lockdown mode.
|
||||||
|
Hunting down options in all apps on your system will leave gaps.
|
||||||
|
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
|
||||||
|
|
||||||
<h3 id="sealedsender">
|
<h3 id="sealedsender">
|
||||||
|
|
||||||
|
|||||||
@@ -29,21 +29,6 @@
|
|||||||
<li><a href="#quanti-membri-possono-partecipare-a-un-singolo-gruppo">Quanti membri possono partecipare a un singolo gruppo?</a></li>
|
<li><a href="#quanti-membri-possono-partecipare-a-un-singolo-gruppo">Quanti membri possono partecipare a un singolo gruppo?</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#canali">Canali</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#iscriversi-a-un-canale">Iscriversi a un canale</a></li>
|
|
||||||
<li><a href="#creare-un-canale">Creare un canale</a></li>
|
|
||||||
<li><a href="#quanti-iscritti-può-avere-un-canale">Quanti iscritti può avere un canale?</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#calls">Chiamate</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#place-a-call">Place a call</a></li>
|
|
||||||
<li><a href="#accept-or-reject-a-call">Accept or reject a call</a></li>
|
|
||||||
<li><a href="#during-a-call">During a call</a></li>
|
|
||||||
<li><a href="#missed-calls-and-notifications">Missed calls and notifications</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#webxdc">Apps in chat</a>
|
<li><a href="#webxdc">Apps in chat</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#dove-posso-trovare-le-apps-in-chat">Dove posso trovare le apps in chat?</a></li>
|
<li><a href="#dove-posso-trovare-le-apps-in-chat">Dove posso trovare le apps in chat?</a></li>
|
||||||
@@ -635,196 +620,6 @@ ma non è consigliabile superare i 150.</p>
|
|||||||
dove Delta Chat è un servizio di messaggistica privato per chattare con <a href="#groups">uguali diritti</a>.
|
dove Delta Chat è un servizio di messaggistica privato per chattare con <a href="#groups">uguali diritti</a>.
|
||||||
Vedi <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">numero di Dunbar</a> per ulteriori approfondimenti.</p>
|
Vedi <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">numero di Dunbar</a> per ulteriori approfondimenti.</p>
|
||||||
|
|
||||||
<h2 id="canali">
|
|
||||||
|
|
||||||
|
|
||||||
Canali <a href="#canali" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>I canali sono uno strumento da uno a molti per la trasmissione di messaggi.</p>
|
|
||||||
|
|
||||||
<h3 id="iscriversi-a-un-canale">
|
|
||||||
|
|
||||||
|
|
||||||
Iscriversi a un canale <a href="#iscriversi-a-un-canale" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Scansiona il <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>Codice QR</strong>
|
|
||||||
oppure tocca il <strong>collegamento di invito</strong> che hai ricevuto dal proprietario del canale.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Ecco fatto!
|
|
||||||
Riceverai alcuni dei messaggi dalla cronologia del canale
|
|
||||||
e, da quel momento in poi, tutti i nuovi messaggi dal canale.</p>
|
|
||||||
|
|
||||||
<p><strong>Non preoccuparti,</strong> se non accade immediatamente.
|
|
||||||
Non appena il proprietario del canale si connetterà, la tua richiesta di iscrizione verrà elaborata.</p>
|
|
||||||
|
|
||||||
<p>Poiché tutti i Canali di Delta Chat sono privati e decentralizzati,
|
|
||||||
non esiste una funzione di ricerca pubblica.</p>
|
|
||||||
|
|
||||||
<p>Gli altri iscritti al canale non vedranno che ti sei iscritto e non potranno inviarti messaggi.
|
|
||||||
Il proprietario del canale, tuttavia, potrà inviarti messaggi.
|
|
||||||
Inoltre, vedrà che hai letto un messaggio, a meno che tu non abbia disabilitato le conferme di lettura.</p>
|
|
||||||
|
|
||||||
<p>Se non desideri condividere il tuo profilo principale,
|
|
||||||
puoi anche creare un <a href="#multiple-accounts">profilo dedicato</a> per unirti a un canale.</p>
|
|
||||||
|
|
||||||
<h3 id="creare-un-canale">
|
|
||||||
|
|
||||||
|
|
||||||
Creare un canale <a href="#creare-un-canale" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Tocca <strong>Nuova Chat</strong> e scegli <strong>Nuovo Canale</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Inserisci un <strong>nome</strong>, imposta facoltativamente un’<strong>immagine</strong> e una <strong>descrizione</strong>, e fai clic sul pulsante <strong>Crea</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Ora puoi inviare e gestire i messaggi come di consueto.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Dal profilo del canale, <strong>condividi il codice QR o il collegamento di invito con altri</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Gli iscritti riceveranno i tuoi messaggi,
|
|
||||||
ma non potranno inviare messaggi nel tuo canale.
|
|
||||||
Al momento dell’iscrizione, riceveranno <strong>alcuni degli ultimi messaggi della cronologia del canale</strong>.</p>
|
|
||||||
|
|
||||||
<p>Accanto a ciascun messaggio puoi vedere il <strong>numero di visualizzazioni</strong>.
|
|
||||||
Tieni presente che questo conteggio si riferisce solo agli abbonati che hanno attivato le conferme di lettura,
|
|
||||||
quindi il numero reale di visualizzazioni potrebbe essere superiore.</p>
|
|
||||||
|
|
||||||
<h3 id="quanti-iscritti-può-avere-un-canale">
|
|
||||||
|
|
||||||
|
|
||||||
Quanti iscritti può avere un canale? <a href="#quanti-iscritti-può-avere-un-canale" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>I canali sono progettati per un pubblico molto più ampio rispetto ai <a href="#groups">gruppi</a>.</p>
|
|
||||||
|
|
||||||
<p>Il limite pratico dipende dal numero di <a href="#relays">ripetitori</a> utilizzati, quindi non esiste un singolo numero fisso valido ovunque.</p>
|
|
||||||
|
|
||||||
<p>Per canali molto grandi con diverse decine di migliaia di iscritti,
|
|
||||||
consigliamo di utilizzare un <a href="#multiple-accounts">profilo dedicato</a> per il canale
|
|
||||||
e di verificare se il ripetitore è adatto.</p>
|
|
||||||
|
|
||||||
<p>Ma non esitare troppo: Delta Chat è progettato per essere indipendente dal ripetitore,
|
|
||||||
quindi puoi cambiare il tuo ripetitore in qualsiasi momento con facilità -
|
|
||||||
i tuoi iscritti esistenti non se ne accorgeranno nemmeno.
|
|
||||||
In tal caso, dovrai solo aggiornare il collegamento di invito che condividi con i nuovi iscritti.</p>
|
|
||||||
|
|
||||||
<h2 id="calls">
|
|
||||||
|
|
||||||
|
|
||||||
Chiamate <a href="#calls" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Delta Chat supports one-to-one <strong>audio calls</strong> and <strong>video calls</strong>.</p>
|
|
||||||
|
|
||||||
<p>Calls are supported on Desktop, Ubuntu Touch, iOS and Android 8 and newer.</p>
|
|
||||||
|
|
||||||
<h3 id="place-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Place a call <a href="#place-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>In a one-to-one chat, tap the 📞 <strong>call icon</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>This opens a small menu
|
|
||||||
where you can choose whether to place an <strong>Audio Call</strong> or a <strong>Video Call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="accept-or-reject-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Accept or reject a call <a href="#accept-or-reject-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>When someone calls you,
|
|
||||||
Delta Chat shows an <strong>incoming call screen</strong> or notification.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>Accept</strong> to answer
|
|
||||||
or <strong>Decline</strong> to reject the call.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="during-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
During a call <a href="#during-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>mute</strong> your microphone.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>enable or disable your camera</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>On mobile, you can <strong>switch between front and back cameras</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Depending on the device, you can also select the audio output or use picture-in-picture.
|
|
||||||
On desktop, the call is using a dedicated window
|
|
||||||
and you can continue using the main Delta Chat window as usual.</p>
|
|
||||||
|
|
||||||
<h3 id="missed-calls-and-notifications">
|
|
||||||
|
|
||||||
|
|
||||||
Missed calls and notifications <a href="#missed-calls-and-notifications" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>If you do not answer, do not hear the ringing, or do not have your device at hand,
|
|
||||||
the call appears as a <strong>missed call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Only your accepted contacts</strong> can make your device ring.
|
|
||||||
Contact requests will appear as usual and will not ring.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>At <strong>Settings → Notifications → Calls</strong>,
|
|
||||||
you can disable the special call ringing screen completely.
|
|
||||||
If you do so, you will not be disturbed by any ringing notification,
|
|
||||||
you can still pick up the call by tapping the incoming call message bubble in its chat.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="webxdc">
|
<h2 id="webxdc">
|
||||||
|
|
||||||
|
|
||||||
@@ -1617,20 +1412,23 @@ non possono essere identificati facilmente.</p>
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
<p>Il <a href="#relays">ripetitore</a> utilizzato deve conoscere il tuo indirizzo IP,
|
||||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
e talvolta anche i dispositivi dei tuoi contatti se avete una <a href="#experiments">chiamata</a>
|
||||||
or use <a href="#webxdc">apps</a> together.</p>
|
o utilizzate <a href="#webxdc">apps</a> insieme.</p>
|
||||||
|
|
||||||
<p>IP Addresses are needed for connectivity and efficiency.
|
<p>Gli indirizzi IP sono necessari per la connettività e l’efficienza.
|
||||||
Delta Chat neither persists nor exposes them.
|
Non sono né persistenti né esposti.
|
||||||
Note that IP Addresses
|
Si noti che l’indirizzo IP
|
||||||
are not like an address you give to a delivery service,
|
non è come un indirizzo dettagliato che si fornisce a un servizio di consegna,
|
||||||
but typically less precise, often defining city or region only.</p>
|
ma molto più generico, che spesso definisce solo la regione o il paese.</p>
|
||||||
|
|
||||||
<p>If you see your IP Address as a risk,
|
<p>Poiché questo è il modo in cui Internet e altri servizi di messaggistica funzionano di default,
|
||||||
we recommend to use a VPN for the whole system.
|
non offriamo opzioni né poniamo domande in anticipo.</p>
|
||||||
Per-app options leave gaps across your system.
|
|
||||||
For example, tapping a link can expose IP Addresses to unknown parties, which is by far the larger risk.</p>
|
<p>Se ritieni che il tuo indirizzo IP rappresenti un rischio per la sicurezza o la privacy,
|
||||||
|
ti consigliamo di utilizzare una VPN, in combinazione con la modalità di blocco del sistema.
|
||||||
|
Esplorare le opzioni in tutte le app del tuo sistema lascerà delle lacune.
|
||||||
|
Ad esempio, cliccare su un link espone gli indirizzi IP a sconosciuti e rappresenta il rischio di gran lunga maggiore.</p>
|
||||||
|
|
||||||
<h3 id="sealedsender">
|
<h3 id="sealedsender">
|
||||||
|
|
||||||
|
|||||||
@@ -29,21 +29,6 @@
|
|||||||
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#channels">Channels</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#subscribe-to-a-channel">Subscribe to a channel</a></li>
|
|
||||||
<li><a href="#create-a-channel">Create a channel</a></li>
|
|
||||||
<li><a href="#how-many-subscribers-can-a-channel-have">How many subscribers can a channel have?</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#calls">Calls</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#place-a-call">Place a call</a></li>
|
|
||||||
<li><a href="#accept-or-reject-a-call">Accept or reject a call</a></li>
|
|
||||||
<li><a href="#during-a-call">During a call</a></li>
|
|
||||||
<li><a href="#missed-calls-and-notifications">Missed calls and notifications</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#webxdc">In-chat apps</a>
|
<li><a href="#webxdc">In-chat apps</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
||||||
@@ -637,197 +622,6 @@ but more than 150 is not recommended.</p>
|
|||||||
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
||||||
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
||||||
|
|
||||||
<h2 id="channels">
|
|
||||||
|
|
||||||
|
|
||||||
Channels <a href="#channels" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Channels are a one-to-many tool for broadcasting messages.</p>
|
|
||||||
|
|
||||||
<h3 id="subscribe-to-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Subscribe to a channel <a href="#subscribe-to-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Scan the <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>QR code</strong>
|
|
||||||
or tap the <strong>invite link</strong> you got from the channel owner.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>That’s all!
|
|
||||||
You will receive a few of the messages from the channel history
|
|
||||||
and, from that point on, all new messages from the channel.</p>
|
|
||||||
|
|
||||||
<p><strong>Don’t worry,</strong> if that does not happen immediately.
|
|
||||||
Once the channel owner comes online, your join request will be processed.</p>
|
|
||||||
|
|
||||||
<p>As all of Delta Chat, also Channels are private and decentralized,
|
|
||||||
there is no public discovery.</p>
|
|
||||||
|
|
||||||
<p>Other channel subscribers will not see that you subscribed and cannot message you.
|
|
||||||
The channel owner, however, can message you.
|
|
||||||
They will also see that you read a message unless you have read receipts disabled.</p>
|
|
||||||
|
|
||||||
<p>If you do not want to share your main profile,
|
|
||||||
you can also create a <a href="#multiple-accounts">dedicated profile</a> for joining a channel.</p>
|
|
||||||
|
|
||||||
<h3 id="create-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Create a channel <a href="#create-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>New Chat</strong> and choose <strong>New Channel</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Enter a <strong>name</strong>, optionally set an <strong>image</strong> and <strong>description</strong>, and hit the <strong>Create</strong> button.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can now send and manage messages as usual.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>From the channel’s profile, <strong>share the QR code or invite link with others</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Subscribers will receive your messages,
|
|
||||||
but they cannot send messages in your channel.
|
|
||||||
When subscribing, they will receive <strong>a few of the latest messages of the channel history</strong>.</p>
|
|
||||||
|
|
||||||
<p>You can see the <strong>view count</strong> beside each message.
|
|
||||||
Note that this only counts subscribers who have read receipts enabled,
|
|
||||||
so the real view count may be larger.</p>
|
|
||||||
|
|
||||||
<h3 id="how-many-subscribers-can-a-channel-have">
|
|
||||||
|
|
||||||
|
|
||||||
How many subscribers can a channel have? <a href="#how-many-subscribers-can-a-channel-have" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>Channels are designed for much larger audiences than <a href="#groups">groups</a>.</p>
|
|
||||||
|
|
||||||
<p>The practical limit depends on the used <a href="#relays">relay</a>,
|
|
||||||
so there is no single fixed number that applies everywhere.</p>
|
|
||||||
|
|
||||||
<p>For really large channels with several tens of thousands of subscribers,
|
|
||||||
we recommend using a <a href="#multiple-accounts">dedicated profile</a> for the channel
|
|
||||||
and checking whether the relay is suitable.</p>
|
|
||||||
|
|
||||||
<p>But don’t be too hesitant: Delta Chat is designed to be relay-agnostic,
|
|
||||||
so you can change your relay at any point easily -
|
|
||||||
your existing subscribers will not even notice.
|
|
||||||
You only have to update the invite link you share with new subscribers in that case.</p>
|
|
||||||
|
|
||||||
<h2 id="calls">
|
|
||||||
|
|
||||||
|
|
||||||
Calls <a href="#calls" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Delta Chat supports one-to-one <strong>audio calls</strong> and <strong>video calls</strong>.</p>
|
|
||||||
|
|
||||||
<p>Calls are supported on Desktop, Ubuntu Touch, iOS and Android 8 and newer.</p>
|
|
||||||
|
|
||||||
<h3 id="place-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Place a call <a href="#place-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>In a one-to-one chat, tap the 📞 <strong>call icon</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>This opens a small menu
|
|
||||||
where you can choose whether to place an <strong>Audio Call</strong> or a <strong>Video Call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="accept-or-reject-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Accept or reject a call <a href="#accept-or-reject-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>When someone calls you,
|
|
||||||
Delta Chat shows an <strong>incoming call screen</strong> or notification.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>Accept</strong> to answer
|
|
||||||
or <strong>Decline</strong> to reject the call.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="during-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
During a call <a href="#during-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>mute</strong> your microphone.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>enable or disable your camera</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>On mobile, you can <strong>switch between front and back cameras</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Depending on the device, you can also select the audio output or use picture-in-picture.
|
|
||||||
On desktop, the call is using a dedicated window
|
|
||||||
and you can continue using the main Delta Chat window as usual.</p>
|
|
||||||
|
|
||||||
<h3 id="missed-calls-and-notifications">
|
|
||||||
|
|
||||||
|
|
||||||
Missed calls and notifications <a href="#missed-calls-and-notifications" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>If you do not answer, do not hear the ringing, or do not have your device at hand,
|
|
||||||
the call appears as a <strong>missed call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Only your accepted contacts</strong> can make your device ring.
|
|
||||||
Contact requests will appear as usual and will not ring.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>At <strong>Settings → Notifications → Calls</strong>,
|
|
||||||
you can disable the special call ringing screen completely.
|
|
||||||
If you do so, you will not be disturbed by any ringing notification,
|
|
||||||
you can still pick up the call by tapping the incoming call message bubble in its chat.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="webxdc">
|
<h2 id="webxdc">
|
||||||
|
|
||||||
|
|
||||||
@@ -1625,20 +1419,23 @@ can not be identified easily.</p>
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
|
||||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
as well as sometimes your contact’s devices if you have a <a href="#experiments">call</a>
|
||||||
or use <a href="#webxdc">apps</a> together.</p>
|
or use <a href="#webxdc">apps</a> together.</p>
|
||||||
|
|
||||||
<p>IP Addresses are needed for connectivity and efficiency.
|
<p>IP Addresses are needed for connectivity and efficiency.
|
||||||
Delta Chat neither persists nor exposes them.
|
They are neither persisted nor exposed.
|
||||||
Note that IP Addresses
|
Note that the IP Address
|
||||||
are not like an address you give to a delivery service,
|
is not like a detailed address you give to a delivery service,
|
||||||
but typically less precise, often defining city or region only.</p>
|
but much more coarse, often defining region or country only.</p>
|
||||||
|
|
||||||
<p>If you see your IP Address as a risk,
|
<p>As this is just how the internet and other messengers work by default,
|
||||||
we recommend to use a VPN for the whole system.
|
we do not offer options here or ask upfront questions.</p>
|
||||||
Per-app options leave gaps across your system.
|
|
||||||
For example, tapping a link can expose IP Addresses to unknown parties, which is by far the larger risk.</p>
|
<p>If you see your IP Address as a security or privacy risk,
|
||||||
|
we recommend to use a VPN, in combination with system lockdown mode.
|
||||||
|
Hunting down options in all apps on your system will leave gaps.
|
||||||
|
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
|
||||||
|
|
||||||
<h3 id="sealedsender">
|
<h3 id="sealedsender">
|
||||||
|
|
||||||
|
|||||||
+176
-276
@@ -2,46 +2,31 @@
|
|||||||
<html lang="pl"><head><meta charset="UTF-8" /><meta name="viewport" content="initial-scale=1.0" /><link rel="stylesheet" href="../help.css" /></head><body><ul id="top">
|
<html lang="pl"><head><meta charset="UTF-8" /><meta name="viewport" content="initial-scale=1.0" /><link rel="stylesheet" href="../help.css" /></head><body><ul id="top">
|
||||||
<li><a href="#czym-jest-delta-chat">Czym jest Delta Chat?</a>
|
<li><a href="#czym-jest-delta-chat">Czym jest Delta Chat?</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#howtoe2ee">Jak znaleźć osoby do czatu?</a></li>
|
<li><a href="#howtoe2ee">How can I find people to chat with?</a></li>
|
||||||
<li><a href="#dlaczego-czat-jest-oznaczony-jako-prośba">Dlaczego czat jest oznaczony jako „Prośba”?</a></li>
|
<li><a href="#why-is-a-chat-marked-as-request">Why is a chat marked as “Request”?</a></li>
|
||||||
<li><a href="#jak-mogę-skontaktować-ze-sobą-dwóch-znajomych">Jak mogę skontaktować ze sobą dwóch znajomych?</a></li>
|
<li><a href="#how-can-i-put-two-of-my-friends-in-contact-with-each-other">How can I put two of my friends in contact with each other?</a></li>
|
||||||
<li><a href="#multiple-accounts">Czym są profile? Jak mogę przełączać się między nimi?</a></li>
|
<li><a href="#multiple-accounts">Czym są profile? Jak mogę przełączać się między nimi?</a></li>
|
||||||
<li><a href="#kto-widzi-moje-zdjęcie-profilowe">Kto widzi moje zdjęcie profilowe?</a></li>
|
<li><a href="#kto-widzi-moje-zdjęcie-profilowe">Kto widzi moje zdjęcie profilowe?</a></li>
|
||||||
<li><a href="#signature">Czy w Delta Chat mogę ustawić biografię/status?</a></li>
|
<li><a href="#signature">Can I set a Bio/Status with Delta Chat?</a></li>
|
||||||
<li><a href="#co-oznacza-przypinanie-wyciszanie-i-archiwizowanie">Co oznacza przypinanie, wyciszanie i archiwizowanie?</a></li>
|
<li><a href="#co-oznacza-przypinanie-wyciszanie-i-archiwizowanie">Co oznacza przypinanie, wyciszanie i archiwizowanie?</a></li>
|
||||||
<li><a href="#save">Jak działają „Zapisane wiadomości”?</a></li>
|
<li><a href="#save">Jak działają „Zapisane wiadomości”?</a></li>
|
||||||
<li><a href="#co-oznacza-zielona-kropka">Co oznacza zielona kropka?</a></li>
|
<li><a href="#co-oznacza-zielona-kropka">Co oznacza zielona kropka?</a></li>
|
||||||
<li><a href="#co-oznaczają-znaczniki-wyświetlane-obok-wiadomości-wychodzących">Co oznaczają znaczniki wyświetlane obok wiadomości wychodzących?</a></li>
|
<li><a href="#co-oznaczają-znaczniki-wyświetlane-obok-wiadomości-wychodzących">Co oznaczają znaczniki wyświetlane obok wiadomości wychodzących?</a></li>
|
||||||
<li><a href="#edit">Poprawianie literówek i usuwanie wiadomości po wysłaniu</a></li>
|
<li><a href="#edit">Poprawianie literówek i usuwanie wiadomości po wysłaniu</a></li>
|
||||||
<li><a href="#mediaquality">Jak obsługiwana jest jakość multimediów?</a></li>
|
<li><a href="#mediaquality">How is media quality handled?</a></li>
|
||||||
<li><a href="#ephemeralmsgs">Jak działają znikające wiadomości?</a></li>
|
<li><a href="#ephemeralmsgs">Jak działają znikające wiadomości?</a></li>
|
||||||
<li><a href="#delold">Co się stanie, jeśli włączę opcję „Usuń wiadomości z urządzenia”?</a></li>
|
<li><a href="#delold">Co się stanie, jeśli włączę opcję „Usuń wiadomości z urządzenia”?</a></li>
|
||||||
<li><a href="#remove-account">Jak mogę usunąć swój profil czatu?</a></li>
|
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#groups">Grupy</a>
|
<li><a href="#groups">Groups</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#tworzenie-grupy">Tworzenie grupy</a></li>
|
<li><a href="#tworzenie-grupy">Tworzenie grupy</a></li>
|
||||||
<li><a href="#addmembers">Dodawanie i usuwanie członków</a></li>
|
<li><a href="#addmembers">Add and remove members</a></li>
|
||||||
<li><a href="#usunąłem-się-przez-przypadek">Usunąłem się przez przypadek.</a></li>
|
<li><a href="#usunąłem-się-przez-przypadek">Usunąłem się przez przypadek.</a></li>
|
||||||
<li><a href="#nie-chcę-już-otrzymywać-wiadomości-od-grupy">Nie chcę już otrzymywać wiadomości od grupy.</a></li>
|
<li><a href="#nie-chcę-już-otrzymywać-wiadomości-od-grupy">Nie chcę już otrzymywać wiadomości od grupy.</a></li>
|
||||||
<li><a href="#klonowanie-grupy">Klonowanie grupy</a></li>
|
<li><a href="#cloning-a-group">Cloning a group</a></li>
|
||||||
<li><a href="#ilu-członków-może-należeć-do-jednej-grupy">Ilu członków może należeć do jednej grupy?</a></li>
|
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#channels">Kanały</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#subskrybowanie-kanału">Subskrybowanie kanału</a></li>
|
|
||||||
<li><a href="#tworzenie-kanału">Tworzenie kanału</a></li>
|
|
||||||
<li><a href="#ilu-subskrybentów-może-mieć-kanał">Ilu subskrybentów może mieć kanał?</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#calls">Połączenia</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#nawiązywanie-połączenia">Nawiązywanie połączenia</a></li>
|
|
||||||
<li><a href="#odbieranie-lub-odrzucanie-połączenia">Odbieranie lub odrzucanie połączenia</a></li>
|
|
||||||
<li><a href="#w-trakcie-połączenia">W trakcie połączenia</a></li>
|
|
||||||
<li><a href="#nieodebrane-połączenia-i-powiadomienia">Nieodebrane połączenia i powiadomienia</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#webxdc">In-chat apps</a>
|
<li><a href="#webxdc">In-chat apps</a>
|
||||||
@@ -122,67 +107,87 @@
|
|||||||
|
|
||||||
<p>Delta Chat to niezawodna, zdecentralizowana i bezpieczna aplikacja do błyskawicznego przesyłania wiadomości, dostępna na platformy mobilne i stacjonarne.</p>
|
<p>Delta Chat to niezawodna, zdecentralizowana i bezpieczna aplikacja do błyskawicznego przesyłania wiadomości, dostępna na platformy mobilne i stacjonarne.</p>
|
||||||
|
|
||||||
<p>Natychmiastowe tworzenie <strong>prywatnych profili czatu</strong> z bezpiecznymi i interoperacyjnymi <a href="https://chatmail.at/relays">przekaźnikami chatmail</a>, które oferują natychmiastowe dostarczanie wiadomości oraz powiadomienia push dla urządzeń z systemem iOS i Android.</p>
|
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<p>Wszechstronna obsługa <a href="#multiple-accounts">wielu profili</a> i <a href="#multiclient">wielu urządzeń</a> na wszystkich platformach i między różnymi <a href="https://chatmail.at/clients">aplikacjami chatmail</a>.</p>
|
<p>Instant creation of <strong>private chat profiles</strong>
|
||||||
|
with secure and interoperable <a href="https://chatmail.at/relays">chatmail relays</a>
|
||||||
|
that offer instant message delivery, and Push Notifications for iOS and Android devices.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Interaktywne <a href="#webxdc">aplikacje do czatu</a> w grach i do współpracy</p>
|
<p>Pervasive <a href="#multiple-accounts">multi-profile</a> and
|
||||||
|
<a href="#multiclient">multi-device</a> support on all platforms
|
||||||
|
and between different <a href="https://chatmail.at/clients">chatmail apps</a>.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p><a href="#security-audits">Audytowne szyfrowanie end-to-end</a> zabezpieczające przed atakami sieciowymi i serwerowymi.</p>
|
<p>Interactive <a href="#webxdc">in-chat apps</a> for gaming and collaboration</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Bezpłatne i otwartoźródłowe oprogramowanie zarówno po stronie aplikacji, jak i serwera, stworzone w oparciu o <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">standardy internetowe</a>.</p>
|
<p><a href="#security-audits">Audited end-to-end encryption</a>
|
||||||
|
safe against network and server attacks.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Free and Open Source software, both app and server side,
|
||||||
|
built on <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Internet Standards</a>.</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3 id="howtoe2ee">
|
<h3 id="howtoe2ee">
|
||||||
|
|
||||||
|
|
||||||
Jak znaleźć osoby do czatu? <a href="#howtoe2ee" class="anchor"></a>
|
How can I find people to chat with? <a href="#howtoe2ee" class="anchor"></a>
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Najpierw pamiętaj, że Delta Chat to prywatny komunikator. Nie ma możliwości publicznego wyszukiwania, sam decydujesz o swoich kontaktach.</p>
|
<p>First, note that Delta Chat is a private messenger.
|
||||||
|
There is no public discovery, <em>you</em> decide about your contacts.</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<p>Jeśli jesteś twarzą w twarz ze znajomym lub rodziną, dotknij ikony <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>kodu QR</strong> na ekranie głównym.
|
<p>If you are <strong>face to face</strong> with your friend or family,
|
||||||
Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą aplikacji Delta Chat.</p>
|
tap the <strong>QR Code</strong> icon <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" />
|
||||||
|
on the main screen.<br />
|
||||||
|
Ask your chat partner to <strong>scan</strong> the QR image
|
||||||
|
with their Delta Chat app.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Aby skonfigurować kontakt <strong>zdalny</strong>, na tym samym ekranie naciśnij „Kopiuj” lub „Udostępnij” i wyślij <strong>link zaproszenia</strong> za pośrednictwem innego prywatnego czatu.</p>
|
<p>For a <strong>remote</strong> contact setup,
|
||||||
|
from the same screen,
|
||||||
|
click “Copy” or “Share” and send the <strong>invite link</strong>
|
||||||
|
through another private chat.</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>Poczekaj, aż połączenie zostanie nawiązane.</p>
|
<p>Now wait while connection gets established.</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<p>Jeśli obie strony są online, wkrótce zobaczą czat i będą mogły bezpiecznie wysyłać wiadomości.</p>
|
<p>If both sides are online, they will soon see a chat
|
||||||
|
and can start messaging securely.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Jeśli jedna ze stron jest offline lub ma słaby zasięg, możliwość czatowania zostanie wstrzymana do czasu przywrócenia połączenia.</p>
|
<p>If one side is offline or in bad network,
|
||||||
|
the ability to chat is delayed until connectivity is restored.</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>Gratulacje! Teraz będziesz automatycznie korzystać z <a href="#e2ee">szyfrowania typu end-to-end</a> dla tego kontaktu. Jeśli dodacie się nawzajem do <a href="#groups">grup</a>, szyfrowanie typu end-to-end zostanie nawiązane między wszystkimi członkami.</p>
|
<p>Congratulations!
|
||||||
|
You now will automatically use <a href="#e2ee">end-to-end encryption</a> with this contact.
|
||||||
|
If you add each other to <a href="#groups">groups</a>, end-to-end encryption will be established among all members.</p>
|
||||||
|
|
||||||
<h3 id="dlaczego-czat-jest-oznaczony-jako-prośba">
|
<h3 id="why-is-a-chat-marked-as-request">
|
||||||
|
|
||||||
|
|
||||||
Dlaczego czat jest oznaczony jako „Prośba”? <a href="#dlaczego-czat-jest-oznaczony-jako-prośba" class="anchor"></a>
|
Why is a chat marked as “Request”? <a href="#why-is-a-chat-marked-as-request" class="anchor"></a>
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Ponieważ jest to prywatny komunikator, tylko znajomi i rodzina, którym <a href="#howtoe2ee">udostępnisz swój kod QR lub link zaproszenia</a>, mogą do ciebie pisać.</p>
|
<p>As being a private messenger,
|
||||||
|
only friends and family you <a href="#howtoe2ee">share your QR code or invite link with</a> can write to you.</p>
|
||||||
|
|
||||||
<p>Twoi znajomi mogą udostępniać twoje dane kontaktowe innym znajomym, co jest oznaczone jako <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Prośba</b></p>
|
<p>Your friends may share your contact with other friends,
|
||||||
|
this appears as <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Request</b></p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
@@ -196,17 +201,19 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3 id="jak-mogę-skontaktować-ze-sobą-dwóch-znajomych">
|
<h3 id="how-can-i-put-two-of-my-friends-in-contact-with-each-other">
|
||||||
|
|
||||||
|
|
||||||
Jak mogę skontaktować ze sobą dwóch znajomych? <a href="#jak-mogę-skontaktować-ze-sobą-dwóch-znajomych" class="anchor"></a>
|
How can I put two of my friends in contact with each other? <a href="#how-can-i-put-two-of-my-friends-in-contact-with-each-other" class="anchor"></a>
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Dołącz pierwszy kontakt do czatu drugiego, używając przycisku <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Dołączania → Kontakt</strong>. Możesz również dodać krótką wiadomość powitalną.</p>
|
<p>Attach the first contact to the chat of the second using <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment Button → Contact</strong>.
|
||||||
|
You can also add a little introduction message.</p>
|
||||||
|
|
||||||
<p>Drugi kontakt otrzyma wtedy <strong>kartkę</strong> i może ją nacisnąć, aby rozpocząć czat z pierwszym kontaktem.</p>
|
<p>The second contact will receive a <strong>card</strong> then
|
||||||
|
and can tap it to start chatting with the first contact.</p>
|
||||||
|
|
||||||
<h3 id="multiple-accounts">
|
<h3 id="multiple-accounts">
|
||||||
|
|
||||||
@@ -216,13 +223,15 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Profil składa się z <strong>nazwy, zdjęcia</strong> i dodatkowych informacji służących do szyfrowania wiadomości. Profil jest dostępny tylko na twoim urządzeniu (urządzeniach) i korzysta z serwera wyłącznie do przekazywania wiadomości.</p>
|
<p>A profile is <strong>a name, a picture</strong> and some additional information for encrypting messages.
|
||||||
|
A profile lives on your device(s) only
|
||||||
|
and uses the server only to relay messages.</p>
|
||||||
|
|
||||||
<p>Podczas pierwszej instalacji Delta Chat tworzony jest pierwszy profil.</p>
|
<p>Podczas pierwszej instalacji Delta Chat tworzony jest pierwszy profil.</p>
|
||||||
|
|
||||||
<p>Później możesz dotknąć swojego zdjęcia profilowego w lewym górnym rogu, aby <strong>Dodać profile</strong> lub <strong>Przełączyć profile</strong>.</p>
|
<p>Później możesz dotknąć swojego zdjęcia profilowego w lewym górnym rogu, aby <strong>Dodać profile</strong> lub <strong>Przełączyć profile</strong>.</p>
|
||||||
|
|
||||||
<p>Możesz używać osobnych profili dla aktywności politycznych, rodzinnych lub zawodowych.</p>
|
<p>You may want to use separate profiles for political, family or work related activities.</p>
|
||||||
|
|
||||||
<p>Możesz także dowiedzieć się, <a href="#multiclient">jak używać tego samego profilu na wielu urządzeniach</a>.</p>
|
<p>Możesz także dowiedzieć się, <a href="#multiclient">jak używać tego samego profilu na wielu urządzeniach</a>.</p>
|
||||||
|
|
||||||
@@ -234,19 +243,22 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Możesz dodać zdjęcie profilowe w swoich ustawieniach. Jeśli napiszesz do swoich kontaktów lub dodasz je za pomocą kodu QR, automatycznie zobaczą je jako twoje zdjęcie profilowe.</p>
|
<p>Możesz dodać zdjęcie profilowe w swoich ustawieniach. Jeśli napiszesz do swoich kontaktów lub dodasz je za pomocą kodu QR, automatycznie zobaczą je jako Twoje zdjęcie profilowe.</p>
|
||||||
|
|
||||||
<p>Ze względów prywatności nikt nie widzi twojego zdjęcia profilowego, dopóki nie napiszesz do niego wiadomości.</p>
|
<p>Ze względów prywatności nikt nie widzi Twojego zdjęcia profilowego, dopóki nie napiszesz do niego wiadomości.</p>
|
||||||
|
|
||||||
<h3 id="signature">
|
<h3 id="signature">
|
||||||
|
|
||||||
|
|
||||||
Czy w Delta Chat mogę ustawić biografię/status? <a href="#signature" class="anchor"></a>
|
Can I set a Bio/Status with Delta Chat? <a href="#signature" class="anchor"></a>
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Tak, możesz to zrobić w <strong>Ustawieniach → Profil → Biografia</strong>. Po wysłaniu wiadomości do kontaktu zostanie ona wyświetlona, gdy będzie on przeglądał twoje dane kontaktowe.</p>
|
<p>Yes,
|
||||||
|
you can do so under <strong>Settings → Profile → Bio</strong>.
|
||||||
|
Once you sent a message to a contact,
|
||||||
|
they will see it when they view your contact details.</p>
|
||||||
|
|
||||||
<h3 id="co-oznacza-przypinanie-wyciszanie-i-archiwizowanie">
|
<h3 id="co-oznacza-przypinanie-wyciszanie-i-archiwizowanie">
|
||||||
|
|
||||||
@@ -266,7 +278,7 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
|||||||
<p><strong>Wycisz czaty</strong>, jeśli nie chcesz otrzymywać z nich powiadomień. Wyciszone czaty pozostają na swoim miejscu i możesz też przypiąć wyciszony czat.</p>
|
<p><strong>Wycisz czaty</strong>, jeśli nie chcesz otrzymywać z nich powiadomień. Wyciszone czaty pozostają na swoim miejscu i możesz też przypiąć wyciszony czat.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p><strong>Archiwizuj czaty</strong>, jeśli nie chcesz ich już widzieć na liście czatów. Pozostają dostępne nad listą czatów lub poprzez wyszukiwanie i są oznaczone jako <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Zarchiwizowane</b></p>
|
<p><strong>Archiwizuj czaty</strong>, jeśli nie chcesz ich już widzieć na liście czatów. Zarchiwizowane czaty pozostają dostępne nad listą czatów lub poprzez wyszukiwanie.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Gdy zarchiwizowany czat otrzyma nową wiadomość, o ile nie zostanie wyciszony, <strong>wyskoczy z archiwum</strong> i wróci na twoją listę czatów.
|
<p>Gdy zarchiwizowany czat otrzyma nową wiadomość, o ile nie zostanie wyciszony, <strong>wyskoczy z archiwum</strong> i wróci na twoją listę czatów.
|
||||||
@@ -297,7 +309,7 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
|||||||
<p>Później otwórz czat „Zapisane wiadomości” — zobaczysz tam zapisane wiadomości. Naciskając <img style="vertical-align:middle; width:1.2em; margin:1px" src="../go-to-original.png" alt="ikona strzałki w prawo" />, możesz wrócić do oryginalnej wiadomości w oryginalnym czacie</p>
|
<p>Później otwórz czat „Zapisane wiadomości” — zobaczysz tam zapisane wiadomości. Naciskając <img style="vertical-align:middle; width:1.2em; margin:1px" src="../go-to-original.png" alt="ikona strzałki w prawo" />, możesz wrócić do oryginalnej wiadomości w oryginalnym czacie</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Na koniec możesz również użyć „Zapisanych wiadomości”, aby robić <strong>osobiste notatki</strong> — otwórz czat, wpisz coś, dodaj zdjęcie lub wiadomość głosową itp.</p>
|
<p>Na koniec możesz również użyć „Zapisz wiadomości”, aby robić <strong>osobiste notatki</strong> — otwórz czat, wpisz coś, dodaj zdjęcie lub wiadomość głosową itp.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Ponieważ „Zapisane wiadomości” są zsynchronizowane, mogą być bardzo przydatne do przesyłania danych między urządzeniami</p>
|
<p>Ponieważ „Zapisane wiadomości” są zsynchronizowane, mogą być bardzo przydatne do przesyłania danych między urządzeniami</p>
|
||||||
@@ -314,9 +326,13 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Czasami można zobaczyć <strong>zieloną kropkę</strong> <img style="vertical-align:middle; width:1.2em; margin:1px" src="../green-dot.png" alt="" /> obok awatara kontaktu. Oznacza to, że był on <strong>niedawno widziany przez ciebie</strong> w ciągu ostatnich 10 minut, np. wysłał ci wiadomość lub potwierdzenie odczytu.</p>
|
<p>You can sometimes see a <strong>green dot</strong> <img style="vertical-align:middle; width:1.2em; margin:1px" src="../green-dot.png" alt="" />
|
||||||
|
next to the avatar of a contact.
|
||||||
|
It means they were <strong>recently seen by you</strong> in the last 10 minutes,
|
||||||
|
e.g. because they messaged you or sent a read receipt.</p>
|
||||||
|
|
||||||
<p>Nie jest to więc status online w czasie rzeczywistym i inni również nie zawsze zobaczą, że jesteś „online”.</p>
|
<p>So this is not a real time online status
|
||||||
|
and others will as well not always see that you are “online”.</p>
|
||||||
|
|
||||||
<h3 id="co-oznaczają-znaczniki-wyświetlane-obok-wiadomości-wychodzących">
|
<h3 id="co-oznaczają-znaczniki-wyświetlane-obok-wiadomości-wychodzących">
|
||||||
|
|
||||||
@@ -328,16 +344,19 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<p><strong>Jeden znacznik</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick1.png" alt="" /> oznacza, że wiadomość została pomyślnie wysłana do <a href="#relays">przekaźnika</a>.</p>
|
<p><strong>One tick</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick1.png" alt="" />
|
||||||
|
means that the message was sent successfully to the <a href="#relays">relay</a>.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p><strong>Dwa znaczniki</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick2.png" alt="" /> oznaczają, że twój kontakt przeczytał wiadomość.</p>
|
<p><strong>Two ticks</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick2.png" alt="" />
|
||||||
|
indicate your contact has read the message.</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>W <a href="#groups">grupach</a> drugi znacznik oznacza, że co najmniej jeden członek potwierdził przeczytanie wiadomości.</p>
|
<p>In <a href="#groups">groups</a> the second tick means that at least one member has reported back having read the message.</p>
|
||||||
|
|
||||||
<p>Drugi znacznik pojawi się tylko wtedy, gdy ty i jeden z odbiorców, którzy przeczytali wiadomość, macie włączoną opcję <strong>Ustawienia → Czaty → Potwierdzenie odczytu</strong>.</p>
|
<p>You will only get the second tick if both you and one of the recipients who read the message
|
||||||
|
has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
|
||||||
|
|
||||||
<h3 id="edit">
|
<h3 id="edit">
|
||||||
|
|
||||||
@@ -363,22 +382,26 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
|||||||
<h3 id="mediaquality">
|
<h3 id="mediaquality">
|
||||||
|
|
||||||
|
|
||||||
Jak obsługiwana jest jakość multimediów? <a href="#mediaquality" class="anchor"></a>
|
How is media quality handled? <a href="#mediaquality" class="anchor"></a>
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Obrazy, filmy, pliki, wiadomości głosowe itp. można wysyłać za pomocą przycisków: <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Załącz</strong> lub <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /><strong>Wiadomość głosowa</strong>.</p>
|
<p>Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attach-</strong>
|
||||||
|
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons.</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<p>Domyślnie kompresja zapewnia <strong>szybką i wydajną dostawę</strong>, respektując limity danych i pamięci wszystkich użytkowników. Jest to idealne rozwiązanie do codziennej komunikacji.</p>
|
<p>By default, compression ensures <strong>fast, efficient delivery</strong> that respects everyone’s data limits and storage.
|
||||||
|
This is ideal for everyday communication.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>W regionach o słabszej łączności można wybrać wyższą kompresję w <strong>Ustawieniach → Czaty → Jakość mediów wychodzących</strong>.</p>
|
<p>In regions with worse connectivity,
|
||||||
|
you can choose higher compression at <strong>Settings → Chats → Outgoing Media Quality</strong>.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Jeśli chcesz wysłać multimedia w <strong>oryginalnej jakości</strong>, użyj w czacie opcji <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Załącz → Plik</strong>. Używaj tej metody oszczędnie, ponieważ wysyłanie oryginalnych plików znacznie zwiększy zużycie danych przez ciebie i wszystkich odbiorców na czacie.</p>
|
<p>If you specifically need to send media in its <strong>original quality</strong>, use <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attach → File</strong> in the chat.
|
||||||
|
Please use this method sparingly, as sending original files will significantly increase data usage for you and all recipients in the chat.</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@@ -392,11 +415,21 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
|||||||
|
|
||||||
<p>Możesz włączyć „znikające wiadomości” w ustawieniach czatu, w prawym górnym rogu okna czatu, wybierając przedział czasu od 5 minut do 1 roku.</p>
|
<p>Możesz włączyć „znikające wiadomości” w ustawieniach czatu, w prawym górnym rogu okna czatu, wybierając przedział czasu od 5 minut do 1 roku.</p>
|
||||||
|
|
||||||
<p>Dopóki ustawienie nie zostanie ponownie wyłączone, aplikacja Delta Chat u każdego członka czatu zajmie się usuwaniem wiadomości po wybranym okresie. Przedział czasu rozpoczyna się w momencie, gdy odbiorca po raz pierwszy zobaczy wiadomość w Delta Chat. Wiadomości są usuwane zarówno na serwerze, jak i w samej aplikacji.</p>
|
<p>Until the setting is turned off again,
|
||||||
|
each chat member’s Delta Chat app takes care
|
||||||
|
of deleting the messages
|
||||||
|
after the selected time span.
|
||||||
|
The time span begins
|
||||||
|
when the receiver first sees the message in Delta Chat.
|
||||||
|
The messages are deleted both,
|
||||||
|
on the servers,
|
||||||
|
and in the apps itself.</p>
|
||||||
|
|
||||||
<p>Pamiętaj, że na znikających wiadomościach możesz polegać tylko wtedy, gdy ufasz swoim partnerom czatu; złośliwi partnerzy czatu mogą robić zdjęcia lub w inny sposób zapisywać, kopiować lub przesyłać dalej wiadomości przed usunięciem.</p>
|
<p>Pamiętaj, że na znikających wiadomościach możesz polegać tylko wtedy, gdy ufasz swoim partnerom czatu; złośliwi partnerzy czatu mogą robić zdjęcia lub w inny sposób zapisywać, kopiować lub przesyłać dalej wiadomości przed usunięciem.</p>
|
||||||
|
|
||||||
<p>Poza tym, jeśli jeden z uczestników czatu odinstaluje aplikację Delta Chat, usunięcie (i tak zaszyfrowanych) wiadomości z jego serwera może potrwać dłużej.</p>
|
<p>Apart from that,
|
||||||
|
if one chat partner uninstalls Delta Chat,
|
||||||
|
the (anyway encrypted) messages may take longer to get deleted from their server.</p>
|
||||||
|
|
||||||
<h3 id="delold">
|
<h3 id="delold">
|
||||||
|
|
||||||
@@ -406,35 +439,46 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Jeśli chcesz zaoszczędzić miejsce na swoim urządzeniu, możesz wybrać opcję automatycznego usuwania starych wiadomości.</p>
|
<p>Jeśli chcesz zaoszczędzić miejsce na urządzeniu, możesz wybrać opcję automatycznego usuwania starych wiadomości.</p>
|
||||||
|
|
||||||
<p>Aby ją włączyć, przejdź do <strong>Ustawienia → Czaty → Usuń wiadomości z urządzenia</strong> . Możesz ustawić przedział czasowy pomiędzy „po 1 godzinie” a „po 1 roku”; w ten sposób <em>wszystkie</em> wiadomości zostaną usunięte z urządzenia, gdy tylko staną się starsze.</p>
|
<p>Aby ją włączyć, przejdź do „Usuń wiadomości z urządzenia” w ustawieniach w sekcji „Czaty i media”. Możesz ustawić przedział czasowy pomiędzy „po 1 godzinie” a „po 1 roku”; w ten sposób <em>wszystkie</em> wiadomości zostaną usunięte z urządzenia, gdy tylko staną się starsze.</p>
|
||||||
|
|
||||||
<h3 id="remove-account">
|
<h3 id="remove-account">
|
||||||
|
|
||||||
|
|
||||||
Jak mogę usunąć swój profil czatu? <a href="#remove-account" class="anchor"></a>
|
How can I delete my chat profile? <a href="#remove-account" class="anchor"></a>
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Jeśli używasz więcej niż jednego profilu czatu, możesz usunąć pojedyncze profile w górnym menu przełączania profili (na Androidzie i iOS) lub w pasku bocznym, klikając prawym przyciskiem myszy (w aplikacji na komputery). Profile czatu są usuwane tylko na urządzeniu, na którym nastąpiło usunięcie. Profile czatu na innych urządzeniach będą nadal w pełni działać.</p>
|
<p>If you are using more than one chat profile,
|
||||||
|
you can remove single ones in the top profile switcher menu (on Android and iOS),
|
||||||
|
or in the sidebar with a right click (in the Desktop app).
|
||||||
|
Chat profiles are only removed on the device where deletion was triggered.
|
||||||
|
Chat profiles on other devices will continue to fully function.</p>
|
||||||
|
|
||||||
<p>Jeśli używasz jednego domyślnego profilu czatu, możesz po prostu odinstalować aplikację. Spowoduje to automatyczne usunięcie wszystkich powiązanych danych adresowych na serwerze czatu. Aby uzyskać więcej informacji, zapoznaj się z informacjami o <a href="https://nine.testrun.org/info.html#account-deletion">usuwaniu adresów na stronie nine.testrun.org</a> lub odpowiednią stroną wybranego <a href="https://chatmail.at/relays">serwera czatu innej firmy</a>.</p>
|
<p>If you use a single default chat profile you can simply uninstall the app.
|
||||||
|
This will still automatically trigger deletion of all associated address data on the chatmail server.
|
||||||
|
For more info, please refer to <a href="https://nine.testrun.org/info.html#account-deletion">nine.testrun.org address-deletion</a>
|
||||||
|
or the respective page from your chosen <a href="https://chatmail.at/relays">3rd party chatmail server</a>.</p>
|
||||||
|
|
||||||
<h2 id="groups">
|
<h2 id="groups">
|
||||||
|
|
||||||
|
|
||||||
Grupy <a href="#groups" class="anchor"></a>
|
Groups <a href="#groups" class="anchor"></a>
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p>Grupy pozwalają kilku osobom na prywatną rozmowę na <strong>równych prawach</strong>.</p>
|
<p>Groups let several people chat together privately with <strong>equal rights</strong>.</p>
|
||||||
|
|
||||||
<p>Każdy może zmienić nazwę grupy lub awatar, <a href="#addmembers">dodawać lub usuwać członków</a>, ustawiać <a href="#ephemeralmsgs">znikające wiadomości</a> oraz <a href="#edit">usuwać własne wiadomości</a> z urządzeń wszystkich członków.</p>
|
<p>Anyone can
|
||||||
|
change the group name or avatar,
|
||||||
|
<a href="#addmembers">add or remove members</a>,
|
||||||
|
set <a href="#ephemeralmsgs">disappearing messages</a>,
|
||||||
|
and <a href="#edit">delete their own messages</a> from all member’s devices.</p>
|
||||||
|
|
||||||
<p>Ponieważ wszyscy członkowie mają te same uprawnienia, grupy najlepiej sprawdzają się w gronie <strong>zaufanych przyjaciół i rodziny</strong>.</p>
|
<p>Because all members have the same rights, groups work best among <strong>trusted friends and family</strong>.</p>
|
||||||
|
|
||||||
<h3 id="tworzenie-grupy">
|
<h3 id="tworzenie-grupy">
|
||||||
|
|
||||||
@@ -449,37 +493,43 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
|||||||
<p>Wybierz <strong>Nowy czat</strong>, a następnie <strong>Nowa grupa</strong> z menu w prawym górnym rogu lub naciśnij odpowiedni przycisk na Androidzie / iOS.</p>
|
<p>Wybierz <strong>Nowy czat</strong>, a następnie <strong>Nowa grupa</strong> z menu w prawym górnym rogu lub naciśnij odpowiedni przycisk na Androidzie / iOS.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Na następnym ekranie wybierz <strong>członków grupy</strong> i zdefiniuj <strong>nazwę grupy</strong>. Możesz też wybrać <strong>awatar grupy</strong>.</p>
|
<p>Na następnym ekranie wybierz <strong>członków grupy</strong> i zdefiniuj <strong>nazwę grupy</strong>. Możesz też wybrać awatar <strong>grupy</strong>.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Gdy tylko napiszesz <strong>pierwszą wiadomość</strong> w grupie, wszyscy członkowie zostaną poinformowani o nowej grupie i będą mogli odpowiadać w grupie (dopóki nie napiszesz wiadomości w grupie, grupa będzie niewidoczna dla członków).</p>
|
<p>Zaraz po napisaniu pierwszej wiadomości w grupie wszyscy członkowie zostaną poinformowani o nowej grupie i mogą odpowiedzieć w grupie (jeżeli nie napiszesz wiadomości w grupie, grupa jest niewidoczna dla członków).</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3 id="addmembers">
|
<h3 id="addmembers">
|
||||||
|
|
||||||
|
|
||||||
Dodawanie i usuwanie członków <a href="#addmembers" class="anchor"></a>
|
Add and remove members <a href="#addmembers" class="anchor"></a>
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Wszyscy członkowie grupy mają <strong>takie same uprawnienia</strong>. Z tego powodu każdy może usunąć dowolnego członka lub dodać nowych.</p>
|
<p>All group members have the <strong>same rights</strong>.
|
||||||
|
For this reason, everyone can delete any member or add new ones.</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<p>Aby <strong>dodać lub usunąć członków</strong>, dotknij nazwę grupy na czacie i wybierz członka, którego chcesz dodać lub usunąć.</p>
|
<p>To <strong>add or delete members</strong>, tap the group name in the chat and select the member to add or remove.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Jeśli członek nie znajduje się jeszcze na twojej liście kontaktów, ale rozmawiasz z nim <strong>twarzą w twarz</strong>, na tym samym ekranie pokaż mu <strong>kod QR</strong>.
|
<p>If the member is not yet in your contact list, but <strong>face to face</strong> with you,
|
||||||
Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą aplikacji Delta Chat, dotykając <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> na ekranie głównym.</p>
|
from the same screen, show a <strong>QR code</strong>.<br />
|
||||||
|
Ask your chat partner to <strong>scan</strong> the QR image with their Delta Chat app by tapping
|
||||||
|
<img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> on the main screen.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Aby dodać członka <strong>zdalnie</strong>, naciśnij „Kopiuj” lub „Udostępnij” i wyślij <strong>link zaproszenia</strong> nowemu członkowi za pośrednictwem innego prywatnego czatu.</p>
|
<p>For a <strong>remote</strong> member addition,
|
||||||
|
click “Copy” or “Share” and send the <strong>invite link</strong>
|
||||||
|
through another private chat to the new member.</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>Kod QR i link zaproszenia można wykorzystać do dodania kilku członków. Ponieważ jednak grupy są <a href="#groups">przeznaczone dla zaufanych osób</a>, unikaj udostępniania ich publicznie.</p>
|
<p>QR code and invite link can be used to add several members.
|
||||||
|
However, since groups are <a href="#groups">meant for trusted people</a>, avoid sharing them publicly.</p>
|
||||||
|
|
||||||
<h3 id="usunąłem-się-przez-przypadek">
|
<h3 id="usunąłem-się-przez-przypadek">
|
||||||
|
|
||||||
@@ -489,7 +539,8 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Ponieważ nie jesteś członkiem grupy, nie możesz dodać siebie ponownie. Jednak nie ma problemu, po prostu poproś dowolnego członka grupy na normalnym czacie, aby dodał cię ponownie.</p>
|
<p>Ponieważ nie jesteś członkiem grupy, nie możesz dodać siebie ponownie.
|
||||||
|
Jednak nie ma problemu, po prostu poproś dowolnego członka grupy na normalnym czacie, aby dodał cię ponownie.</p>
|
||||||
|
|
||||||
<h3 id="nie-chcę-już-otrzymywać-wiadomości-od-grupy">
|
<h3 id="nie-chcę-już-otrzymywać-wiadomości-od-grupy">
|
||||||
|
|
||||||
@@ -504,203 +555,47 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
|||||||
Jeśli później będziesz chciał ponownie dołączyć do grupy, poproś innego członka grupy, aby dodał cię do grupy.</li>
|
Jeśli później będziesz chciał ponownie dołączyć do grupy, poproś innego członka grupy, aby dodał cię do grupy.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>Alternatywnie możesz też „Wyłączyć powiadomienia” dla grupy, dzięki temu otrzymasz wszystkie wiadomości i nadal będziesz mógł pisać, ale nie będziesz już powiadamiany o żadnych nowych wiadomościach.</p>
|
<p>Alternatywnie możesz też „Wyłączyć powiadomienia” dla grupy dzięki temu otrzymasz wszystkie wiadomości i
|
||||||
|
nadal będziesz mógł pisać, ale nie będziesz już powiadamiany o żadnych nowych wiadomościach.</p>
|
||||||
|
|
||||||
<h3 id="klonowanie-grupy">
|
<h3 id="cloning-a-group">
|
||||||
|
|
||||||
|
|
||||||
Klonowanie grupy <a href="#klonowanie-grupy" class="anchor"></a>
|
Cloning a group <a href="#cloning-a-group" class="anchor"></a>
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Możesz zduplikować grupę, aby rozpocząć osobną dyskusję lub wykluczyć członków bez ich wiedzy.</p>
|
<p>You can duplicate a group to start a separate discussion
|
||||||
|
or to exclude members without them noticing.</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<p>Otwórz profil grupy i dotknij opcji <strong>Klonuj czat</strong> (Android/iOS) lub kliknij prawym przyciskiem myszy grupę na liście czatów (komputer).</p>
|
<p>Open the group profile and tap <strong>Clone Chat</strong> (Android/iOS),
|
||||||
|
or right-click the group in the chat list (Desktop).</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Ustaw nową nazwę, wybierz awatar i w razie potrzeby dostosuj listę członków.</p>
|
<p>Set a new name, choose an avatar, and adjust the member list if needed.</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>Nowa grupa jest <strong>w pełni niezależna</strong> od oryginalnej, która nadal działa jak dotychczas.</p>
|
<p>The new group is <strong>fully independent</strong> from the original,
|
||||||
|
which continues to work as before.</p>
|
||||||
|
|
||||||
<h3 id="ilu-członków-może-należeć-do-jednej-grupy">
|
<h3 id="how-many-members-can-participate-in-a-single-group">
|
||||||
|
|
||||||
|
|
||||||
Ilu członków może należeć do jednej grupy? <a href="#ilu-członków-może-należeć-do-jednej-grupy" class="anchor"></a>
|
How many members can participate in a single group? <a href="#how-many-members-can-participate-in-a-single-group" class="anchor"></a>
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Nie ma ścisłego limitu technicznego, ale nie zaleca się przekraczania 150 osób.</p>
|
<p>There is no strict technical limit,
|
||||||
|
but more than 150 is not recommended.</p>
|
||||||
|
|
||||||
<p>W miarę jak grupy się rozrastają, mogą stawać się niestabilne społecznie i wymagać hierarchii – gdzie Delta Chat pełni rolę prywatnego komunikatora do czatowania na <a href="#groups">równych prawach</a>. Więcej informacji znajdziesz w artykule <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Liczba Dunbara</a>.</p>
|
<p>As groups get larger, they can become socially unstable and may need a hierarchy -
|
||||||
|
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
||||||
<h2 id="channels">
|
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
||||||
|
|
||||||
|
|
||||||
Kanały <a href="#channels" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Kanały to narzędzie typu jeden do wielu, służące do nadawania wiadomości.</p>
|
|
||||||
|
|
||||||
<h3 id="subskrybowanie-kanału">
|
|
||||||
|
|
||||||
|
|
||||||
Subskrybowanie kanału <a href="#subskrybowanie-kanału" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Zeskanuj <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>kod QR</strong> lub naciśnij link zaproszenia otrzymany od właściciela kanału.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>To wszystko! Otrzymasz kilka wiadomości z historii kanału, a od tego momentu wszystkie nowe wiadomości z kanału.</p>
|
|
||||||
|
|
||||||
<p><strong>Nie martw się</strong>, jeśli to nie nastąpi od razu. Gdy właściciel kanału będzie online, twoja prośba o dołączenie zostanie przetworzona.</p>
|
|
||||||
|
|
||||||
<p>Podobnie jak cała platforma Delta Chat, również kanały są prywatne i zdecentralizowane, dlatego nie ma możliwości publicznego ujawnienia.</p>
|
|
||||||
|
|
||||||
<p>Inni subskrybenci kanału nie zobaczą, że go subskrybujesz i nie będą mogli wysyłać ci wiadomości. Właściciel kanału może jednak napisać do ciebie. Zobaczy również, że przeczytałeś wiadomość, chyba że masz wyłączone potwierdzenia odczytu.</p>
|
|
||||||
|
|
||||||
<p>Jeśli nie chcesz udostępniać swojego głównego profilu, możesz również utworzyć <a href="#multiple-accounts">dedykowany profil</a> do dołączenia do kanału.</p>
|
|
||||||
|
|
||||||
<h3 id="tworzenie-kanału">
|
|
||||||
|
|
||||||
|
|
||||||
Tworzenie kanału <a href="#tworzenie-kanału" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Naciśnij <strong>Nowy cza</strong> i wybierz <strong>Nowy kanał</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Wprowadź <strong>nazwę</strong>, opcjonalnie ustaw <strong>obraz</strong> i <strong>opis</strong>, a następnie naciśnij przycisk <strong>Utwórz</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Możesz teraz wysyłać i zarządzać wiadomościami jak zwykle.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Z profilu kanału <strong>udostępnij kod QR lub link zaproszenia innym osobom</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Subskrybenci będą otrzymywać twoje wiadomości, ale nie będą mogli wysyłać wiadomości na twoim kanale. Po zasubskrybowaniu otrzymają <strong>kilka najnowszych wiadomości z historii kanału</strong>.</p>
|
|
||||||
|
|
||||||
<p>Obok każdej wiadomości jest widoczna <strong>liczba wyświetleń</strong>. Pamiętaj, że uwzględnia ona tylko subskrybentów z włączonymi potwierdzeniami odczytu, więc rzeczywista liczba wyświetleń może być wyższa.</p>
|
|
||||||
|
|
||||||
<h3 id="ilu-subskrybentów-może-mieć-kanał">
|
|
||||||
|
|
||||||
|
|
||||||
Ilu subskrybentów może mieć kanał? <a href="#ilu-subskrybentów-może-mieć-kanał" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>Kanały są przeznaczone dla znacznie większej publiczności niż <a href="#groups">grupy</a>.</p>
|
|
||||||
|
|
||||||
<p>Praktyczny limit zależy od używanego <a href="#relays">przekaźnika</a>, więc nie ma jednej, stałej liczby, która obowiązywałaby wszędzie.</p>
|
|
||||||
|
|
||||||
<p>W przypadku naprawdę dużych kanałów z dziesiątkami tysięcy subskrybentów zalecamy użycie <a href="#multiple-accounts">dedykowanego profilu</a> dla kanału i sprawdzenie, czy przekaźnik jest odpowiedni.</p>
|
|
||||||
|
|
||||||
<p>Ale nie wahaj się zbytnio: Delta Chat został zaprojektowany tak, aby nie był zależny od przekaźnika, więc możesz go łatwo zmienić w dowolnym momencie – twoi obecni subskrybenci nawet tego nie zauważą. W takim przypadku wystarczy zaktualizować link zaproszenia udostępniany nowym subskrybentom.</p>
|
|
||||||
|
|
||||||
<h2 id="calls">
|
|
||||||
|
|
||||||
|
|
||||||
Połączenia <a href="#calls" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Delta Chat umożliwia indywidualne <strong>połączenia audio i wideo</strong>.</p>
|
|
||||||
|
|
||||||
<p>Połączenia są obsługiwane na komputerach stacjonarnych, Ubuntu Touch, iOS oraz Androidzie 8 i nowszych.</p>
|
|
||||||
|
|
||||||
<h3 id="nawiązywanie-połączenia">
|
|
||||||
|
|
||||||
|
|
||||||
Nawiązywanie połączenia <a href="#nawiązywanie-połączenia" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>W czacie indywidualnym dotknij <strong>ikony połączenia</strong> 📞.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Otworzy się małe menu, w którym możesz wybrać, czy chcesz nawiązać <strong>połączenie audio</strong>, czy <strong>wideo</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="odbieranie-lub-odrzucanie-połączenia">
|
|
||||||
|
|
||||||
|
|
||||||
Odbieranie lub odrzucanie połączenia <a href="#odbieranie-lub-odrzucanie-połączenia" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Gdy ktoś do ciebie dzwoni, Delta Chat wyświetla <strong>ekran połączenia przychodzącego</strong> lub powiadomienie.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Dotknij <strong>Akceptuj</strong>, aby odebrać, lub <strong>Odrzuć</strong>, aby odrzucić połączenie.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="w-trakcie-połączenia">
|
|
||||||
|
|
||||||
|
|
||||||
W trakcie połączenia <a href="#w-trakcie-połączenia" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Możesz <strong>wyciszyć</strong> mikrofon.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Możesz <strong>włączyć lub wyłączyć kamerę</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Na urządzeniach mobilnych możesz <strong>przełączać się między przednią i tylną kamerą</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>W zależności od urządzenia możesz również wybrać wyjście audio lub skorzystać z trybu obrazu w obrazie. Na komputerach stacjonarnych połączenie jest wyświetlane w dedykowanym oknie i możesz kontynuować korzystanie z głównego okna Delta Chat jak zwykle.</p>
|
|
||||||
|
|
||||||
<h3 id="nieodebrane-połączenia-i-powiadomienia">
|
|
||||||
|
|
||||||
|
|
||||||
Nieodebrane połączenia i powiadomienia <a href="#nieodebrane-połączenia-i-powiadomienia" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Jeśli nie odbierzesz, nie usłyszysz dzwonka lub nie będziesz mieć urządzenia pod ręką, połączenie zostanie oznaczone jako <strong>nieodebrane</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Tylko zaakceptowane przez ciebie kontakty</strong> mogą wywołać dzwonek na urządzeniu. Prośby o kontakt będą wyświetlane normalnie i nie będą dzwonić.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>W <strong>Ustawienia → Powiadomienia → Połączenia</strong> możesz całkowicie wyłączyć specjalny ekran dzwonka. Jeśli to zrobisz, nie będzie ci przeszkadzał żaden dzwonek, a połączenie nadal będzie można odebrać, dotykając w czacie dymku wiadomości o przychodzącym połączeniu.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="webxdc">
|
<h2 id="webxdc">
|
||||||
|
|
||||||
@@ -954,7 +849,8 @@ Welcome to the power of the interoperable chatmail relay network :)</p>
|
|||||||
<p>Sprawdź dokładnie, czy oba urządzenia są w tym <strong>samym Wi-Fi lub tej samej sieci</strong></p>
|
<p>Sprawdź dokładnie, czy oba urządzenia są w tym <strong>samym Wi-Fi lub tej samej sieci</strong></p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Na <strong>Windowsie</strong>, przejdź do „Panel sterowania / Sieć i internet” i upewnij się, że <strong>Sieć prywatna</strong> jest wybrana jako „Typ profilu sieci” (po przeniesieniu możesz wrócić do pierwotnej wartości)</p>
|
<p>Na <strong>Windowsie</strong>, przejdź do “Panel sterowania / Sieć i internet” i upewnij się, że <strong>Sieć prywatna</strong> jest wybrana jako “Typ profilu sieci”
|
||||||
|
(po przeniesieniu możesz wrócić do pierwotnej wartości)</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>W systemie <strong>iOS</strong> upewnij się, że jest przydzielony dostęp do opcji „Ustawienia » Aplikacje » Delta Chat » <strong>Sieć lokalna</strong>”</p>
|
<p>W systemie <strong>iOS</strong> upewnij się, że jest przydzielony dostęp do opcji „Ustawienia » Aplikacje » Delta Chat » <strong>Sieć lokalna</strong>”</p>
|
||||||
@@ -998,14 +894,15 @@ Welcome to the power of the interoperable chatmail relay network :)</p>
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<p>Na starym urządzeniu przejdź do <strong>Ustawienia → Czaty → Eksport kopii zapasowej</strong>. Wprowadź swój PIN odblokowania ekranu, wzór lub hasło. Następnie możesz nacisnąć „Utwórz kopię”. Spowoduje to zapisanie pliku kopii zapasowej na urządzeniu. Teraz musisz jakoś przenieść go na inne urządzenie.</p>
|
<p>Na starym urządzeniu przejdź do <strong>Ustawienia → Czaty i media → Eksport kopii zapasowej</strong>. Wprowadź swój PIN odblokowania ekranu, wzór lub hasło. Następnie możesz nacisnąć „Utwórz kopię”. Spowoduje to zapisanie pliku kopii zapasowej na urządzeniu. Teraz musisz jakoś przenieść go na inne urządzenie.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Na nowym urządzeniu wybierz: <strong>Mam już profil → Przywróć z kopii zapasowej</strong>. Jeśli korzystasz z iOS i napotykasz trudności, może <a href="https://support.delta.chat/t/import-backup-to-ios/1628">ten poradnik</a> ci pomoże.</p>
|
<p>Na nowym urządzeniu, na ekranie logowania, zamiast logować się na swoje konto e-mail, wybierz <strong>Przywróć z kopii zapasowej</strong>. Po zaimportowaniu Twoje rozmowy, klucze szyfrujące i multimedia powinny zostać skopiowane na nowe urządzenie.
|
||||||
|
Jeśli korzystasz z iOS i napotykasz trudności, może <a href="https://support.delta.chat/t/import-backup-to-ios/1628">ten poradnik</a> Ci pomoże.</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>Jesteś teraz zsynchronizowany i w komunikacji ze swoimi partnerami możesz używać obu urządzeń do wysyłania i odbierania wiadomości zaszyfrowanych metodą end-to-end.</p>
|
<p>Jesteś teraz zsynchronizowany i możesz używać obu urządzeń do wysyłania i odbierania wiadomości zaszyfrowanych end-to-end w komunikacji ze swoimi partnerami.</p>
|
||||||
|
|
||||||
<h3 id="czy-są-jakieś-plany-wprowadzenia-klienta-web-delta-chat">
|
<h3 id="czy-są-jakieś-plany-wprowadzenia-klienta-web-delta-chat">
|
||||||
|
|
||||||
@@ -1392,20 +1289,23 @@ can not be identified easily.</p>
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
|
||||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
as well as sometimes your contact’s devices if you have a <a href="#experiments">call</a>
|
||||||
or use <a href="#webxdc">apps</a> together.</p>
|
or use <a href="#webxdc">apps</a> together.</p>
|
||||||
|
|
||||||
<p>IP Addresses are needed for connectivity and efficiency.
|
<p>IP Addresses are needed for connectivity and efficiency.
|
||||||
Delta Chat neither persists nor exposes them.
|
They are neither persisted nor exposed.
|
||||||
Note that IP Addresses
|
Note that the IP Address
|
||||||
are not like an address you give to a delivery service,
|
is not like a detailed address you give to a delivery service,
|
||||||
but typically less precise, often defining city or region only.</p>
|
but much more coarse, often defining region or country only.</p>
|
||||||
|
|
||||||
<p>If you see your IP Address as a risk,
|
<p>As this is just how the internet and other messengers work by default,
|
||||||
we recommend to use a VPN for the whole system.
|
we do not offer options here or ask upfront questions.</p>
|
||||||
Per-app options leave gaps across your system.
|
|
||||||
For example, tapping a link can expose IP Addresses to unknown parties, which is by far the larger risk.</p>
|
<p>If you see your IP Address as a security or privacy risk,
|
||||||
|
we recommend to use a VPN, in combination with system lockdown mode.
|
||||||
|
Hunting down options in all apps on your system will leave gaps.
|
||||||
|
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
|
||||||
|
|
||||||
<h3 id="sealedsender">
|
<h3 id="sealedsender">
|
||||||
|
|
||||||
|
|||||||
@@ -29,21 +29,6 @@
|
|||||||
<li><a href="#quantos-membros-podem-participar-em-um-único-grupo">Quantos membros podem participar em um único grupo?</a></li>
|
<li><a href="#quantos-membros-podem-participar-em-um-único-grupo">Quantos membros podem participar em um único grupo?</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#channels">Channels</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#subscribe-to-a-channel">Subscribe to a channel</a></li>
|
|
||||||
<li><a href="#create-a-channel">Create a channel</a></li>
|
|
||||||
<li><a href="#how-many-subscribers-can-a-channel-have">How many subscribers can a channel have?</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#calls">Calls</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#place-a-call">Place a call</a></li>
|
|
||||||
<li><a href="#accept-or-reject-a-call">Accept or reject a call</a></li>
|
|
||||||
<li><a href="#during-a-call">During a call</a></li>
|
|
||||||
<li><a href="#missed-calls-and-notifications">Missed calls and notifications</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#webxdc">Aplicativos embutidos</a>
|
<li><a href="#webxdc">Aplicativos embutidos</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#onde-posso-obter-aplicativos-embutidos">Onde posso obter aplicativos embutidos?</a></li>
|
<li><a href="#onde-posso-obter-aplicativos-embutidos">Onde posso obter aplicativos embutidos?</a></li>
|
||||||
@@ -637,197 +622,6 @@ mas não é recomendável mais de 150.</p>
|
|||||||
mas o Delta Chat é um mensageiro privado para conversar com <a href="#groups">direitos iguais</a>.
|
mas o Delta Chat é um mensageiro privado para conversar com <a href="#groups">direitos iguais</a>.
|
||||||
Consulte o <a href="https://pt.wikipedia.org/wiki/N%C3%BAmero_de_Dunbar">Número de Dunbar</a> para obter mais informações.</p>
|
Consulte o <a href="https://pt.wikipedia.org/wiki/N%C3%BAmero_de_Dunbar">Número de Dunbar</a> para obter mais informações.</p>
|
||||||
|
|
||||||
<h2 id="channels">
|
|
||||||
|
|
||||||
|
|
||||||
Channels <a href="#channels" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Channels are a one-to-many tool for broadcasting messages.</p>
|
|
||||||
|
|
||||||
<h3 id="subscribe-to-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Subscribe to a channel <a href="#subscribe-to-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Scan the <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>QR code</strong>
|
|
||||||
or tap the <strong>invite link</strong> you got from the channel owner.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>That’s all!
|
|
||||||
You will receive a few of the messages from the channel history
|
|
||||||
and, from that point on, all new messages from the channel.</p>
|
|
||||||
|
|
||||||
<p><strong>Don’t worry,</strong> if that does not happen immediately.
|
|
||||||
Once the channel owner comes online, your join request will be processed.</p>
|
|
||||||
|
|
||||||
<p>As all of Delta Chat, also Channels are private and decentralized,
|
|
||||||
there is no public discovery.</p>
|
|
||||||
|
|
||||||
<p>Other channel subscribers will not see that you subscribed and cannot message you.
|
|
||||||
The channel owner, however, can message you.
|
|
||||||
They will also see that you read a message unless you have read receipts disabled.</p>
|
|
||||||
|
|
||||||
<p>If you do not want to share your main profile,
|
|
||||||
you can also create a <a href="#multiple-accounts">dedicated profile</a> for joining a channel.</p>
|
|
||||||
|
|
||||||
<h3 id="create-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Create a channel <a href="#create-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>New Chat</strong> and choose <strong>New Channel</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Enter a <strong>name</strong>, optionally set an <strong>image</strong> and <strong>description</strong>, and hit the <strong>Create</strong> button.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can now send and manage messages as usual.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>From the channel’s profile, <strong>share the QR code or invite link with others</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Subscribers will receive your messages,
|
|
||||||
but they cannot send messages in your channel.
|
|
||||||
When subscribing, they will receive <strong>a few of the latest messages of the channel history</strong>.</p>
|
|
||||||
|
|
||||||
<p>You can see the <strong>view count</strong> beside each message.
|
|
||||||
Note that this only counts subscribers who have read receipts enabled,
|
|
||||||
so the real view count may be larger.</p>
|
|
||||||
|
|
||||||
<h3 id="how-many-subscribers-can-a-channel-have">
|
|
||||||
|
|
||||||
|
|
||||||
How many subscribers can a channel have? <a href="#how-many-subscribers-can-a-channel-have" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>Channels are designed for much larger audiences than <a href="#groups">groups</a>.</p>
|
|
||||||
|
|
||||||
<p>The practical limit depends on the used <a href="#relays">relay</a>,
|
|
||||||
so there is no single fixed number that applies everywhere.</p>
|
|
||||||
|
|
||||||
<p>For really large channels with several tens of thousands of subscribers,
|
|
||||||
we recommend using a <a href="#multiple-accounts">dedicated profile</a> for the channel
|
|
||||||
and checking whether the relay is suitable.</p>
|
|
||||||
|
|
||||||
<p>But don’t be too hesitant: Delta Chat is designed to be relay-agnostic,
|
|
||||||
so you can change your relay at any point easily -
|
|
||||||
your existing subscribers will not even notice.
|
|
||||||
You only have to update the invite link you share with new subscribers in that case.</p>
|
|
||||||
|
|
||||||
<h2 id="calls">
|
|
||||||
|
|
||||||
|
|
||||||
Calls <a href="#calls" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Delta Chat supports one-to-one <strong>audio calls</strong> and <strong>video calls</strong>.</p>
|
|
||||||
|
|
||||||
<p>Calls are supported on Desktop, Ubuntu Touch, iOS and Android 8 and newer.</p>
|
|
||||||
|
|
||||||
<h3 id="place-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Place a call <a href="#place-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>In a one-to-one chat, tap the 📞 <strong>call icon</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>This opens a small menu
|
|
||||||
where you can choose whether to place an <strong>Audio Call</strong> or a <strong>Video Call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="accept-or-reject-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Accept or reject a call <a href="#accept-or-reject-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>When someone calls you,
|
|
||||||
Delta Chat shows an <strong>incoming call screen</strong> or notification.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>Accept</strong> to answer
|
|
||||||
or <strong>Decline</strong> to reject the call.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="during-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
During a call <a href="#during-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>mute</strong> your microphone.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>enable or disable your camera</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>On mobile, you can <strong>switch between front and back cameras</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Depending on the device, you can also select the audio output or use picture-in-picture.
|
|
||||||
On desktop, the call is using a dedicated window
|
|
||||||
and you can continue using the main Delta Chat window as usual.</p>
|
|
||||||
|
|
||||||
<h3 id="missed-calls-and-notifications">
|
|
||||||
|
|
||||||
|
|
||||||
Missed calls and notifications <a href="#missed-calls-and-notifications" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>If you do not answer, do not hear the ringing, or do not have your device at hand,
|
|
||||||
the call appears as a <strong>missed call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Only your accepted contacts</strong> can make your device ring.
|
|
||||||
Contact requests will appear as usual and will not ring.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>At <strong>Settings → Notifications → Calls</strong>,
|
|
||||||
you can disable the special call ringing screen completely.
|
|
||||||
If you do so, you will not be disturbed by any ringing notification,
|
|
||||||
you can still pick up the call by tapping the incoming call message bubble in its chat.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="webxdc">
|
<h2 id="webxdc">
|
||||||
|
|
||||||
|
|
||||||
@@ -1626,20 +1420,23 @@ can not be identified easily.</p>
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
|
||||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
as well as sometimes your contact’s devices if you have a <a href="#experiments">call</a>
|
||||||
or use <a href="#webxdc">apps</a> together.</p>
|
or use <a href="#webxdc">apps</a> together.</p>
|
||||||
|
|
||||||
<p>IP Addresses are needed for connectivity and efficiency.
|
<p>IP Addresses are needed for connectivity and efficiency.
|
||||||
Delta Chat neither persists nor exposes them.
|
They are neither persisted nor exposed.
|
||||||
Note that IP Addresses
|
Note that the IP Address
|
||||||
are not like an address you give to a delivery service,
|
is not like a detailed address you give to a delivery service,
|
||||||
but typically less precise, often defining city or region only.</p>
|
but much more coarse, often defining region or country only.</p>
|
||||||
|
|
||||||
<p>If you see your IP Address as a risk,
|
<p>As this is just how the internet and other messengers work by default,
|
||||||
we recommend to use a VPN for the whole system.
|
we do not offer options here or ask upfront questions.</p>
|
||||||
Per-app options leave gaps across your system.
|
|
||||||
For example, tapping a link can expose IP Addresses to unknown parties, which is by far the larger risk.</p>
|
<p>If you see your IP Address as a security or privacy risk,
|
||||||
|
we recommend to use a VPN, in combination with system lockdown mode.
|
||||||
|
Hunting down options in all apps on your system will leave gaps.
|
||||||
|
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
|
||||||
|
|
||||||
<h3 id="sealedsender">
|
<h3 id="sealedsender">
|
||||||
|
|
||||||
|
|||||||
@@ -29,21 +29,6 @@
|
|||||||
<li><a href="#сколько-участников-может-быть-в-одной-группе">Сколько участников может быть в одной группе?</a></li>
|
<li><a href="#сколько-участников-может-быть-в-одной-группе">Сколько участников может быть в одной группе?</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#channels">Каналы</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#подписка-на-канал">Подписка на канал</a></li>
|
|
||||||
<li><a href="#создание-канала">Создание канала</a></li>
|
|
||||||
<li><a href="#какое-максимальное-количество-подписчиков-может-быть-у-канала">Какое максимальное количество подписчиков может быть у канала?</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#calls">Звонки</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#как-сделать-звонок">Как сделать звонок</a></li>
|
|
||||||
<li><a href="#принять-или-отклонить-вызов">Принять или отклонить вызов</a></li>
|
|
||||||
<li><a href="#во-время-звонка">Во время звонка</a></li>
|
|
||||||
<li><a href="#пропущенные-вызовы-и-уведомления">Пропущенные вызовы и уведомления</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#webxdc">Встроенные приложения чата</a>
|
<li><a href="#webxdc">Встроенные приложения чата</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#где-можно-найти-встроенные-приложения">Где можно найти встроенные приложения?</a></li>
|
<li><a href="#где-можно-найти-встроенные-приложения">Где можно найти встроенные приложения?</a></li>
|
||||||
@@ -637,197 +622,6 @@
|
|||||||
в то время как Delta Chat - это приватный мессенджер для общения на <a href="#groups">равных правах</a>.
|
в то время как Delta Chat - это приватный мессенджер для общения на <a href="#groups">равных правах</a>.
|
||||||
Смотрите <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">число Данбара</a> для более глубокого понимания.</p>
|
Смотрите <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">число Данбара</a> для более глубокого понимания.</p>
|
||||||
|
|
||||||
<h2 id="channels">
|
|
||||||
|
|
||||||
|
|
||||||
Каналы <a href="#channels" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Каналы представляют собой инструмент типа “один-ко-многим” для трансляции сообщений.</p>
|
|
||||||
|
|
||||||
<h3 id="подписка-на-канал">
|
|
||||||
|
|
||||||
|
|
||||||
Подписка на канал <a href="#подписка-на-канал" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Отсканируйте <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>QR-код</strong>
|
|
||||||
или нажмите на ссылку-приглашение, которую вы получили от владельца канала.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Всё готово!
|
|
||||||
Сначала вы получите несколько сообщений из истории канала,
|
|
||||||
а затем — все новые сообщения, поступающие в него.</p>
|
|
||||||
|
|
||||||
<p><strong>Не беспокойтесь,</strong> если это произойдет не сразу.
|
|
||||||
Как только владелец канала выйдет в сеть, ваш запрос на вступление будет обработан.</p>
|
|
||||||
|
|
||||||
<p>Как и весь Delta Chat, каналы являются приватными и децентрализованными,
|
|
||||||
поэтому возможность публичного поиска каналов отсутствует.</p>
|
|
||||||
|
|
||||||
<p>Другие подписчики канала не увидят факта вашей подписки и не смогут отправлять вам сообщения.
|
|
||||||
Однако, владелец канала сможет отправить вам сообщение.
|
|
||||||
Также он будет видеть, что вы прочитали сообщение, если только вы не отключите подтверждение о прочтении.</p>
|
|
||||||
|
|
||||||
<p>Если вы не хотите использовать свой основной профиль,
|
|
||||||
вы можете создать <a href="#multiple-accounts">специальный профиль</a> для подписки на канал.</p>
|
|
||||||
|
|
||||||
<h3 id="создание-канала">
|
|
||||||
|
|
||||||
|
|
||||||
Создание канала <a href="#создание-канала" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Нажмите <strong>Новый чат</strong> и выберите <strong>Новый канал</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Введите <strong>название</strong>, по желанию добавьте <strong>изображение</strong> и <strong>описание</strong>, а затем нажмите кнопку <strong>Создать</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Теперь вы можете отправлять сообщения и управлять ими в обычном режиме.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>В профиле канала вы можете <strong>поделиться QR-кодом или ссылкой-приглашением с другими пользователями</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Подписчики будут получать ваши сообщения,
|
|
||||||
но не смогут отправлять сообщения в вашем канале.
|
|
||||||
При подписке они получат <strong>несколько последних сообщений из истории канала</strong>.</p>
|
|
||||||
|
|
||||||
<p>Рядом с каждым сообщением вы можете увидеть <strong>количество просмотров</strong>.
|
|
||||||
Обратите внимание, что учитываются только те подписчики, у которых включены уведомления о прочтении,
|
|
||||||
поэтому реальное количество просмотров может быть больше.</p>
|
|
||||||
|
|
||||||
<h3 id="какое-максимальное-количество-подписчиков-может-быть-у-канала">
|
|
||||||
|
|
||||||
|
|
||||||
Какое максимальное количество подписчиков может быть у канала? <a href="#какое-максимальное-количество-подписчиков-может-быть-у-канала" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>Каналы предназначены для гораздо более широкой аудитории, чем <a href="#groups">группы</a>.</p>
|
|
||||||
|
|
||||||
<p>Практический предел зависит от используемого <a href="#relays">релея</a>,
|
|
||||||
поэтому не существует единого фиксированного значения, применимого во всех случаях.</p>
|
|
||||||
|
|
||||||
<p>Для крайне крупных каналов с десятками тысяч подписчиков,
|
|
||||||
мы рекомендуем использовать <a href="#multiple-accounts">специальный профиль</a> для управления каналом,
|
|
||||||
а также предварительно проверить пригодность релея.</p>
|
|
||||||
|
|
||||||
<p>Не стоит опасаться: архитектура Delta Chat позволяет использовать любой релей (relay-agnostic),
|
|
||||||
поэтому вы можете легко изменить его в любой момент -
|
|
||||||
ваши текущие подписчики этого даже не заметят.
|
|
||||||
В этом случае достаточно будет обновить ссылку-приглашение, которую вы передаёте новым пользователям.</p>
|
|
||||||
|
|
||||||
<h2 id="calls">
|
|
||||||
|
|
||||||
|
|
||||||
Звонки <a href="#calls" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Delta Chat поддерживает <strong>аудио-</strong> и <strong>видеозвонки</strong> в режиме “один-на-один”.</p>
|
|
||||||
|
|
||||||
<p>Звонки работают на ПК, Ubuntu Touch, iOS и Android версии 8 и новее.</p>
|
|
||||||
|
|
||||||
<h3 id="как-сделать-звонок">
|
|
||||||
|
|
||||||
|
|
||||||
Как сделать звонок <a href="#как-сделать-звонок" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>В чате “один-на-один” нажмите на 📞 <strong>значок вызова</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Откроется небольшое меню
|
|
||||||
в котором вы сможете выбрать вид связи <strong>аудио-</strong> или <strong>видеозвонок</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="принять-или-отклонить-вызов">
|
|
||||||
|
|
||||||
|
|
||||||
Принять или отклонить вызов <a href="#принять-или-отклонить-вызов" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>При входящем звонке,
|
|
||||||
Delta Chat показывает <strong>экран входящего вызова</strong> или уведомление.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Нажмите <strong>Принять</strong> чтобы ответить
|
|
||||||
или <strong>Отклонить</strong> чтобы сбросить звонок.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="во-время-звонка">
|
|
||||||
|
|
||||||
|
|
||||||
Во время звонка <a href="#во-время-звонка" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Вы можете <strong>отключить</strong> звук микрофона.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Вы можете <strong>включить или выключить камеру</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>На мобильных устройствах можно <strong>переключаться между фронтальной и основной камерами</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>В зависимости от устройства вы можете выбрать источник аудиовыхода или использовать режим “картинка в картинке”.
|
|
||||||
В приложении для ПК звонок осуществляется в отдельном окне,
|
|
||||||
что позволяет продолжать работу в основном окне Delta Chat в обычном режиме.</p>
|
|
||||||
|
|
||||||
<h3 id="пропущенные-вызовы-и-уведомления">
|
|
||||||
|
|
||||||
|
|
||||||
Пропущенные вызовы и уведомления <a href="#пропущенные-вызовы-и-уведомления" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Если вы не ответите на звонок, не услышите сигнал или ваше устройство будет недоступно,
|
|
||||||
вызов отобразится как <strong>пропущенный</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Только ваши подтвержденные контакты</strong> могут заставить ваше устройство звонить.
|
|
||||||
Запросы на добавление в контакты будут приходить как обычно, но вызова не будет.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>В разделе <strong>Настройки → Уведомления → Звонки</strong>,
|
|
||||||
вы можете полностью отключить специальный экран входящего вызова.
|
|
||||||
Если вы это сделаете, никакие уведомления о звонках не будут вас беспокоить;
|
|
||||||
при этом вы всё равно сможете принять звонок, нажав на иконку сообщения о входящем звонке в соответствующем чате.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="webxdc">
|
<h2 id="webxdc">
|
||||||
|
|
||||||
|
|
||||||
@@ -1624,20 +1418,25 @@ Delta Chat вместо этого использует реализацию Ope
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Используемым <a href="#relays">релеям</a> необходимо знать ваш IP-адрес,
|
<p>Используемый <a href="#relays">релей</a> должен знать ваш IP-адрес,
|
||||||
а в некоторых случаях — данные устройств ваших контактов, если вы совершаете <a href="#calls">вызов</a>
|
а также иногда устройства ваших контактов, если вы проводите совместные <a href="#experiments">звонки</a>
|
||||||
или совместно используете <a href="#webxdc">приложения</a>.</p>
|
или используете <a href="#webxdc">приложения</a>.</p>
|
||||||
|
|
||||||
<p>IP-адреса необходимы для обеспечения связи и эффективной работы.
|
<p>IP-адреса необходимы для обеспечения соединения и эффективности.
|
||||||
Delta Chat не сохраняет их и не раскрывает третьим лицам.
|
Они не сохраняются и не передаются третьим лицам.
|
||||||
Обратите внимание, что IP-адрес
|
Обратите внимание, что IP-адрес</p>
|
||||||
— это не тот же адрес, который вы указываете службе доставки,
|
<ul>
|
||||||
он, как правило, менее точен и зачастую позволяет определить лишь город или регион.</p>
|
<li>это не подробный адрес, который вы указываете службе доставки,
|
||||||
|
а скорее приблизительный, обычно определяющий регион или страну.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<p>Если вы считаете свой IP-адрес зоной риска,
|
<p>Поскольку именно так по умолчанию работает интернет и другие мессенджеры,
|
||||||
мы рекомендуем использовать VPN для всей системы.
|
мы не предлагаем здесь никаких настроек и не задаём предварительных вопросов</p>
|
||||||
Настройка VPN для отдельных приложений оставляет уязвимости в общей защите устройства.
|
|
||||||
Например, нажатие на ссылку может раскрыть ваш IP-адрес неизвестным сторонам, что представляет собой гораздо больший риск.</p>
|
<p>Если вы считаете свой IP-адрес угрозой безопасности или конфиденциальности,
|
||||||
|
мы рекомендуем использовать VPN в сочетании с режимом блокировки системы.
|
||||||
|
Поиск настроек во всех приложениях на вашем устройстве оставит уязвимости.
|
||||||
|
Например, нажатие на ссылку раскрывает IP-адрес неизвестным лицам и представляет собой гораздо больший риск в данном случае.</p>
|
||||||
|
|
||||||
<h3 id="sealedsender">
|
<h3 id="sealedsender">
|
||||||
|
|
||||||
|
|||||||
@@ -29,21 +29,6 @@
|
|||||||
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#channels">Channels</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#subscribe-to-a-channel">Subscribe to a channel</a></li>
|
|
||||||
<li><a href="#create-a-channel">Create a channel</a></li>
|
|
||||||
<li><a href="#how-many-subscribers-can-a-channel-have">How many subscribers can a channel have?</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#calls">Calls</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#place-a-call">Place a call</a></li>
|
|
||||||
<li><a href="#accept-or-reject-a-call">Accept or reject a call</a></li>
|
|
||||||
<li><a href="#during-a-call">During a call</a></li>
|
|
||||||
<li><a href="#missed-calls-and-notifications">Missed calls and notifications</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#webxdc">In-chat apps</a>
|
<li><a href="#webxdc">In-chat apps</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
||||||
@@ -642,197 +627,6 @@ but more than 150 is not recommended.</p>
|
|||||||
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
||||||
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
||||||
|
|
||||||
<h2 id="channels">
|
|
||||||
|
|
||||||
|
|
||||||
Channels <a href="#channels" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Channels are a one-to-many tool for broadcasting messages.</p>
|
|
||||||
|
|
||||||
<h3 id="subscribe-to-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Subscribe to a channel <a href="#subscribe-to-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Scan the <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>QR code</strong>
|
|
||||||
or tap the <strong>invite link</strong> you got from the channel owner.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>That’s all!
|
|
||||||
You will receive a few of the messages from the channel history
|
|
||||||
and, from that point on, all new messages from the channel.</p>
|
|
||||||
|
|
||||||
<p><strong>Don’t worry,</strong> if that does not happen immediately.
|
|
||||||
Once the channel owner comes online, your join request will be processed.</p>
|
|
||||||
|
|
||||||
<p>As all of Delta Chat, also Channels are private and decentralized,
|
|
||||||
there is no public discovery.</p>
|
|
||||||
|
|
||||||
<p>Other channel subscribers will not see that you subscribed and cannot message you.
|
|
||||||
The channel owner, however, can message you.
|
|
||||||
They will also see that you read a message unless you have read receipts disabled.</p>
|
|
||||||
|
|
||||||
<p>If you do not want to share your main profile,
|
|
||||||
you can also create a <a href="#multiple-accounts">dedicated profile</a> for joining a channel.</p>
|
|
||||||
|
|
||||||
<h3 id="create-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Create a channel <a href="#create-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>New Chat</strong> and choose <strong>New Channel</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Enter a <strong>name</strong>, optionally set an <strong>image</strong> and <strong>description</strong>, and hit the <strong>Create</strong> button.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can now send and manage messages as usual.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>From the channel’s profile, <strong>share the QR code or invite link with others</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Subscribers will receive your messages,
|
|
||||||
but they cannot send messages in your channel.
|
|
||||||
When subscribing, they will receive <strong>a few of the latest messages of the channel history</strong>.</p>
|
|
||||||
|
|
||||||
<p>You can see the <strong>view count</strong> beside each message.
|
|
||||||
Note that this only counts subscribers who have read receipts enabled,
|
|
||||||
so the real view count may be larger.</p>
|
|
||||||
|
|
||||||
<h3 id="how-many-subscribers-can-a-channel-have">
|
|
||||||
|
|
||||||
|
|
||||||
How many subscribers can a channel have? <a href="#how-many-subscribers-can-a-channel-have" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>Channels are designed for much larger audiences than <a href="#groups">groups</a>.</p>
|
|
||||||
|
|
||||||
<p>The practical limit depends on the used <a href="#relays">relay</a>,
|
|
||||||
so there is no single fixed number that applies everywhere.</p>
|
|
||||||
|
|
||||||
<p>For really large channels with several tens of thousands of subscribers,
|
|
||||||
we recommend using a <a href="#multiple-accounts">dedicated profile</a> for the channel
|
|
||||||
and checking whether the relay is suitable.</p>
|
|
||||||
|
|
||||||
<p>But don’t be too hesitant: Delta Chat is designed to be relay-agnostic,
|
|
||||||
so you can change your relay at any point easily -
|
|
||||||
your existing subscribers will not even notice.
|
|
||||||
You only have to update the invite link you share with new subscribers in that case.</p>
|
|
||||||
|
|
||||||
<h2 id="calls">
|
|
||||||
|
|
||||||
|
|
||||||
Calls <a href="#calls" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Delta Chat supports one-to-one <strong>audio calls</strong> and <strong>video calls</strong>.</p>
|
|
||||||
|
|
||||||
<p>Calls are supported on Desktop, Ubuntu Touch, iOS and Android 8 and newer.</p>
|
|
||||||
|
|
||||||
<h3 id="place-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Place a call <a href="#place-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>In a one-to-one chat, tap the 📞 <strong>call icon</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>This opens a small menu
|
|
||||||
where you can choose whether to place an <strong>Audio Call</strong> or a <strong>Video Call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="accept-or-reject-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Accept or reject a call <a href="#accept-or-reject-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>When someone calls you,
|
|
||||||
Delta Chat shows an <strong>incoming call screen</strong> or notification.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>Accept</strong> to answer
|
|
||||||
or <strong>Decline</strong> to reject the call.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="during-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
During a call <a href="#during-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>mute</strong> your microphone.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>enable or disable your camera</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>On mobile, you can <strong>switch between front and back cameras</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Depending on the device, you can also select the audio output or use picture-in-picture.
|
|
||||||
On desktop, the call is using a dedicated window
|
|
||||||
and you can continue using the main Delta Chat window as usual.</p>
|
|
||||||
|
|
||||||
<h3 id="missed-calls-and-notifications">
|
|
||||||
|
|
||||||
|
|
||||||
Missed calls and notifications <a href="#missed-calls-and-notifications" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>If you do not answer, do not hear the ringing, or do not have your device at hand,
|
|
||||||
the call appears as a <strong>missed call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Only your accepted contacts</strong> can make your device ring.
|
|
||||||
Contact requests will appear as usual and will not ring.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>At <strong>Settings → Notifications → Calls</strong>,
|
|
||||||
you can disable the special call ringing screen completely.
|
|
||||||
If you do so, you will not be disturbed by any ringing notification,
|
|
||||||
you can still pick up the call by tapping the incoming call message bubble in its chat.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="webxdc">
|
<h2 id="webxdc">
|
||||||
|
|
||||||
|
|
||||||
@@ -1631,20 +1425,23 @@ can not be identified easily.</p>
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
|
||||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
as well as sometimes your contact’s devices if you have a <a href="#experiments">call</a>
|
||||||
or use <a href="#webxdc">apps</a> together.</p>
|
or use <a href="#webxdc">apps</a> together.</p>
|
||||||
|
|
||||||
<p>IP Addresses are needed for connectivity and efficiency.
|
<p>IP Addresses are needed for connectivity and efficiency.
|
||||||
Delta Chat neither persists nor exposes them.
|
They are neither persisted nor exposed.
|
||||||
Note that IP Addresses
|
Note that the IP Address
|
||||||
are not like an address you give to a delivery service,
|
is not like a detailed address you give to a delivery service,
|
||||||
but typically less precise, often defining city or region only.</p>
|
but much more coarse, often defining region or country only.</p>
|
||||||
|
|
||||||
<p>If you see your IP Address as a risk,
|
<p>As this is just how the internet and other messengers work by default,
|
||||||
we recommend to use a VPN for the whole system.
|
we do not offer options here or ask upfront questions.</p>
|
||||||
Per-app options leave gaps across your system.
|
|
||||||
For example, tapping a link can expose IP Addresses to unknown parties, which is by far the larger risk.</p>
|
<p>If you see your IP Address as a security or privacy risk,
|
||||||
|
we recommend to use a VPN, in combination with system lockdown mode.
|
||||||
|
Hunting down options in all apps on your system will leave gaps.
|
||||||
|
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
|
||||||
|
|
||||||
<h3 id="sealedsender">
|
<h3 id="sealedsender">
|
||||||
|
|
||||||
|
|||||||
@@ -29,21 +29,6 @@
|
|||||||
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#channels">Channels</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#subscribe-to-a-channel">Subscribe to a channel</a></li>
|
|
||||||
<li><a href="#create-a-channel">Create a channel</a></li>
|
|
||||||
<li><a href="#how-many-subscribers-can-a-channel-have">How many subscribers can a channel have?</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#calls">Calls</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#place-a-call">Place a call</a></li>
|
|
||||||
<li><a href="#accept-or-reject-a-call">Accept or reject a call</a></li>
|
|
||||||
<li><a href="#during-a-call">During a call</a></li>
|
|
||||||
<li><a href="#missed-calls-and-notifications">Missed calls and notifications</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#webxdc">In-chat apps</a>
|
<li><a href="#webxdc">In-chat apps</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
||||||
@@ -642,197 +627,6 @@ but more than 150 is not recommended.</p>
|
|||||||
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
||||||
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
||||||
|
|
||||||
<h2 id="channels">
|
|
||||||
|
|
||||||
|
|
||||||
Channels <a href="#channels" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Channels are a one-to-many tool for broadcasting messages.</p>
|
|
||||||
|
|
||||||
<h3 id="subscribe-to-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Subscribe to a channel <a href="#subscribe-to-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Scan the <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>QR code</strong>
|
|
||||||
or tap the <strong>invite link</strong> you got from the channel owner.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>That’s all!
|
|
||||||
You will receive a few of the messages from the channel history
|
|
||||||
and, from that point on, all new messages from the channel.</p>
|
|
||||||
|
|
||||||
<p><strong>Don’t worry,</strong> if that does not happen immediately.
|
|
||||||
Once the channel owner comes online, your join request will be processed.</p>
|
|
||||||
|
|
||||||
<p>As all of Delta Chat, also Channels are private and decentralized,
|
|
||||||
there is no public discovery.</p>
|
|
||||||
|
|
||||||
<p>Other channel subscribers will not see that you subscribed and cannot message you.
|
|
||||||
The channel owner, however, can message you.
|
|
||||||
They will also see that you read a message unless you have read receipts disabled.</p>
|
|
||||||
|
|
||||||
<p>If you do not want to share your main profile,
|
|
||||||
you can also create a <a href="#multiple-accounts">dedicated profile</a> for joining a channel.</p>
|
|
||||||
|
|
||||||
<h3 id="create-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Create a channel <a href="#create-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>New Chat</strong> and choose <strong>New Channel</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Enter a <strong>name</strong>, optionally set an <strong>image</strong> and <strong>description</strong>, and hit the <strong>Create</strong> button.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can now send and manage messages as usual.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>From the channel’s profile, <strong>share the QR code or invite link with others</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Subscribers will receive your messages,
|
|
||||||
but they cannot send messages in your channel.
|
|
||||||
When subscribing, they will receive <strong>a few of the latest messages of the channel history</strong>.</p>
|
|
||||||
|
|
||||||
<p>You can see the <strong>view count</strong> beside each message.
|
|
||||||
Note that this only counts subscribers who have read receipts enabled,
|
|
||||||
so the real view count may be larger.</p>
|
|
||||||
|
|
||||||
<h3 id="how-many-subscribers-can-a-channel-have">
|
|
||||||
|
|
||||||
|
|
||||||
How many subscribers can a channel have? <a href="#how-many-subscribers-can-a-channel-have" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>Channels are designed for much larger audiences than <a href="#groups">groups</a>.</p>
|
|
||||||
|
|
||||||
<p>The practical limit depends on the used <a href="#relays">relay</a>,
|
|
||||||
so there is no single fixed number that applies everywhere.</p>
|
|
||||||
|
|
||||||
<p>For really large channels with several tens of thousands of subscribers,
|
|
||||||
we recommend using a <a href="#multiple-accounts">dedicated profile</a> for the channel
|
|
||||||
and checking whether the relay is suitable.</p>
|
|
||||||
|
|
||||||
<p>But don’t be too hesitant: Delta Chat is designed to be relay-agnostic,
|
|
||||||
so you can change your relay at any point easily -
|
|
||||||
your existing subscribers will not even notice.
|
|
||||||
You only have to update the invite link you share with new subscribers in that case.</p>
|
|
||||||
|
|
||||||
<h2 id="calls">
|
|
||||||
|
|
||||||
|
|
||||||
Calls <a href="#calls" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Delta Chat supports one-to-one <strong>audio calls</strong> and <strong>video calls</strong>.</p>
|
|
||||||
|
|
||||||
<p>Calls are supported on Desktop, Ubuntu Touch, iOS and Android 8 and newer.</p>
|
|
||||||
|
|
||||||
<h3 id="place-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Place a call <a href="#place-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>In a one-to-one chat, tap the 📞 <strong>call icon</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>This opens a small menu
|
|
||||||
where you can choose whether to place an <strong>Audio Call</strong> or a <strong>Video Call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="accept-or-reject-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Accept or reject a call <a href="#accept-or-reject-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>When someone calls you,
|
|
||||||
Delta Chat shows an <strong>incoming call screen</strong> or notification.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>Accept</strong> to answer
|
|
||||||
or <strong>Decline</strong> to reject the call.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="during-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
During a call <a href="#during-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>mute</strong> your microphone.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>enable or disable your camera</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>On mobile, you can <strong>switch between front and back cameras</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Depending on the device, you can also select the audio output or use picture-in-picture.
|
|
||||||
On desktop, the call is using a dedicated window
|
|
||||||
and you can continue using the main Delta Chat window as usual.</p>
|
|
||||||
|
|
||||||
<h3 id="missed-calls-and-notifications">
|
|
||||||
|
|
||||||
|
|
||||||
Missed calls and notifications <a href="#missed-calls-and-notifications" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>If you do not answer, do not hear the ringing, or do not have your device at hand,
|
|
||||||
the call appears as a <strong>missed call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Only your accepted contacts</strong> can make your device ring.
|
|
||||||
Contact requests will appear as usual and will not ring.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>At <strong>Settings → Notifications → Calls</strong>,
|
|
||||||
you can disable the special call ringing screen completely.
|
|
||||||
If you do so, you will not be disturbed by any ringing notification,
|
|
||||||
you can still pick up the call by tapping the incoming call message bubble in its chat.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="webxdc">
|
<h2 id="webxdc">
|
||||||
|
|
||||||
|
|
||||||
@@ -1633,20 +1427,23 @@ can not be identified easily.</p>
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
|
||||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
as well as sometimes your contact’s devices if you have a <a href="#experiments">call</a>
|
||||||
or use <a href="#webxdc">apps</a> together.</p>
|
or use <a href="#webxdc">apps</a> together.</p>
|
||||||
|
|
||||||
<p>IP Addresses are needed for connectivity and efficiency.
|
<p>IP Addresses are needed for connectivity and efficiency.
|
||||||
Delta Chat neither persists nor exposes them.
|
They are neither persisted nor exposed.
|
||||||
Note that IP Addresses
|
Note that the IP Address
|
||||||
are not like an address you give to a delivery service,
|
is not like a detailed address you give to a delivery service,
|
||||||
but typically less precise, often defining city or region only.</p>
|
but much more coarse, often defining region or country only.</p>
|
||||||
|
|
||||||
<p>If you see your IP Address as a risk,
|
<p>As this is just how the internet and other messengers work by default,
|
||||||
we recommend to use a VPN for the whole system.
|
we do not offer options here or ask upfront questions.</p>
|
||||||
Per-app options leave gaps across your system.
|
|
||||||
For example, tapping a link can expose IP Addresses to unknown parties, which is by far the larger risk.</p>
|
<p>If you see your IP Address as a security or privacy risk,
|
||||||
|
we recommend to use a VPN, in combination with system lockdown mode.
|
||||||
|
Hunting down options in all apps on your system will leave gaps.
|
||||||
|
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
|
||||||
|
|
||||||
<h3 id="sealedsender">
|
<h3 id="sealedsender">
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
<li><a href="#що-таке-delta-chat">Що таке Delta Chat?</a>
|
<li><a href="#що-таке-delta-chat">Що таке Delta Chat?</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#howtoe2ee">Як мені знайти людей для спілкування?</a></li>
|
<li><a href="#howtoe2ee">Як мені знайти людей для спілкування?</a></li>
|
||||||
<li><a href="#чому-чат-позначений-як-запит">Чому чат позначений як «Запит»?</a></li>
|
<li><a href="#why-is-a-chat-marked-as-request">Why is a chat marked as “Request”?</a></li>
|
||||||
<li><a href="#як-я-можу-познайомити-двох-своїх-друзів-один-з-одним">Як я можу познайомити двох своїх друзів один з одним?</a></li>
|
<li><a href="#how-can-i-put-two-of-my-friends-in-contact-with-each-other">How can I put two of my friends in contact with each other?</a></li>
|
||||||
<li><a href="#multiple-accounts">Що таке профілі? Як я можу перемикатися між ними?</a></li>
|
<li><a href="#multiple-accounts">Що таке профілі? Як я можу перемикатися між ними?</a></li>
|
||||||
<li><a href="#хто-бачить-моє-зображення-профілю">Хто бачить моє зображення профілю?</a></li>
|
<li><a href="#хто-бачить-моє-зображення-профілю">Хто бачить моє зображення профілю?</a></li>
|
||||||
<li><a href="#signature">Чи можу я встановити біографію/статус у Delta Chat?</a></li>
|
<li><a href="#signature">Чи можу я встановити біографію/статус у Delta Chat?</a></li>
|
||||||
@@ -28,21 +28,6 @@
|
|||||||
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#channels">Channels</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#subscribe-to-a-channel">Subscribe to a channel</a></li>
|
|
||||||
<li><a href="#create-a-channel">Create a channel</a></li>
|
|
||||||
<li><a href="#how-many-subscribers-can-a-channel-have">How many subscribers can a channel have?</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#calls">Calls</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#place-a-call">Place a call</a></li>
|
|
||||||
<li><a href="#accept-or-reject-a-call">Accept or reject a call</a></li>
|
|
||||||
<li><a href="#during-a-call">During a call</a></li>
|
|
||||||
<li><a href="#missed-calls-and-notifications">Missed calls and notifications</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#webxdc">In-chat apps</a>
|
<li><a href="#webxdc">In-chat apps</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
||||||
@@ -166,7 +151,8 @@
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<p>Якщо обидві сторони перебувають у мережі, незабаром з’явиться вікно чату, і вони зможуть безпечно обмінюватися повідомленнями.</p>
|
<p>If both sides are online, they will soon see a chat
|
||||||
|
and can start messaging securely.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>If one side is offline or in bad network,
|
<p>If one side is offline or in bad network,
|
||||||
@@ -178,17 +164,19 @@ the ability to chat is delayed until connectivity is restored.</p>
|
|||||||
Тепер Ви автоматично використовуватимете <a href="#e2ee">наскрізне шифрування</a> з цим контактом.
|
Тепер Ви автоматично використовуватимете <a href="#e2ee">наскрізне шифрування</a> з цим контактом.
|
||||||
Якщо ви додасте один одного у <a href="#groups">групи</a>, наскрізне шифрування буде встановлено між усіма учасниками.</p>
|
Якщо ви додасте один одного у <a href="#groups">групи</a>, наскрізне шифрування буде встановлено між усіма учасниками.</p>
|
||||||
|
|
||||||
<h3 id="чому-чат-позначений-як-запит">
|
<h3 id="why-is-a-chat-marked-as-request">
|
||||||
|
|
||||||
|
|
||||||
Чому чат позначений як «Запит»? <a href="#чому-чат-позначений-як-запит" class="anchor"></a>
|
Why is a chat marked as “Request”? <a href="#why-is-a-chat-marked-as-request" class="anchor"></a>
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Оскільки це приватний месенджер, лише друзі та родичі, яким ви <a href="#howtoe2ee">надіслали свій QR-код або посилання-запрошення</a>, можуть вам писати.</p>
|
<p>As being a private messenger,
|
||||||
|
only friends and family you <a href="#howtoe2ee">share your QR code or invite link with</a> can write to you.</p>
|
||||||
|
|
||||||
<p>Ваші друзі можуть поділитися вашими контактними даними з іншими друзями, це відображається як <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Запит</b></p>
|
<p>Your friends may share your contact with other friends,
|
||||||
|
this appears as <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Request</b></p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
@@ -202,17 +190,19 @@ the ability to chat is delayed until connectivity is restored.</p>
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3 id="як-я-можу-познайомити-двох-своїх-друзів-один-з-одним">
|
<h3 id="how-can-i-put-two-of-my-friends-in-contact-with-each-other">
|
||||||
|
|
||||||
|
|
||||||
Як я можу познайомити двох своїх друзів один з одним? <a href="#як-я-можу-познайомити-двох-своїх-друзів-один-з-одним" class="anchor"></a>
|
How can I put two of my friends in contact with each other? <a href="#how-can-i-put-two-of-my-friends-in-contact-with-each-other" class="anchor"></a>
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Додайте перший контакт до чату другого, скориставшись функцією <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Кнопка «Додати» → Контакт</strong>. Ви також можете додати коротке повідомлення-представлення.</p>
|
<p>Attach the first contact to the chat of the second using <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment Button → Contact</strong>.
|
||||||
|
You can also add a little introduction message.</p>
|
||||||
|
|
||||||
<p>Тоді другий контакт отримає <strong>картку</strong> і зможе натиснути на неї, щоб почати чат із першим контактом.</p>
|
<p>The second contact will receive a <strong>card</strong> then
|
||||||
|
and can tap it to start chatting with the first contact.</p>
|
||||||
|
|
||||||
<h3 id="multiple-accounts">
|
<h3 id="multiple-accounts">
|
||||||
|
|
||||||
@@ -222,7 +212,9 @@ the ability to chat is delayed until connectivity is restored.</p>
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Профіль — це <strong>ім’я, зображення</strong> та деяка додаткова інформація для шифрування повідомлень. Профіль зберігається виключно на вашому пристрої (пристроях) і використовує сервер лише для передачі повідомлень.</p>
|
<p>A profile is <strong>a name, a picture</strong> and some additional information for encrypting messages.
|
||||||
|
A profile lives on your device(s) only
|
||||||
|
and uses the server only to relay messages.</p>
|
||||||
|
|
||||||
<p>Під час першого встановлення Delta Chat створюється перший профіль.</p>
|
<p>Під час першого встановлення Delta Chat створюється перший профіль.</p>
|
||||||
|
|
||||||
@@ -252,7 +244,10 @@ the ability to chat is delayed until connectivity is restored.</p>
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Так, ви можете це зробити в розділі <strong>Налаштування → Профіль → Опис</strong>. Після того як ви надішлете повідомлення контакту, він побачить його, переглянувши ваші контактні дані.</p>
|
<p>Yes,
|
||||||
|
you can do so under <strong>Settings → Profile → Bio</strong>.
|
||||||
|
Once you sent a message to a contact,
|
||||||
|
they will see it when they view your contact details.</p>
|
||||||
|
|
||||||
<h3 id="що-значить-закріплення-приглушення-архівування">
|
<h3 id="що-значить-закріплення-приглушення-архівування">
|
||||||
|
|
||||||
@@ -320,7 +315,10 @@ the ability to chat is delayed until connectivity is restored.</p>
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>Іноді біля аватара контакту можна побачити <strong>зелену крапку</strong> <img style="vertical-align:middle; width:1.2em; margin:1px" src="../green-dot.png" alt="" />. Це означає, що ви <strong>нещодавно бачили його</strong> протягом останніх 10 хвилин, наприклад, тому що він надіслав вам повідомлення або підтвердження прочитання.</p>
|
<p>You can sometimes see a <strong>green dot</strong> <img style="vertical-align:middle; width:1.2em; margin:1px" src="../green-dot.png" alt="" />
|
||||||
|
next to the avatar of a contact.
|
||||||
|
It means they were <strong>recently seen by you</strong> in the last 10 minutes,
|
||||||
|
e.g. because they messaged you or sent a read receipt.</p>
|
||||||
|
|
||||||
<p>So this is not a real time online status
|
<p>So this is not a real time online status
|
||||||
and others will as well not always see that you are “online”.</p>
|
and others will as well not always see that you are “online”.</p>
|
||||||
@@ -584,197 +582,6 @@ but more than 150 is not recommended.</p>
|
|||||||
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
||||||
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
||||||
|
|
||||||
<h2 id="channels">
|
|
||||||
|
|
||||||
|
|
||||||
Channels <a href="#channels" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Channels are a one-to-many tool for broadcasting messages.</p>
|
|
||||||
|
|
||||||
<h3 id="subscribe-to-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Subscribe to a channel <a href="#subscribe-to-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Scan the <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>QR code</strong>
|
|
||||||
or tap the <strong>invite link</strong> you got from the channel owner.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>That’s all!
|
|
||||||
You will receive a few of the messages from the channel history
|
|
||||||
and, from that point on, all new messages from the channel.</p>
|
|
||||||
|
|
||||||
<p><strong>Don’t worry,</strong> if that does not happen immediately.
|
|
||||||
Once the channel owner comes online, your join request will be processed.</p>
|
|
||||||
|
|
||||||
<p>As all of Delta Chat, also Channels are private and decentralized,
|
|
||||||
there is no public discovery.</p>
|
|
||||||
|
|
||||||
<p>Other channel subscribers will not see that you subscribed and cannot message you.
|
|
||||||
The channel owner, however, can message you.
|
|
||||||
They will also see that you read a message unless you have read receipts disabled.</p>
|
|
||||||
|
|
||||||
<p>If you do not want to share your main profile,
|
|
||||||
you can also create a <a href="#multiple-accounts">dedicated profile</a> for joining a channel.</p>
|
|
||||||
|
|
||||||
<h3 id="create-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Create a channel <a href="#create-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>New Chat</strong> and choose <strong>New Channel</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Enter a <strong>name</strong>, optionally set an <strong>image</strong> and <strong>description</strong>, and hit the <strong>Create</strong> button.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can now send and manage messages as usual.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>From the channel’s profile, <strong>share the QR code or invite link with others</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Subscribers will receive your messages,
|
|
||||||
but they cannot send messages in your channel.
|
|
||||||
When subscribing, they will receive <strong>a few of the latest messages of the channel history</strong>.</p>
|
|
||||||
|
|
||||||
<p>You can see the <strong>view count</strong> beside each message.
|
|
||||||
Note that this only counts subscribers who have read receipts enabled,
|
|
||||||
so the real view count may be larger.</p>
|
|
||||||
|
|
||||||
<h3 id="how-many-subscribers-can-a-channel-have">
|
|
||||||
|
|
||||||
|
|
||||||
How many subscribers can a channel have? <a href="#how-many-subscribers-can-a-channel-have" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>Channels are designed for much larger audiences than <a href="#groups">groups</a>.</p>
|
|
||||||
|
|
||||||
<p>The practical limit depends on the used <a href="#relays">relay</a>,
|
|
||||||
so there is no single fixed number that applies everywhere.</p>
|
|
||||||
|
|
||||||
<p>For really large channels with several tens of thousands of subscribers,
|
|
||||||
we recommend using a <a href="#multiple-accounts">dedicated profile</a> for the channel
|
|
||||||
and checking whether the relay is suitable.</p>
|
|
||||||
|
|
||||||
<p>But don’t be too hesitant: Delta Chat is designed to be relay-agnostic,
|
|
||||||
so you can change your relay at any point easily -
|
|
||||||
your existing subscribers will not even notice.
|
|
||||||
You only have to update the invite link you share with new subscribers in that case.</p>
|
|
||||||
|
|
||||||
<h2 id="calls">
|
|
||||||
|
|
||||||
|
|
||||||
Calls <a href="#calls" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Delta Chat supports one-to-one <strong>audio calls</strong> and <strong>video calls</strong>.</p>
|
|
||||||
|
|
||||||
<p>Calls are supported on Desktop, Ubuntu Touch, iOS and Android 8 and newer.</p>
|
|
||||||
|
|
||||||
<h3 id="place-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Place a call <a href="#place-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>In a one-to-one chat, tap the 📞 <strong>call icon</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>This opens a small menu
|
|
||||||
where you can choose whether to place an <strong>Audio Call</strong> or a <strong>Video Call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="accept-or-reject-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Accept or reject a call <a href="#accept-or-reject-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>When someone calls you,
|
|
||||||
Delta Chat shows an <strong>incoming call screen</strong> or notification.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>Accept</strong> to answer
|
|
||||||
or <strong>Decline</strong> to reject the call.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="during-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
During a call <a href="#during-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>mute</strong> your microphone.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>enable or disable your camera</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>On mobile, you can <strong>switch between front and back cameras</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Depending on the device, you can also select the audio output or use picture-in-picture.
|
|
||||||
On desktop, the call is using a dedicated window
|
|
||||||
and you can continue using the main Delta Chat window as usual.</p>
|
|
||||||
|
|
||||||
<h3 id="missed-calls-and-notifications">
|
|
||||||
|
|
||||||
|
|
||||||
Missed calls and notifications <a href="#missed-calls-and-notifications" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>If you do not answer, do not hear the ringing, or do not have your device at hand,
|
|
||||||
the call appears as a <strong>missed call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Only your accepted contacts</strong> can make your device ring.
|
|
||||||
Contact requests will appear as usual and will not ring.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>At <strong>Settings → Notifications → Calls</strong>,
|
|
||||||
you can disable the special call ringing screen completely.
|
|
||||||
If you do so, you will not be disturbed by any ringing notification,
|
|
||||||
you can still pick up the call by tapping the incoming call message bubble in its chat.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="webxdc">
|
<h2 id="webxdc">
|
||||||
|
|
||||||
|
|
||||||
@@ -1461,20 +1268,23 @@ can not be identified easily.</p>
|
|||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
|
||||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
as well as sometimes your contact’s devices if you have a <a href="#experiments">call</a>
|
||||||
or use <a href="#webxdc">apps</a> together.</p>
|
or use <a href="#webxdc">apps</a> together.</p>
|
||||||
|
|
||||||
<p>IP Addresses are needed for connectivity and efficiency.
|
<p>IP Addresses are needed for connectivity and efficiency.
|
||||||
Delta Chat neither persists nor exposes them.
|
They are neither persisted nor exposed.
|
||||||
Note that IP Addresses
|
Note that the IP Address
|
||||||
are not like an address you give to a delivery service,
|
is not like a detailed address you give to a delivery service,
|
||||||
but typically less precise, often defining city or region only.</p>
|
but much more coarse, often defining region or country only.</p>
|
||||||
|
|
||||||
<p>If you see your IP Address as a risk,
|
<p>As this is just how the internet and other messengers work by default,
|
||||||
we recommend to use a VPN for the whole system.
|
we do not offer options here or ask upfront questions.</p>
|
||||||
Per-app options leave gaps across your system.
|
|
||||||
For example, tapping a link can expose IP Addresses to unknown parties, which is by far the larger risk.</p>
|
<p>If you see your IP Address as a security or privacy risk,
|
||||||
|
we recommend to use a VPN, in combination with system lockdown mode.
|
||||||
|
Hunting down options in all apps on your system will leave gaps.
|
||||||
|
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
|
||||||
|
|
||||||
<h3 id="sealedsender">
|
<h3 id="sealedsender">
|
||||||
|
|
||||||
|
|||||||
@@ -32,21 +32,6 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#channels">Channels</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#subscribe-to-a-channel">Subscribe to a channel</a></li>
|
|
||||||
<li><a href="#create-a-channel">Create a channel</a></li>
|
|
||||||
<li><a href="#how-many-subscribers-can-a-channel-have">How many subscribers can a channel have?</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#calls">Calls</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#place-a-call">Place a call</a></li>
|
|
||||||
<li><a href="#accept-or-reject-a-call">Accept or reject a call</a></li>
|
|
||||||
<li><a href="#during-a-call">During a call</a></li>
|
|
||||||
<li><a href="#missed-calls-and-notifications">Missed calls and notifications</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#webxdc">Webxdc 应用</a>
|
<li><a href="#webxdc">Webxdc 应用</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#我在哪里可以获得-webxdc-应用">我在哪里可以获得 Webxdc 应用?</a></li>
|
<li><a href="#我在哪里可以获得-webxdc-应用">我在哪里可以获得 Webxdc 应用?</a></li>
|
||||||
@@ -636,197 +621,6 @@ Please use this method sparingly, as sending original files will significantly i
|
|||||||
其中Delta Chat 是与<a href="#groups">平等权利</a> 聊天的私人信使。
|
其中Delta Chat 是与<a href="#groups">平等权利</a> 聊天的私人信使。
|
||||||
相关知识,请参阅<a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">邓巴数</a>。</p>
|
相关知识,请参阅<a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">邓巴数</a>。</p>
|
||||||
|
|
||||||
<h2 id="channels">
|
|
||||||
|
|
||||||
|
|
||||||
Channels <a href="#channels" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Channels are a one-to-many tool for broadcasting messages.</p>
|
|
||||||
|
|
||||||
<h3 id="subscribe-to-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Subscribe to a channel <a href="#subscribe-to-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Scan the <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> <strong>QR code</strong>
|
|
||||||
or tap the <strong>invite link</strong> you got from the channel owner.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>That’s all!
|
|
||||||
You will receive a few of the messages from the channel history
|
|
||||||
and, from that point on, all new messages from the channel.</p>
|
|
||||||
|
|
||||||
<p><strong>Don’t worry,</strong> if that does not happen immediately.
|
|
||||||
Once the channel owner comes online, your join request will be processed.</p>
|
|
||||||
|
|
||||||
<p>As all of Delta Chat, also Channels are private and decentralized,
|
|
||||||
there is no public discovery.</p>
|
|
||||||
|
|
||||||
<p>Other channel subscribers will not see that you subscribed and cannot message you.
|
|
||||||
The channel owner, however, can message you.
|
|
||||||
They will also see that you read a message unless you have read receipts disabled.</p>
|
|
||||||
|
|
||||||
<p>If you do not want to share your main profile,
|
|
||||||
you can also create a <a href="#multiple-accounts">dedicated profile</a> for joining a channel.</p>
|
|
||||||
|
|
||||||
<h3 id="create-a-channel">
|
|
||||||
|
|
||||||
|
|
||||||
Create a channel <a href="#create-a-channel" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>New Chat</strong> and choose <strong>New Channel</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Enter a <strong>name</strong>, optionally set an <strong>image</strong> and <strong>description</strong>, and hit the <strong>Create</strong> button.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can now send and manage messages as usual.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>From the channel’s profile, <strong>share the QR code or invite link with others</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Subscribers will receive your messages,
|
|
||||||
but they cannot send messages in your channel.
|
|
||||||
When subscribing, they will receive <strong>a few of the latest messages of the channel history</strong>.</p>
|
|
||||||
|
|
||||||
<p>You can see the <strong>view count</strong> beside each message.
|
|
||||||
Note that this only counts subscribers who have read receipts enabled,
|
|
||||||
so the real view count may be larger.</p>
|
|
||||||
|
|
||||||
<h3 id="how-many-subscribers-can-a-channel-have">
|
|
||||||
|
|
||||||
|
|
||||||
How many subscribers can a channel have? <a href="#how-many-subscribers-can-a-channel-have" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>Channels are designed for much larger audiences than <a href="#groups">groups</a>.</p>
|
|
||||||
|
|
||||||
<p>The practical limit depends on the used <a href="#relays">relay</a>,
|
|
||||||
so there is no single fixed number that applies everywhere.</p>
|
|
||||||
|
|
||||||
<p>For really large channels with several tens of thousands of subscribers,
|
|
||||||
we recommend using a <a href="#multiple-accounts">dedicated profile</a> for the channel
|
|
||||||
and checking whether the relay is suitable.</p>
|
|
||||||
|
|
||||||
<p>But don’t be too hesitant: Delta Chat is designed to be relay-agnostic,
|
|
||||||
so you can change your relay at any point easily -
|
|
||||||
your existing subscribers will not even notice.
|
|
||||||
You only have to update the invite link you share with new subscribers in that case.</p>
|
|
||||||
|
|
||||||
<h2 id="calls">
|
|
||||||
|
|
||||||
|
|
||||||
Calls <a href="#calls" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>Delta Chat supports one-to-one <strong>audio calls</strong> and <strong>video calls</strong>.</p>
|
|
||||||
|
|
||||||
<p>Calls are supported on Desktop, Ubuntu Touch, iOS and Android 8 and newer.</p>
|
|
||||||
|
|
||||||
<h3 id="place-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Place a call <a href="#place-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>In a one-to-one chat, tap the 📞 <strong>call icon</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>This opens a small menu
|
|
||||||
where you can choose whether to place an <strong>Audio Call</strong> or a <strong>Video Call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="accept-or-reject-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
Accept or reject a call <a href="#accept-or-reject-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>When someone calls you,
|
|
||||||
Delta Chat shows an <strong>incoming call screen</strong> or notification.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Tap <strong>Accept</strong> to answer
|
|
||||||
or <strong>Decline</strong> to reject the call.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="during-a-call">
|
|
||||||
|
|
||||||
|
|
||||||
During a call <a href="#during-a-call" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>mute</strong> your microphone.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can <strong>enable or disable your camera</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>On mobile, you can <strong>switch between front and back cameras</strong>.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Depending on the device, you can also select the audio output or use picture-in-picture.
|
|
||||||
On desktop, the call is using a dedicated window
|
|
||||||
and you can continue using the main Delta Chat window as usual.</p>
|
|
||||||
|
|
||||||
<h3 id="missed-calls-and-notifications">
|
|
||||||
|
|
||||||
|
|
||||||
Missed calls and notifications <a href="#missed-calls-and-notifications" class="anchor"></a>
|
|
||||||
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p>If you do not answer, do not hear the ringing, or do not have your device at hand,
|
|
||||||
the call appears as a <strong>missed call</strong>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Only your accepted contacts</strong> can make your device ring.
|
|
||||||
Contact requests will appear as usual and will not ring.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>At <strong>Settings → Notifications → Calls</strong>,
|
|
||||||
you can disable the special call ringing screen completely.
|
|
||||||
If you do so, you will not be disturbed by any ringing notification,
|
|
||||||
you can still pick up the call by tapping the incoming call message bubble in its chat.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="webxdc">
|
<h2 id="webxdc">
|
||||||
|
|
||||||
|
|
||||||
@@ -1620,20 +1414,22 @@ Delta Chat 应用程序不会在服务器上存储任何有关联系人或群组
|
|||||||
|
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
<p>使用的 <a href="#relays">中继服务器</a> 需要知道您的 IP 地址、
|
||||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
有时还需要知道联系人的设备(如果你们有 <a href="#experiments">通话</a>),或一起使用 <a href="#webxdc">Webxdc应用程序</a>。</p>
|
||||||
or use <a href="#webxdc">apps</a> together.</p>
|
|
||||||
|
|
||||||
<p>IP Addresses are needed for connectivity and efficiency.
|
<p>IP 地址是连接和提高效率所必需的。
|
||||||
Delta Chat neither persists nor exposes them.
|
它们既不会持久存在,也不会暴露。
|
||||||
Note that IP Addresses
|
请注意,IP 地址
|
||||||
are not like an address you give to a delivery service,
|
不像你给快递服务的详细地址、
|
||||||
but typically less precise, often defining city or region only.</p>
|
而是更粗略,通常只定义地区或国家。</p>
|
||||||
|
|
||||||
<p>If you see your IP Address as a risk,
|
<p>这只是互联网和其他信使的默认工作方式、
|
||||||
we recommend to use a VPN for the whole system.
|
我们在此不提供选项,也不预先提问。</p>
|
||||||
Per-app options leave gaps across your system.
|
|
||||||
For example, tapping a link can expose IP Addresses to unknown parties, which is by far the larger risk.</p>
|
<p>如果你认为你的 IP 地址存在安全或隐私风险、
|
||||||
|
我们建议使用 VPN 并结合系统锁定模式。
|
||||||
|
在系统的所有应用程序中查找选项会留下漏洞。
|
||||||
|
例如,点击链接会将 IP 地址暴露给未知方,这是目前最大的风险。</p>
|
||||||
|
|
||||||
<p>###Delta Chat 是否支持 “密封发件人”?{#sealedsender}</p>
|
<p>###Delta Chat 是否支持 “密封发件人”?{#sealedsender}</p>
|
||||||
|
|
||||||
|
|||||||
@@ -443,19 +443,10 @@ public class Rpc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Estimates the number of messages that will be deleted
|
* Estimate the number of messages that will be deleted
|
||||||
* by the `set_config()`-option `delete_device_after`.
|
* by the set_config()-options `delete_device_after` or `delete_server_after`.
|
||||||
* <p>
|
|
||||||
* This is typically used to show the estimated impact to the user
|
* This is typically used to show the estimated impact to the user
|
||||||
* before actually enabling deletion of old messages.
|
* before actually enabling deletion of old messages.
|
||||||
* <p>
|
|
||||||
* Messages in the "Saved Messages" chat are not counted as they will not be deleted automatically.
|
|
||||||
* <p>
|
|
||||||
* Parameters:
|
|
||||||
* - `from_server`: Deprecated, pass `false` here
|
|
||||||
* - `seconds`: Count messages older than the given number of seconds.
|
|
||||||
* <p>
|
|
||||||
* Returns the number of messages that are older than the given number of seconds.
|
|
||||||
*/
|
*/
|
||||||
public Integer estimateAutoDeletionCount(Integer accountId, Boolean fromServer, Integer seconds) throws RpcException {
|
public Integer estimateAutoDeletionCount(Integer accountId, Boolean fromServer, Integer seconds) throws RpcException {
|
||||||
return transport.callForResult(new TypeReference<Integer>(){}, "estimate_auto_deletion_count", mapper.valueToTree(accountId), mapper.valueToTree(fromServer), mapper.valueToTree(seconds));
|
return transport.callForResult(new TypeReference<Integer>(){}, "estimate_auto_deletion_count", mapper.valueToTree(accountId), mapper.valueToTree(fromServer), mapper.valueToTree(seconds));
|
||||||
@@ -719,6 +710,9 @@ public class Rpc {
|
|||||||
* because the word "channel" already appears a lot in the code,
|
* because the word "channel" already appears a lot in the code,
|
||||||
* which would make it hard to grep for it.
|
* which would make it hard to grep for it.
|
||||||
* <p>
|
* <p>
|
||||||
|
* After creation, the chat contains no recipients and is in _unpromoted_ state;
|
||||||
|
* see [`CommandApi::create_group_chat`] for more information on the unpromoted state.
|
||||||
|
* <p>
|
||||||
* Returns the created chat's id.
|
* Returns the created chat's id.
|
||||||
*/
|
*/
|
||||||
public Integer createBroadcast(Integer accountId, String chatName) throws RpcException {
|
public Integer createBroadcast(Integer accountId, String chatName) throws RpcException {
|
||||||
@@ -919,22 +913,8 @@ public class Rpc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all message IDs belonging to a chat.
|
* Returns all messages of a particular chat.
|
||||||
* <p>
|
* <p>
|
||||||
* The list is already sorted and starts with the oldest message.
|
|
||||||
* Clients should not try to re-sort the list as this would be an expensive action
|
|
||||||
* and would result in inconsistencies between clients.
|
|
||||||
* Note that the messages are not necessarily sorted by their ID or by their displayed timestamp;
|
|
||||||
* UIs need to handle both the case of descending message IDs
|
|
||||||
* and of decreasing timestamps.
|
|
||||||
* <p>
|
|
||||||
* Optionally, 'daymarkers' added to the ID array may help to
|
|
||||||
* implement virtual lists.
|
|
||||||
* <p>
|
|
||||||
* Parameters:
|
|
||||||
* <p>
|
|
||||||
* * chat_id The chat ID of which the messages IDs should be queried.
|
|
||||||
* * _info_only: Deprecated, pass `false` here.
|
|
||||||
* * `add_daymarker` - If `true`, add day markers as `DC_MSG_ID_DAYMARKER` to the result,
|
* * `add_daymarker` - If `true`, add day markers as `DC_MSG_ID_DAYMARKER` to the result,
|
||||||
* e.g. [1234, 1237, 9, 1239]. The day marker timestamp is the midnight one for the
|
* e.g. [1234, 1237, 9, 1239]. The day marker timestamp is the midnight one for the
|
||||||
* corresponding (following) day in the local timezone.
|
* corresponding (following) day in the local timezone.
|
||||||
@@ -952,14 +932,6 @@ public class Rpc {
|
|||||||
return transport.callForResult(new TypeReference<java.util.List<Integer>>(){}, "get_existing_msg_ids", mapper.valueToTree(accountId), mapper.valueToTree(msgIds));
|
return transport.callForResult(new TypeReference<java.util.List<Integer>>(){}, "get_existing_msg_ids", mapper.valueToTree(accountId), mapper.valueToTree(msgIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all messages belonging to a chat.
|
|
||||||
* <p>
|
|
||||||
* Similar to `get_message_ids` / `getMessageIds`,
|
|
||||||
* see that function for details.
|
|
||||||
* The difference is that this function here returns a list of `MessageListItem`,
|
|
||||||
* which is an enum of a message or a daymarker.
|
|
||||||
*/
|
|
||||||
public java.util.List<MessageListItem> getMessageListItems(Integer accountId, Integer chatId, Boolean infoOnly, Boolean addDaymarker) throws RpcException {
|
public java.util.List<MessageListItem> getMessageListItems(Integer accountId, Integer chatId, Boolean infoOnly, Boolean addDaymarker) throws RpcException {
|
||||||
return transport.callForResult(new TypeReference<java.util.List<MessageListItem>>(){}, "get_message_list_items", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(infoOnly), mapper.valueToTree(addDaymarker));
|
return transport.callForResult(new TypeReference<java.util.List<MessageListItem>>(){}, "get_message_list_items", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(infoOnly), mapper.valueToTree(addDaymarker));
|
||||||
}
|
}
|
||||||
@@ -1209,6 +1181,11 @@ public class Rpc {
|
|||||||
return transport.callForResult(new TypeReference<String>(){}, "make_vcard", mapper.valueToTree(accountId), mapper.valueToTree(contacts));
|
return transport.callForResult(new TypeReference<String>(){}, "make_vcard", mapper.valueToTree(accountId), mapper.valueToTree(contacts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sets vCard containing the given contacts to the message draft. */
|
||||||
|
public void setDraftVcard(Integer accountId, Integer msgId, java.util.List<Integer> contacts) throws RpcException {
|
||||||
|
transport.call("set_draft_vcard", mapper.valueToTree(accountId), mapper.valueToTree(msgId), mapper.valueToTree(contacts));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the [`ChatId`] for the 1:1 chat with `contact_id` if it exists.
|
* Returns the [`ChatId`] for the 1:1 chat with `contact_id` if it exists.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -1351,47 +1328,10 @@ public class Rpc {
|
|||||||
return transport.callForResult(new TypeReference<String>(){}, "get_connectivity_html", mapper.valueToTree(accountId));
|
return transport.callForResult(new TypeReference<String>(){}, "get_connectivity_html", mapper.valueToTree(accountId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets current location.
|
|
||||||
* <p>
|
|
||||||
* Returns true if location streaming is currently
|
|
||||||
* enabled and locations should be updated.
|
|
||||||
* <p>
|
|
||||||
* Location is represented as latitude and longitude in degrees
|
|
||||||
* and horizontal accuracy in meters.
|
|
||||||
*/
|
|
||||||
public Boolean setLocation(Float latitude, Float longitude, Float accuracy) throws RpcException {
|
|
||||||
return transport.callForResult(new TypeReference<Boolean>(){}, "set_location", mapper.valueToTree(latitude), mapper.valueToTree(longitude), mapper.valueToTree(accuracy));
|
|
||||||
}
|
|
||||||
|
|
||||||
public java.util.List<Location> getLocations(Integer accountId, Integer chatId, Integer contactId, Integer timestampBegin, Integer timestampEnd) throws RpcException {
|
public java.util.List<Location> getLocations(Integer accountId, Integer chatId, Integer contactId, Integer timestampBegin, Integer timestampEnd) throws RpcException {
|
||||||
return transport.callForResult(new TypeReference<java.util.List<Location>>(){}, "get_locations", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(contactId), mapper.valueToTree(timestampBegin), mapper.valueToTree(timestampEnd));
|
return transport.callForResult(new TypeReference<java.util.List<Location>>(){}, "get_locations", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(contactId), mapper.valueToTree(timestampBegin), mapper.valueToTree(timestampEnd));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables location streaming in chat identified by `chat_id` for `seconds` seconds.
|
|
||||||
* <p>
|
|
||||||
* Pass 0 as the number of seconds to disable location streaming in the chat.
|
|
||||||
*/
|
|
||||||
public void sendLocationsToChat(Integer accountId, Integer chatId, Integer seconds) throws RpcException {
|
|
||||||
transport.call("send_locations_to_chat", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(seconds));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns whether any chat is sending locations. */
|
|
||||||
public Boolean isSendingLocations(Integer accountId) throws RpcException {
|
|
||||||
return transport.callForResult(new TypeReference<Boolean>(){}, "is_sending_locations", mapper.valueToTree(accountId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns whether `chat_id` is sending locations. */
|
|
||||||
public Boolean isSendingLocationsToChat(Integer accountId, Integer chatId) throws RpcException {
|
|
||||||
return transport.callForResult(new TypeReference<Boolean>(){}, "is_sending_locations_to_chat", mapper.valueToTree(accountId), mapper.valueToTree(chatId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Stops sending locations to all chats. */
|
|
||||||
public void stopSendingLocations() throws RpcException {
|
|
||||||
transport.call("stop_sending_locations");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendWebxdcStatusUpdate(Integer accountId, Integer instanceMsgId, String updateStr, String descr) throws RpcException {
|
public void sendWebxdcStatusUpdate(Integer accountId, Integer instanceMsgId, String updateStr, String descr) throws RpcException {
|
||||||
transport.call("send_webxdc_status_update", mapper.valueToTree(accountId), mapper.valueToTree(instanceMsgId), mapper.valueToTree(updateStr), mapper.valueToTree(descr));
|
transport.call("send_webxdc_status_update", mapper.valueToTree(accountId), mapper.valueToTree(instanceMsgId), mapper.valueToTree(updateStr), mapper.valueToTree(descr));
|
||||||
}
|
}
|
||||||
@@ -1527,18 +1467,17 @@ public class Rpc {
|
|||||||
transport.call("resend_messages", mapper.valueToTree(accountId), mapper.valueToTree(messageIds));
|
transport.call("resend_messages", mapper.valueToTree(accountId), mapper.valueToTree(messageIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated as of 2026-04; use `send_msg` with `Viewtype::Sticker` instead. */
|
|
||||||
public Integer sendSticker(Integer accountId, Integer chatId, String stickerPath) throws RpcException {
|
public Integer sendSticker(Integer accountId, Integer chatId, String stickerPath) throws RpcException {
|
||||||
return transport.callForResult(new TypeReference<Integer>(){}, "send_sticker", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(stickerPath));
|
return transport.callForResult(new TypeReference<Integer>(){}, "send_sticker", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(stickerPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a reaction to message.
|
* Send a reaction to message.
|
||||||
* <p>
|
* <p>
|
||||||
* A reaction is a string that represents an emoji.
|
* Reaction is a string of emojis separated by spaces. Reaction to a
|
||||||
* You can call this function again to change the emoji;
|
* single message can be sent multiple times. The last reaction
|
||||||
* the last sent reaction overrides all previously sent reactions.
|
* received overrides all previously received reactions. It is
|
||||||
* It is possible to remove the reaction by sending an empty string.
|
* possible to remove all reactions by sending an empty string.
|
||||||
*/
|
*/
|
||||||
public Integer sendReaction(Integer accountId, Integer messageId, java.util.List<String> reaction) throws RpcException {
|
public Integer sendReaction(Integer accountId, Integer messageId, java.util.List<String> reaction) throws RpcException {
|
||||||
return transport.callForResult(new TypeReference<Integer>(){}, "send_reaction", mapper.valueToTree(accountId), mapper.valueToTree(messageId), mapper.valueToTree(reaction));
|
return transport.callForResult(new TypeReference<Integer>(){}, "send_reaction", mapper.valueToTree(accountId), mapper.valueToTree(messageId), mapper.valueToTree(reaction));
|
||||||
|
|||||||
@@ -12,13 +12,6 @@ public class EnteredLoginParam {
|
|||||||
/** TLS options: whether to allow invalid certificates and/or invalid hostnames. Default: Automatic */
|
/** TLS options: whether to allow invalid certificates and/or invalid hostnames. Default: Automatic */
|
||||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||||
public EnteredCertificateChecks certificateChecks;
|
public EnteredCertificateChecks certificateChecks;
|
||||||
/**
|
|
||||||
* IMAP server folder.
|
|
||||||
* <p>
|
|
||||||
* Defaults to "INBOX" if not set. Should not be an empty string.
|
|
||||||
*/
|
|
||||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
|
||||||
public String imapFolder;
|
|
||||||
/** Imap server port. */
|
/** Imap server port. */
|
||||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||||
public Integer imapPort;
|
public Integer imapPort;
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ package chat.delta.rpc.types;
|
|||||||
public class Reactions {
|
public class Reactions {
|
||||||
/** Unique reactions and their count, sorted in descending order. */
|
/** Unique reactions and their count, sorted in descending order. */
|
||||||
public java.util.List<Reaction> reactions;
|
public java.util.List<Reaction> reactions;
|
||||||
/** Map from a contact to it's reaction to message. There is only a single reaction per contact, but this contains a list of reactions for historical reasons. */
|
/** Map from a contact to it's reaction to message. */
|
||||||
public java.util.Map<String, java.util.List<String>> reactionsByContact;
|
public java.util.Map<String, java.util.List<String>> reactionsByContact;
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@ public enum Viewtype {
|
|||||||
Gif,
|
Gif,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message containing a sticker, similar to image.
|
* Message containing a sticker, similar to image. NB: When sending, the message viewtype may be changed to `Image` by some heuristics like checking for transparent pixels. Use `Message::force_sticker()` to disable them.
|
||||||
* <p>
|
* <p>
|
||||||
* If possible, the ui should display the image without borders in a transparent way. A click on a sticker will offer to install the sticker set in some future.
|
* If possible, the ui should display the image without borders in a transparent way. A click on a sticker will offer to install the sticker set in some future.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
package chat.delta.rpc.types;
|
package chat.delta.rpc.types;
|
||||||
|
|
||||||
public class WebxdcMessageInfo {
|
public class WebxdcMessageInfo {
|
||||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
|
||||||
public String orientation;
|
|
||||||
/** if the Webxdc represents a document, then this is the name of the document */
|
/** if the Webxdc represents a document, then this is the name of the document */
|
||||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||||
public String document;
|
public String document;
|
||||||
@@ -17,10 +15,6 @@ public class WebxdcMessageInfo {
|
|||||||
public String icon;
|
public String icon;
|
||||||
/** True if full internet access should be granted to the app. */
|
/** True if full internet access should be granted to the app. */
|
||||||
public Boolean internetAccess;
|
public Boolean internetAccess;
|
||||||
/** Define if the local user is the one who initially shared the webxdc application in the chat. */
|
|
||||||
public Boolean isAppSender;
|
|
||||||
/** Define if the app runs in a broadcasting context. */
|
|
||||||
public Boolean isBroadcast;
|
|
||||||
/**
|
/**
|
||||||
* The name of the app.
|
* The name of the app.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ public class DcAccounts {
|
|||||||
public boolean isAllChatmail() {
|
public boolean isAllChatmail() {
|
||||||
for (int accountId : getAll()) {
|
for (int accountId : getAll()) {
|
||||||
DcContext dcContext = getAccount(accountId);
|
DcContext dcContext = getAccount(accountId);
|
||||||
if (dcContext.getConfigInt("is_chatmail") == 0) {
|
if (!dcContext.isChatmail()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -362,6 +362,10 @@ public class DcContext {
|
|||||||
return displayname;
|
return displayname;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isChatmail() {
|
||||||
|
return getConfigInt("is_chatmail") == 1;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isMuted() {
|
public boolean isMuted() {
|
||||||
return getConfigInt("is_muted") == 1;
|
return getConfigInt("is_muted") == 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ public class DcMsg {
|
|||||||
public static final int DC_VIDEOCHATTYPE_UNKNOWN = 0;
|
public static final int DC_VIDEOCHATTYPE_UNKNOWN = 0;
|
||||||
public static final int DC_VIDEOCHATTYPE_BASICWEBRTC = 1;
|
public static final int DC_VIDEOCHATTYPE_BASICWEBRTC = 1;
|
||||||
|
|
||||||
|
private static final String TAG = DcMsg.class.getSimpleName();
|
||||||
|
|
||||||
public DcMsg(DcContext context, int viewtype) {
|
public DcMsg(DcContext context, int viewtype) {
|
||||||
msgCPtr = context.createMsgCPtr(viewtype);
|
msgCPtr = context.createMsgCPtr(viewtype);
|
||||||
}
|
}
|
||||||
@@ -194,6 +196,8 @@ public class DcMsg {
|
|||||||
|
|
||||||
public native void setHtml(String text);
|
public native void setHtml(String text);
|
||||||
|
|
||||||
|
public native void forceSticker();
|
||||||
|
|
||||||
public native void setFileAndDeduplicate(String file, String name, String filemime);
|
public native void setFileAndDeduplicate(String file, String name, String filemime);
|
||||||
|
|
||||||
public native void setDimension(int width, int height);
|
public native void setDimension(int width, int height);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.ComponentName;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
@@ -11,23 +12,21 @@ import androidx.appcompat.view.ActionMode;
|
|||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.media3.session.MediaController;
|
import androidx.media3.session.MediaController;
|
||||||
import androidx.media3.session.SessionCommand;
|
import androidx.media3.session.SessionCommand;
|
||||||
import androidx.media3.session.SessionToken;
|
import androidx.media3.session.SessionToken;
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
|
||||||
import com.b44t.messenger.DcChat;
|
import com.b44t.messenger.DcChat;
|
||||||
import com.b44t.messenger.DcContext;
|
import com.b44t.messenger.DcContext;
|
||||||
import com.b44t.messenger.DcEvent;
|
import com.b44t.messenger.DcEvent;
|
||||||
import com.b44t.messenger.DcMsg;
|
import com.b44t.messenger.DcMsg;
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
import com.google.android.material.tabs.TabLayoutMediator;
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||||
import org.thoughtcrime.securesms.components.audioplay.ChatAudioQueueProvider;
|
|
||||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
import org.thoughtcrime.securesms.service.AudioPlaybackService;
|
import org.thoughtcrime.securesms.service.AudioPlaybackService;
|
||||||
@@ -36,7 +35,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
|
|
||||||
public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||||
implements DcEventCenter.DcEventDelegate {
|
implements DcEventCenter.DcEventDelegate {
|
||||||
private static final String TAG = "AllMediaActivity";
|
private static final String TAG = AllMediaActivity.class.getSimpleName();
|
||||||
|
|
||||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||||
public static final String CONTACT_ID_EXTRA = "contact_id";
|
public static final String CONTACT_ID_EXTRA = "contact_id";
|
||||||
@@ -55,6 +54,7 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
|||||||
this.type3 = type3;
|
this.type3 = type3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
;
|
||||||
|
|
||||||
private DcContext dcContext;
|
private DcContext dcContext;
|
||||||
private int chatId;
|
private int chatId;
|
||||||
@@ -63,7 +63,7 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
|||||||
private final ArrayList<TabData> tabs = new ArrayList<>();
|
private final ArrayList<TabData> tabs = new ArrayList<>();
|
||||||
private Toolbar toolbar;
|
private Toolbar toolbar;
|
||||||
private TabLayout tabLayout;
|
private TabLayout tabLayout;
|
||||||
private ViewPager2 viewPager;
|
private ViewPager viewPager;
|
||||||
|
|
||||||
private @Nullable MediaController mediaController;
|
private @Nullable MediaController mediaController;
|
||||||
private ListenableFuture<MediaController> mediaControllerFuture;
|
private ListenableFuture<MediaController> mediaControllerFuture;
|
||||||
@@ -97,20 +97,8 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
|||||||
isGlobalGallery() ? R.string.menu_all_media : R.string.apps_and_media);
|
isGlobalGallery() ? R.string.menu_all_media : R.string.apps_and_media);
|
||||||
}
|
}
|
||||||
|
|
||||||
AllMediaPagerAdapter adapter = new AllMediaPagerAdapter(this);
|
this.tabLayout.setupWithViewPager(viewPager);
|
||||||
this.viewPager.setAdapter(adapter);
|
this.viewPager.setAdapter(new AllMediaPagerAdapter(getSupportFragmentManager()));
|
||||||
this.viewPager.registerOnPageChangeCallback(
|
|
||||||
new ViewPager2.OnPageChangeCallback() {
|
|
||||||
@Override
|
|
||||||
public void onPageSelected(int position) {
|
|
||||||
adapter.onPageChanged(position);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
new TabLayoutMediator(
|
|
||||||
this.tabLayout,
|
|
||||||
this.viewPager,
|
|
||||||
(tab, position) -> tab.setText(getString(tabs.get(position).title)))
|
|
||||||
.attach();
|
|
||||||
if (getIntent().getBooleanExtra(FORCE_GALLERY, false)) {
|
if (getIntent().getBooleanExtra(FORCE_GALLERY, false)) {
|
||||||
this.viewPager.setCurrentItem(1, false);
|
this.viewPager.setCurrentItem(1, false);
|
||||||
}
|
}
|
||||||
@@ -119,9 +107,7 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
|||||||
eventCenter.addObserver(DcContext.DC_EVENT_CHAT_MODIFIED, this);
|
eventCenter.addObserver(DcContext.DC_EVENT_CHAT_MODIFIED, this);
|
||||||
eventCenter.addObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
|
eventCenter.addObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
|
||||||
|
|
||||||
int accountId = DcHelper.getAccounts(this).getSelectedAccount().getAccountId();
|
|
||||||
playbackViewModel = new ViewModelProvider(this).get(AudioPlaybackViewModel.class);
|
playbackViewModel = new ViewModelProvider(this).get(AudioPlaybackViewModel.class);
|
||||||
playbackViewModel.setQueueProvider(new ChatAudioQueueProvider(this, chatId, accountId));
|
|
||||||
initializeMediaController();
|
initializeMediaController();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +119,6 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
|||||||
mediaController = null;
|
mediaController = null;
|
||||||
playbackViewModel.setMediaController(null);
|
playbackViewModel.setMediaController(null);
|
||||||
}
|
}
|
||||||
playbackViewModel.setQueueProvider(null);
|
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,16 +182,31 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
|||||||
return contactId == 0 && chatId == 0;
|
return contactId == 0 && chatId == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AllMediaPagerAdapter extends FragmentStateAdapter {
|
private class AllMediaPagerAdapter extends FragmentStatePagerAdapter {
|
||||||
private int currentPosition = -1;
|
private Object currentFragment = null;
|
||||||
|
|
||||||
AllMediaPagerAdapter(FragmentActivity activity) {
|
AllMediaPagerAdapter(FragmentManager fragmentManager) {
|
||||||
super(activity);
|
super(fragmentManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||||
|
super.setPrimaryItem(container, position, object);
|
||||||
|
if (currentFragment != null && currentFragment != object) {
|
||||||
|
ActionMode action = null;
|
||||||
|
if (currentFragment instanceof MessageSelectorFragment) {
|
||||||
|
action = ((MessageSelectorFragment) currentFragment).getActionMode();
|
||||||
|
}
|
||||||
|
if (action != null) {
|
||||||
|
action.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentFragment = object;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Fragment createFragment(int position) {
|
public Fragment getItem(int position) {
|
||||||
TabData data = tabs.get(position);
|
TabData data = tabs.get(position);
|
||||||
Fragment fragment;
|
Fragment fragment;
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
@@ -229,24 +229,13 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getCount() {
|
||||||
return tabs.size();
|
return tabs.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPageChanged(int newPosition) {
|
@Override
|
||||||
if (currentPosition != -1 && currentPosition != newPosition) {
|
public CharSequence getPageTitle(int position) {
|
||||||
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
|
return getString(tabs.get(position).title);
|
||||||
if (!(fragment instanceof MessageSelectorFragment)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ActionMode action = ((MessageSelectorFragment) fragment).getActionMode();
|
|
||||||
if (action != null) {
|
|
||||||
action.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentPosition = newPosition;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import org.thoughtcrime.securesms.util.Util;
|
|||||||
import org.thoughtcrime.securesms.webxdc.WebxdcGarbageCollectionWorker;
|
import org.thoughtcrime.securesms.webxdc.WebxdcGarbageCollectionWorker;
|
||||||
|
|
||||||
public class ApplicationContext extends MultiDexApplication {
|
public class ApplicationContext extends MultiDexApplication {
|
||||||
private static final String TAG = "ApplicationContext";
|
private static final String TAG = ApplicationContext.class.getSimpleName();
|
||||||
private static final Object initLock = new Object();
|
private static final Object initLock = new Object();
|
||||||
private static volatile boolean isInitialized = false;
|
private static volatile boolean isInitialized = false;
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ public class ApplicationContext extends MultiDexApplication {
|
|||||||
Log.i(TAG, "DcAccounts created");
|
Log.i(TAG, "DcAccounts created");
|
||||||
rpc = new Rpc(new FFITransport(dcAccounts.getJsonrpcInstance()));
|
rpc = new Rpc(new FFITransport(dcAccounts.getJsonrpcInstance()));
|
||||||
Log.i(TAG, "Rpc created");
|
Log.i(TAG, "Rpc created");
|
||||||
AccountManager.getInstance().migrateToDcAccounts(this, dcAccounts);
|
AccountManager.getInstance().migrateToDcAccounts(this);
|
||||||
|
|
||||||
int[] allAccounts = dcAccounts.getAll();
|
int[] allAccounts = dcAccounts.getAll();
|
||||||
Log.i(TAG, "Number of profiles: " + allAccounts.length);
|
Log.i(TAG, "Number of profiles: " + allAccounts.length);
|
||||||
@@ -255,6 +255,12 @@ public class ApplicationContext extends MultiDexApplication {
|
|||||||
// 2025-12-16: The setting was removed.
|
// 2025-12-16: The setting was removed.
|
||||||
// Revert it to the default if it was changed in the past.
|
// Revert it to the default if it was changed in the past.
|
||||||
ac.setConfigInt("webxdc_realtime_enabled", 1);
|
ac.setConfigInt("webxdc_realtime_enabled", 1);
|
||||||
|
|
||||||
|
// 2025-11-12: this is needed until core starts ignoring "delete_server_after" for
|
||||||
|
// chatmail
|
||||||
|
if (ac.isChatmail()) {
|
||||||
|
ac.setConfig("delete_server_after", null); // reset
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (allAccounts.length == 0) {
|
if (allAccounts.length == 0) {
|
||||||
try {
|
try {
|
||||||
@@ -302,24 +308,16 @@ public class ApplicationContext extends MultiDexApplication {
|
|||||||
Log.i(
|
Log.i(
|
||||||
"DeltaChat",
|
"DeltaChat",
|
||||||
"++++++++++++++++++ NetworkCallback.onAvailable() #" + debugOnAvailableCount++);
|
"++++++++++++++++++ NetworkCallback.onAvailable() #" + debugOnAvailableCount++);
|
||||||
// onBlockedStatusChanged is only available on API 29+
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
|
||||||
getDcAccounts().maybeNetwork();
|
getDcAccounts().maybeNetwork();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBlockedStatusChanged(
|
public void onBlockedStatusChanged(
|
||||||
@NonNull android.net.Network network, boolean blocked) {
|
@NonNull android.net.Network network, boolean blocked) {
|
||||||
Log.i(
|
Log.i(
|
||||||
"DeltaChat",
|
"DeltaChat",
|
||||||
"++++++++++++++++++ NetworkCallback.onBlockedStatusChanged("
|
"++++++++++++++++++ NetworkCallback.onBlockedStatusChanged() #"
|
||||||
+ blocked
|
|
||||||
+ ") #"
|
|
||||||
+ debugOnBlockedStatusChangedCount++);
|
+ debugOnBlockedStatusChangedCount++);
|
||||||
if (!blocked) {
|
|
||||||
getDcAccounts().maybeNetwork();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
|
|
||||||
public abstract class BaseActionBarActivity extends AppCompatActivity {
|
public abstract class BaseActionBarActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private static final String TAG = "BaseActionBarActivity";
|
private static final String TAG = BaseActionBarActivity.class.getSimpleName();
|
||||||
protected DynamicTheme dynamicTheme = new DynamicTheme();
|
protected DynamicTheme dynamicTheme = new DynamicTheme();
|
||||||
|
|
||||||
protected void onPreCreate() {
|
protected void onPreCreate() {
|
||||||
|
|||||||
@@ -41,12 +41,13 @@ import org.thoughtcrime.securesms.connect.DcHelper;
|
|||||||
import org.thoughtcrime.securesms.connect.DirectShareUtil;
|
import org.thoughtcrime.securesms.connect.DirectShareUtil;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.SendRelayedMessageUtil;
|
import org.thoughtcrime.securesms.util.SendRelayedMessageUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.ShareUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask;
|
import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask;
|
||||||
import org.thoughtcrime.securesms.util.views.ProgressDialog;
|
import org.thoughtcrime.securesms.util.views.ProgressDialog;
|
||||||
|
|
||||||
public abstract class BaseConversationListFragment extends Fragment implements ActionMode.Callback {
|
public abstract class BaseConversationListFragment extends Fragment implements ActionMode.Callback {
|
||||||
private static final String TAG = "BaseConversationListFragment";
|
private static final String TAG = BaseConversationListFragment.class.getSimpleName();
|
||||||
protected ActionMode actionMode;
|
protected ActionMode actionMode;
|
||||||
protected PulsingFloatingActionButton fab;
|
protected PulsingFloatingActionButton fab;
|
||||||
|
|
||||||
@@ -422,18 +423,9 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
|||||||
.setIcon(IconCompat.createWithAdaptiveBitmap(avatar))
|
.setIcon(IconCompat.createWithAdaptiveBitmap(avatar))
|
||||||
.setIntent(intent)
|
.setIntent(intent)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
boolean success;
|
|
||||||
try {
|
|
||||||
success = ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfoCompat, null);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "ErrAddToHomescreen: requestPinShortcut() failed", e);
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
boolean finalSuccess = success;
|
|
||||||
Util.runOnMain(
|
Util.runOnMain(
|
||||||
() -> {
|
() -> {
|
||||||
if (!finalSuccess) {
|
if (!ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfoCompat, null)) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
activity,
|
activity,
|
||||||
"ErrAddToHomescreen: requestPinShortcut() failed",
|
"ErrAddToHomescreen: requestPinShortcut() failed",
|
||||||
@@ -487,6 +479,10 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
|||||||
@Override
|
@Override
|
||||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||||
if (isRelayingMessageContent(getActivity())) {
|
if (isRelayingMessageContent(getActivity())) {
|
||||||
|
if (ShareUtil.getSharedContactId(getActivity()) != 0) {
|
||||||
|
return false; // no sharing of a contact to multiple recipients at the same time, we can
|
||||||
|
// reconsider when that becomes a real-world need
|
||||||
|
}
|
||||||
Context context = getContext();
|
Context context = getContext();
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
fab.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_send_sms_white_24dp));
|
fab.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_send_sms_white_24dp));
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
*/
|
*/
|
||||||
public abstract class ContactSelectionActivity extends PassphraseRequiredActionBarActivity
|
public abstract class ContactSelectionActivity extends PassphraseRequiredActionBarActivity
|
||||||
implements ContactSelectionListFragment.OnContactSelectedListener {
|
implements ContactSelectionListFragment.OnContactSelectedListener {
|
||||||
private static final String TAG = "ContactSelectionActivity";
|
private static final String TAG = ContactSelectionActivity.class.getSimpleName();
|
||||||
|
|
||||||
protected ContactSelectionListFragment contactsFragment;
|
protected ContactSelectionListFragment contactsFragment;
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
*/
|
*/
|
||||||
public class ContactSelectionListFragment extends Fragment
|
public class ContactSelectionListFragment extends Fragment
|
||||||
implements LoaderManager.LoaderCallbacks<DcContactsLoader.Ret>, DcEventCenter.DcEventDelegate {
|
implements LoaderManager.LoaderCallbacks<DcContactsLoader.Ret>, DcEventCenter.DcEventDelegate {
|
||||||
private static final String TAG = "ContactSelectionListFragment";
|
private static final String TAG = ContactSelectionListFragment.class.getSimpleName();
|
||||||
|
|
||||||
public static final String MULTI_SELECT = "multi_select";
|
public static final String MULTI_SELECT = "multi_select";
|
||||||
public static final String SELECT_UNENCRYPTED_EXTRA = "select_unencrypted_extra";
|
public static final String SELECT_UNENCRYPTED_EXTRA = "select_unencrypted_extra";
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ import org.thoughtcrime.securesms.components.ScaleStableImageView;
|
|||||||
import org.thoughtcrime.securesms.components.SendButton;
|
import org.thoughtcrime.securesms.components.SendButton;
|
||||||
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||||
import org.thoughtcrime.securesms.components.audioplay.AudioView;
|
import org.thoughtcrime.securesms.components.audioplay.AudioView;
|
||||||
import org.thoughtcrime.securesms.components.audioplay.ChatAudioQueueProvider;
|
|
||||||
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
|
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
|
||||||
import org.thoughtcrime.securesms.connect.AccountManager;
|
import org.thoughtcrime.securesms.connect.AccountManager;
|
||||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||||
@@ -149,7 +148,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
InputPanel.Listener,
|
InputPanel.Listener,
|
||||||
InputPanel.MediaListener,
|
InputPanel.MediaListener,
|
||||||
AudioView.OnActionListener {
|
AudioView.OnActionListener {
|
||||||
private static final String TAG = "ConversationActivity";
|
private static final String TAG = ConversationActivity.class.getSimpleName();
|
||||||
|
|
||||||
public static final String ACCOUNT_ID_EXTRA = "account_id";
|
public static final String ACCOUNT_ID_EXTRA = "account_id";
|
||||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||||
@@ -200,6 +199,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
private final boolean isSecureText = true;
|
private final boolean isSecureText = true;
|
||||||
private boolean isDefaultSms = true;
|
private boolean isDefaultSms = true;
|
||||||
private boolean isSecurityInitialized = false;
|
private boolean isSecurityInitialized = false;
|
||||||
|
private boolean successfulForwardingAttempt = false;
|
||||||
private boolean isEditing = false;
|
private boolean isEditing = false;
|
||||||
private boolean switchedProfile = false;
|
private boolean switchedProfile = false;
|
||||||
|
|
||||||
@@ -223,9 +223,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
initializeViews();
|
initializeViews();
|
||||||
initializeResources();
|
initializeResources();
|
||||||
|
|
||||||
int accountId = DcHelper.getAccounts(this).getSelectedAccount().getAccountId();
|
|
||||||
playbackViewModel = new ViewModelProvider(this).get(AudioPlaybackViewModel.class);
|
playbackViewModel = new ViewModelProvider(this).get(AudioPlaybackViewModel.class);
|
||||||
playbackViewModel.setQueueProvider(new ChatAudioQueueProvider(this, chatId, accountId));
|
|
||||||
initializeMediaController();
|
initializeMediaController();
|
||||||
|
|
||||||
initializeSecurity(false, isDefaultSms)
|
initializeSecurity(false, isDefaultSms)
|
||||||
@@ -386,11 +384,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
|
|
||||||
if (inputPanel.isRecording() && inputPanel.getRecordingDuration() > 1000) {
|
|
||||||
saveRecording();
|
|
||||||
} else {
|
|
||||||
processComposeControls(ACTION_SAVE_DRAFT);
|
processComposeControls(ACTION_SAVE_DRAFT);
|
||||||
}
|
|
||||||
|
|
||||||
DcHelper.getNotificationCenter(this).clearVisibleChat();
|
DcHelper.getNotificationCenter(this).clearVisibleChat();
|
||||||
if (isFinishing()) overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right);
|
if (isFinishing()) overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right);
|
||||||
@@ -419,7 +413,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
mediaController = null;
|
mediaController = null;
|
||||||
playbackViewModel.setMediaController(null);
|
playbackViewModel.setMediaController(null);
|
||||||
}
|
}
|
||||||
playbackViewModel.setQueueProvider(null);
|
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -639,12 +632,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
return true;
|
return true;
|
||||||
} else if (itemId == R.id.menu_start_audio_call) {
|
} else if (itemId == R.id.menu_start_audio_call) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
CallUtil.startAudioCall(this, chatId);
|
CallUtil.startAudioCall(context, chatId);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if (itemId == R.id.menu_start_video_call) {
|
} else if (itemId == R.id.menu_start_video_call) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
CallUtil.startVideoCall(this, chatId);
|
CallUtil.startVideoCall(context, chatId);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if (itemId == R.id.menu_all_media) {
|
} else if (itemId == R.id.menu_all_media) {
|
||||||
@@ -679,15 +672,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setDraftText(String txt) {
|
public void setDraftText(String txt) {
|
||||||
try {
|
|
||||||
if (rpc.canSend(rpc.getSelectedAccountId(), chatId)) {
|
|
||||||
composeText.setText(txt);
|
composeText.setText(txt);
|
||||||
composeText.setSelection(composeText.getText().length());
|
composeText.setSelection(composeText.getText().length());
|
||||||
}
|
}
|
||||||
} catch (RpcException e) {
|
|
||||||
Log.e(TAG, "Rpc error", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void hideSoftKeyboard() {
|
public void hideSoftKeyboard() {
|
||||||
container.hideCurrentInput(composeText);
|
container.hideCurrentInput(composeText);
|
||||||
@@ -716,13 +703,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
extras.putInt(ConversationListFragment.RELOAD_LIST, 1);
|
extras.putInt(ConversationListFragment.RELOAD_LIST, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attachmentManager.isAttachmentPresent()) {
|
playbackViewModel.stopNonMessageAudioPlayback();
|
||||||
SlideDeck slideDeck = attachmentManager.buildSlideDeck();
|
|
||||||
int audioDraftId = slideDeck.getAudioDraftId();
|
|
||||||
if (audioDraftId != 0) {
|
|
||||||
playbackViewModel.stop(audioDraftId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean archived = getIntent().getBooleanExtra(FROM_ARCHIVED_CHATS_EXTRA, false);
|
boolean archived = getIntent().getBooleanExtra(FROM_ARCHIVED_CHATS_EXTRA, false);
|
||||||
Intent intent =
|
Intent intent =
|
||||||
@@ -855,17 +836,20 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
if (dcChat.isSelfTalk()) {
|
if (dcChat.isSelfTalk()) {
|
||||||
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
|
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
|
||||||
} else {
|
} else {
|
||||||
int messageIds[] = ShareUtil.getForwardedMessageIDs(this);
|
String name = dcChat.getName();
|
||||||
int messageCount = messageIds == null ? 0 : messageIds.length;
|
if (!dcChat.isMultiUser()) {
|
||||||
|
int[] contactIds = dcContext.getChatContacts(chatId);
|
||||||
|
if (contactIds.length == 1 || contactIds.length == 2) {
|
||||||
|
name = dcContext.getContact(contactIds[0]).getDisplayName();
|
||||||
|
}
|
||||||
|
}
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setMessage(
|
.setMessage(getString(R.string.ask_forward, name))
|
||||||
getResources()
|
|
||||||
.getQuantityString(
|
|
||||||
R.plurals.ask_forward_messages, messageCount, messageCount, dcChat.getName()))
|
|
||||||
.setPositiveButton(
|
.setPositiveButton(
|
||||||
R.string.forward,
|
R.string.ok,
|
||||||
(dialogInterface, i) -> {
|
(dialogInterface, i) -> {
|
||||||
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
|
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
|
||||||
|
successfulForwardingAttempt = true;
|
||||||
})
|
})
|
||||||
.setNegativeButton(R.string.cancel, (dialogInterface, i) -> finish())
|
.setNegativeButton(R.string.cancel, (dialogInterface, i) -> finish())
|
||||||
.setOnCancelListener(dialog -> finish())
|
.setOnCancelListener(dialog -> finish())
|
||||||
@@ -889,10 +873,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
private void handleSharing() {
|
private void handleSharing() {
|
||||||
ArrayList<Uri> uriList = ShareUtil.getSharedUris(this);
|
ArrayList<Uri> uriList = ShareUtil.getSharedUris(this);
|
||||||
|
int sharedContactId = ShareUtil.getSharedContactId(this);
|
||||||
if (uriList.size() > 1) {
|
if (uriList.size() > 1) {
|
||||||
askSendingFiles(uriList, () -> SendRelayedMessageUtil.immediatelyRelay(this, chatId));
|
askSendingFiles(uriList, () -> SendRelayedMessageUtil.immediatelyRelay(this, chatId));
|
||||||
} else {
|
} else {
|
||||||
if (ShareUtil.getSharedHtml(this) != null
|
if (sharedContactId != 0) {
|
||||||
|
addAttachmentContactInfo(sharedContactId);
|
||||||
|
} else if (ShareUtil.getSharedHtml(this) != null
|
||||||
|| ShareUtil.getSharedSubject(this) != null
|
|| ShareUtil.getSharedSubject(this) != null
|
||||||
|| ("sticker".equals(ShareUtil.getSharedType(this)) && !uriList.isEmpty())) {
|
|| ("sticker".equals(ShareUtil.getSharedType(this)) && !uriList.isEmpty())) {
|
||||||
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
|
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
|
||||||
@@ -980,7 +967,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
setMedia(draft, MediaType.GIF).addListener(listener);
|
setMedia(draft, MediaType.GIF).addListener(listener);
|
||||||
break;
|
break;
|
||||||
case DcMsg.DC_MSG_AUDIO:
|
case DcMsg.DC_MSG_AUDIO:
|
||||||
case DcMsg.DC_MSG_VOICE:
|
|
||||||
setMedia(draft, MediaType.AUDIO).addListener(listener);
|
setMedia(draft, MediaType.AUDIO).addListener(listener);
|
||||||
break;
|
break;
|
||||||
case DcMsg.DC_MSG_VIDEO:
|
case DcMsg.DC_MSG_VIDEO:
|
||||||
@@ -1133,7 +1119,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
if (chatId == DcChat.DC_CHAT_NO_CHAT)
|
if (chatId == DcChat.DC_CHAT_NO_CHAT)
|
||||||
throw new IllegalStateException("can't display a conversation for no chat.");
|
throw new IllegalStateException("can't display a conversation for no chat.");
|
||||||
dcChat = DcHelper.getContext(context).getChat(chatId);
|
dcChat = DcHelper.getContext(context).getChat(chatId);
|
||||||
attachmentTypeSelector = null;
|
|
||||||
recipient = new Recipient(this, dcChat);
|
recipient = new Recipient(this, dcChat);
|
||||||
glideRequests = GlideApp.with(this);
|
glideRequests = GlideApp.with(this);
|
||||||
|
|
||||||
@@ -1276,17 +1261,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
inputPanel.clearSubject();
|
inputPanel.clearSubject();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop draft audio playback
|
// Stop draft audio playback regardless, since it is unlikely
|
||||||
if (slideDeck != null) {
|
// we will need background playback for drafts
|
||||||
int audioDraftId = slideDeck.getAudioDraftId();
|
playbackViewModel.stopNonMessageAudioPlayback();
|
||||||
if (audioDraftId != 0) {
|
|
||||||
playbackViewModel.stop(audioDraftId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DcContext dcContext = DcHelper.getContext(context);
|
DcContext dcContext = DcHelper.getContext(context);
|
||||||
final int currentChatId = dcChat.getId();
|
final int currentChatId = dcChat.getId();
|
||||||
final boolean canSend = dcChat.canSend();
|
|
||||||
Util.runOnAnyBackgroundThread(
|
Util.runOnAnyBackgroundThread(
|
||||||
() -> {
|
() -> {
|
||||||
DcMsg msg = null;
|
DcMsg msg = null;
|
||||||
@@ -1409,8 +1389,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// set or clear draft if user can't send in chat since they can't delete it otherwise
|
dcContext.setDraft(currentChatId, msg);
|
||||||
dcContext.setDraft(currentChatId, canSend ? msg : null);
|
|
||||||
}
|
}
|
||||||
future.set(currentChatId);
|
future.set(currentChatId);
|
||||||
});
|
});
|
||||||
@@ -1632,6 +1611,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
msg.setQuote(quote.get().getQuotedMsg());
|
msg.setQuote(quote.get().getQuotedMsg());
|
||||||
}
|
}
|
||||||
msg.setFileAndDeduplicate(path, null, null);
|
msg.setFileAndDeduplicate(path, null, null);
|
||||||
|
msg.forceSticker();
|
||||||
dcContext.sendMsg(chatId, msg);
|
dcContext.sendMsg(chatId, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1693,56 +1673,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveRecording() {
|
|
||||||
inputPanel.resetRecordingUI();
|
|
||||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
||||||
|
|
||||||
final int thisChatId = chatId;
|
|
||||||
final Optional<QuoteModel> quote = inputPanel.getQuote();
|
|
||||||
|
|
||||||
ListenableFuture<Pair<Uri, Long>> future = audioRecorder.stopRecording();
|
|
||||||
future.addListener(
|
|
||||||
new ListenableFuture.Listener<Pair<Uri, Long>>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(final @NonNull Pair<Uri, Long> result) {
|
|
||||||
Util.runOnAnyBackgroundThread(
|
|
||||||
() -> {
|
|
||||||
try {
|
|
||||||
DcContext dcContext = DcHelper.getContext(context);
|
|
||||||
String path =
|
|
||||||
DcHelper.copyToBlobdir(
|
|
||||||
ConversationActivity.this, result.first, "voice", ".m4a");
|
|
||||||
|
|
||||||
DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_VOICE);
|
|
||||||
msg.setFileAndDeduplicate(path, null, null);
|
|
||||||
if (quote.isPresent()) {
|
|
||||||
msg.setQuote(quote.get().getQuotedMsg());
|
|
||||||
}
|
|
||||||
dcContext.setDraft(thisChatId, msg);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Failed to save voice as draft", e);
|
|
||||||
} finally {
|
|
||||||
PersistentBlobProvider.getInstance()
|
|
||||||
.delete(ConversationActivity.this, result.first);
|
|
||||||
}
|
|
||||||
|
|
||||||
runOnUiThread(
|
|
||||||
() -> {
|
|
||||||
if (chatId == thisChatId && !isFinishing() && !isDestroyed()) {
|
|
||||||
initializeDraft();
|
|
||||||
updateToggleButtonState();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(ExecutionException e) {
|
|
||||||
Log.w(TAG, "Failed to stop recording", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AttachButtonListener implements OnClickListener {
|
private class AttachButtonListener implements OnClickListener {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ import org.thoughtcrime.securesms.util.views.ConversationAdaptiveActionsToolbar;
|
|||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
public class ConversationFragment extends MessageSelectorFragment {
|
public class ConversationFragment extends MessageSelectorFragment {
|
||||||
private static final String TAG = "ConversationFragment";
|
private static final String TAG = ConversationFragment.class.getSimpleName();
|
||||||
|
|
||||||
private static final int SCROLL_ANIMATION_THRESHOLD = 50;
|
private static final int SCROLL_ANIMATION_THRESHOLD = 50;
|
||||||
|
|
||||||
@@ -944,8 +944,6 @@ public class ConversationFragment extends MessageSelectorFragment {
|
|||||||
WebxdcActivity.openWebxdcActivity(
|
WebxdcActivity.openWebxdcActivity(
|
||||||
getContext(), messageRecord.getParent(), messageRecord.getWebxdcHref());
|
getContext(), messageRecord.getParent(), messageRecord.getWebxdcHref());
|
||||||
}
|
}
|
||||||
} else if (messageRecord.getInfoType() == DcMsg.DC_INFO_LOCATIONSTREAMING_ENABLED) {
|
|
||||||
WebxdcActivity.openMaps(getContext(), (int) chatId);
|
|
||||||
} else if (messageRecord.getInfoType() == DcMsg.DC_INFO_CHAT_DESCRIPTION_CHANGED) {
|
} else if (messageRecord.getInfoType() == DcMsg.DC_INFO_CHAT_DESCRIPTION_CHANGED) {
|
||||||
Intent intent = new Intent(getContext(), ProfileActivity.class);
|
Intent intent = new Intent(getContext(), ProfileActivity.class);
|
||||||
intent.putExtra(ProfileActivity.CHAT_ID_EXTRA, (int) chatId);
|
intent.putExtra(ProfileActivity.CHAT_ID_EXTRA, (int) chatId);
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import android.graphics.PorterDuff;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -37,7 +36,6 @@ import androidx.annotation.DimenRes;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.core.view.ViewCompat;
|
|
||||||
import chat.delta.rpc.RpcException;
|
import chat.delta.rpc.RpcException;
|
||||||
import chat.delta.rpc.types.CallInfo;
|
import chat.delta.rpc.types.CallInfo;
|
||||||
import chat.delta.rpc.types.CallState;
|
import chat.delta.rpc.types.CallState;
|
||||||
@@ -46,7 +44,6 @@ import chat.delta.rpc.types.VcardContact;
|
|||||||
import com.b44t.messenger.DcChat;
|
import com.b44t.messenger.DcChat;
|
||||||
import com.b44t.messenger.DcContact;
|
import com.b44t.messenger.DcContact;
|
||||||
import com.b44t.messenger.DcMsg;
|
import com.b44t.messenger.DcMsg;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.thoughtcrime.securesms.calls.CallCoordinator;
|
import org.thoughtcrime.securesms.calls.CallCoordinator;
|
||||||
@@ -75,7 +72,6 @@ import org.thoughtcrime.securesms.mms.VcardSlide;
|
|||||||
import org.thoughtcrime.securesms.reactions.ReactionsConversationView;
|
import org.thoughtcrime.securesms.reactions.ReactionsConversationView;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.Linkifier;
|
import org.thoughtcrime.securesms.util.Linkifier;
|
||||||
import org.thoughtcrime.securesms.util.LongClickCopySpan;
|
|
||||||
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||||
import org.thoughtcrime.securesms.util.MarkdownUtil;
|
import org.thoughtcrime.securesms.util.MarkdownUtil;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
@@ -90,7 +86,7 @@ import org.thoughtcrime.securesms.util.views.Stub;
|
|||||||
* @author Moxie Marlinspike
|
* @author Moxie Marlinspike
|
||||||
*/
|
*/
|
||||||
public class ConversationItem extends BaseConversationItem {
|
public class ConversationItem extends BaseConversationItem {
|
||||||
private static final String TAG = "ConversationItem";
|
private static final String TAG = ConversationItem.class.getSimpleName();
|
||||||
|
|
||||||
private static final Rect SWIPE_RECT = new Rect();
|
private static final Rect SWIPE_RECT = new Rect();
|
||||||
|
|
||||||
@@ -124,9 +120,6 @@ public class ConversationItem extends BaseConversationItem {
|
|||||||
private Stub<CallItemView> callViewStub;
|
private Stub<CallItemView> callViewStub;
|
||||||
private @Nullable EventListener eventListener;
|
private @Nullable EventListener eventListener;
|
||||||
|
|
||||||
// IDs of accessibility actions registered via ViewCompat.addAccessibilityAction
|
|
||||||
private final List<Integer> linkActionIds = new ArrayList<>();
|
|
||||||
|
|
||||||
private int measureCalls;
|
private int measureCalls;
|
||||||
|
|
||||||
private int incomingBubbleColor;
|
private int incomingBubbleColor;
|
||||||
@@ -431,12 +424,6 @@ public class ConversationItem extends BaseConversationItem {
|
|||||||
bodyText.setClickable(false);
|
bodyText.setClickable(false);
|
||||||
bodyText.setFocusable(false);
|
bodyText.setFocusable(false);
|
||||||
|
|
||||||
// Remove any link actions registered for the previous message binding
|
|
||||||
for (int id : linkActionIds) {
|
|
||||||
ViewCompat.removeAccessibilityAction(this, id);
|
|
||||||
}
|
|
||||||
linkActionIds.clear();
|
|
||||||
|
|
||||||
String subject = messageRecord.getSubject();
|
String subject = messageRecord.getSubject();
|
||||||
String text = messageRecord.getText();
|
String text = messageRecord.getText();
|
||||||
|
|
||||||
@@ -446,33 +433,12 @@ public class ConversationItem extends BaseConversationItem {
|
|||||||
if (messageRecord.getType() == DcMsg.DC_MSG_CALL || text.isEmpty()) {
|
if (messageRecord.getType() == DcMsg.DC_MSG_CALL || text.isEmpty()) {
|
||||||
bodyText.setVisibility(View.GONE);
|
bodyText.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
Spannable spannable = MarkdownUtil.toMarkdown(context, text);
|
Spannable spannable = (Spannable) MarkdownUtil.toMarkdown(context, text);
|
||||||
if (batchSelected.isEmpty()) {
|
if (batchSelected.isEmpty()) {
|
||||||
Linkifier.linkify(spannable);
|
spannable = Linkifier.linkify(spannable);
|
||||||
}
|
}
|
||||||
bodyText.setText(spannable);
|
bodyText.setText(spannable);
|
||||||
bodyText.setVisibility(View.VISIBLE);
|
bodyText.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
// Register a TalkBack "Actions" entry for each link in the message
|
|
||||||
Spanned spanned = (Spanned) spannable;
|
|
||||||
final TextView tv = bodyText;
|
|
||||||
for (LongClickCopySpan span :
|
|
||||||
spanned.getSpans(0, spanned.length(), LongClickCopySpan.class)) {
|
|
||||||
int start = spanned.getSpanStart(span);
|
|
||||||
int end = spanned.getSpanEnd(span);
|
|
||||||
if (start >= 0 && end > start && end <= spanned.length()) {
|
|
||||||
String linkText = spanned.subSequence(start, end).toString();
|
|
||||||
String label = context.getString(R.string.open_link, linkText);
|
|
||||||
linkActionIds.add(
|
|
||||||
ViewCompat.addAccessibilityAction(
|
|
||||||
this,
|
|
||||||
label,
|
|
||||||
(v, args) -> {
|
|
||||||
span.onClick(tv);
|
|
||||||
return true;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int downloadState = messageRecord.getDownloadState();
|
int downloadState = messageRecord.getDownloadState();
|
||||||
@@ -1096,18 +1062,7 @@ public class ConversationItem extends BaseConversationItem {
|
|||||||
if (!messageRecord.isOutgoing() && callInfo.state instanceof CallState.Alerting) {
|
if (!messageRecord.isOutgoing() && callInfo.state instanceof CallState.Alerting) {
|
||||||
int callId = messageRecord.getId();
|
int callId = messageRecord.getId();
|
||||||
CallCoordinator coordinator = CallCoordinator.getInstance(context);
|
CallCoordinator coordinator = CallCoordinator.getInstance(context);
|
||||||
|
|
||||||
if (coordinator.hasActiveCall()) {
|
|
||||||
coordinator.showIncomingCallScreen(callId);
|
coordinator.showIncomingCallScreen(callId);
|
||||||
} else {
|
|
||||||
if (callInfo.sdpOffer == null) {
|
|
||||||
Toast.makeText(context, R.string.error, Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int accId = dcContext.getAccountId();
|
|
||||||
coordinator.handleIncomingCallFromConversation(
|
|
||||||
accId, callId, callInfo.sdpOffer, callInfo.hasVideo);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (callInfo.hasVideo) {
|
if (callInfo.hasVideo) {
|
||||||
CallUtil.startVideoCall(getContext(), chatId);
|
CallUtil.startVideoCall(getContext(), chatId);
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
|
|
||||||
public class ConversationListActivity extends PassphraseRequiredActionBarActivity
|
public class ConversationListActivity extends PassphraseRequiredActionBarActivity
|
||||||
implements ConversationListFragment.ConversationSelectedListener {
|
implements ConversationListFragment.ConversationSelectedListener {
|
||||||
private static final String TAG = "ConversationListActivity";
|
private static final String TAG = ConversationListActivity.class.getSimpleName();
|
||||||
private static final String OPENPGP4FPR = "openpgp4fpr";
|
private static final String OPENPGP4FPR = "openpgp4fpr";
|
||||||
private static final String NDK_ARCH_WARNED = "ndk_arch_warned";
|
private static final String NDK_ARCH_WARNED = "ndk_arch_warned";
|
||||||
public static final String CLEAR_NOTIFICATIONS = "clear_notifications";
|
public static final String CLEAR_NOTIFICATIONS = "clear_notifications";
|
||||||
@@ -607,13 +607,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleQrFromSearch(String rawQrString) {
|
|
||||||
qrData = rawQrString;
|
|
||||||
new QrCodeHandler(this)
|
|
||||||
.handleQrData(
|
|
||||||
rawQrString, SecurejoinSource.Scan, SecurejoinUiPath.QrIcon, relayLockLauncher);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleResetRelaying() {
|
private void handleResetRelaying() {
|
||||||
resetRelayingMessageContent(this);
|
resetRelayingMessageContent(this);
|
||||||
refreshTitle();
|
refreshTitle();
|
||||||
@@ -727,7 +720,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
|||||||
refreshAvatar();
|
refreshAvatar();
|
||||||
refreshUnreadIndicator();
|
refreshUnreadIndicator();
|
||||||
refreshTitle();
|
refreshTitle();
|
||||||
conversationListFragment.resetScrollPosition();
|
|
||||||
conversationListFragment.loadChatlistAsync();
|
conversationListFragment.loadChatlistAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.content.Context;
|
|||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -55,16 +56,16 @@ public class ConversationListFragment extends BaseConversationListFragment
|
|||||||
public static final String ARCHIVE = "archive";
|
public static final String ARCHIVE = "archive";
|
||||||
public static final String RELOAD_LIST = "reload_list";
|
public static final String RELOAD_LIST = "reload_list";
|
||||||
|
|
||||||
private static final String TAG = "ConversationListFragment";
|
private static final String TAG = ConversationListFragment.class.getSimpleName();
|
||||||
|
|
||||||
private RecyclerView list;
|
private RecyclerView list;
|
||||||
private View emptyState;
|
private View emptyState;
|
||||||
private TextView emptySearch;
|
private TextView emptySearch;
|
||||||
|
private final String queryFilter = "";
|
||||||
private boolean archive;
|
private boolean archive;
|
||||||
private Timer reloadTimer;
|
private Timer reloadTimer;
|
||||||
private boolean chatlistJustLoaded;
|
private boolean chatlistJustLoaded;
|
||||||
private boolean reloadTimerInstantly;
|
private boolean reloadTimerInstantly;
|
||||||
private boolean resetScrollPosition;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
@@ -241,9 +242,9 @@ public class ConversationListFragment extends BaseConversationListFragment
|
|||||||
|
|
||||||
public void loadChatlistAsync() {
|
public void loadChatlistAsync() {
|
||||||
synchronized (loadChatlistLock) {
|
synchronized (loadChatlistLock) {
|
||||||
|
needsAnotherLoad = true;
|
||||||
if (inLoadChatlist) {
|
if (inLoadChatlist) {
|
||||||
Log.i(TAG, "chatlist loading debounced");
|
Log.i(TAG, "chatlist loading debounced");
|
||||||
needsAnotherLoad = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
inLoadChatlist = true;
|
inLoadChatlist = true;
|
||||||
@@ -252,9 +253,6 @@ public class ConversationListFragment extends BaseConversationListFragment
|
|||||||
Util.runOnAnyBackgroundThread(
|
Util.runOnAnyBackgroundThread(
|
||||||
() -> {
|
() -> {
|
||||||
while (true) {
|
while (true) {
|
||||||
Log.i(TAG, "executing debounced chatlist loading");
|
|
||||||
loadChatlist();
|
|
||||||
|
|
||||||
synchronized (loadChatlistLock) {
|
synchronized (loadChatlistLock) {
|
||||||
if (!needsAnotherLoad) {
|
if (!needsAnotherLoad) {
|
||||||
inLoadChatlist = false;
|
inLoadChatlist = false;
|
||||||
@@ -263,6 +261,8 @@ public class ConversationListFragment extends BaseConversationListFragment
|
|||||||
needsAnotherLoad = false;
|
needsAnotherLoad = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "executing debounced chatlist loading");
|
||||||
|
loadChatlist();
|
||||||
Util.sleep(100);
|
Util.sleep(100);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -285,17 +285,22 @@ public class ConversationListFragment extends BaseConversationListFragment
|
|||||||
Log.w(TAG, "Ignoring call to loadChatlist()");
|
Log.w(TAG, "Ignoring call to loadChatlist()");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long startMs = System.currentTimeMillis();
|
DcChatlist chatlist =
|
||||||
DcChatlist chatlist = DcHelper.getContext(context).getChatlist(listflags, null, 0);
|
DcHelper.getContext(context)
|
||||||
Log.i(TAG, "⏰ getChatlist(): " + (System.currentTimeMillis() - startMs) + "ms");
|
.getChatlist(listflags, queryFilter.isEmpty() ? null : queryFilter, 0);
|
||||||
|
|
||||||
Util.runOnMain(
|
Util.runOnMain(
|
||||||
() -> {
|
() -> {
|
||||||
if (chatlist.getCnt() <= 0) {
|
if (chatlist.getCnt() <= 0 && TextUtils.isEmpty(queryFilter)) {
|
||||||
list.setVisibility(View.INVISIBLE);
|
list.setVisibility(View.INVISIBLE);
|
||||||
emptyState.setVisibility(View.VISIBLE);
|
emptyState.setVisibility(View.VISIBLE);
|
||||||
emptySearch.setVisibility(View.INVISIBLE);
|
emptySearch.setVisibility(View.INVISIBLE);
|
||||||
fab.startPulse(3 * 1000);
|
fab.startPulse(3 * 1000);
|
||||||
|
} else if (chatlist.getCnt() <= 0 && !TextUtils.isEmpty(queryFilter)) {
|
||||||
|
list.setVisibility(View.INVISIBLE);
|
||||||
|
emptyState.setVisibility(View.GONE);
|
||||||
|
emptySearch.setVisibility(View.VISIBLE);
|
||||||
|
emptySearch.setText(getString(R.string.search_no_result_for_x, queryFilter));
|
||||||
} else {
|
} else {
|
||||||
list.setVisibility(View.VISIBLE);
|
list.setVisibility(View.VISIBLE);
|
||||||
emptyState.setVisibility(View.GONE);
|
emptyState.setVisibility(View.GONE);
|
||||||
@@ -304,11 +309,6 @@ public class ConversationListFragment extends BaseConversationListFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
((ConversationListAdapter) list.getAdapter()).changeData(chatlist);
|
((ConversationListAdapter) list.getAdapter()).changeData(chatlist);
|
||||||
|
|
||||||
if (resetScrollPosition) {
|
|
||||||
list.scrollToPosition(0);
|
|
||||||
resetScrollPosition = false;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,8 +365,4 @@ public class ConversationListFragment extends BaseConversationListFragment
|
|||||||
loadChatlistAsync();
|
loadChatlistAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetScrollPosition() {
|
|
||||||
resetScrollPosition = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,11 +44,9 @@ import org.thoughtcrime.securesms.components.AvatarView;
|
|||||||
import org.thoughtcrime.securesms.components.DeliveryStatusView;
|
import org.thoughtcrime.securesms.components.DeliveryStatusView;
|
||||||
import org.thoughtcrime.securesms.components.FromTextView;
|
import org.thoughtcrime.securesms.components.FromTextView;
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
|
|
||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.search.QrInviteData;
|
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
@@ -224,40 +222,6 @@ public class ConversationListItem extends RelativeLayout
|
|||||||
avatar.setSeenRecently(false);
|
avatar.setSeenRecently(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(@NonNull QrInviteData inviteData, @NonNull GlideRequests glideRequests) {
|
|
||||||
this.selectedThreads = Collections.emptySet();
|
|
||||||
|
|
||||||
fromView.setText(inviteData.getDisplayTitle());
|
|
||||||
fromView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
|
||||||
subjectView.setVisibility(VISIBLE);
|
|
||||||
subjectView.setText(inviteData.getDisplaySubtitle());
|
|
||||||
subjectView.setTypeface(LIGHT_TYPEFACE);
|
|
||||||
subjectView.setTextColor(
|
|
||||||
ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_subject_color));
|
|
||||||
|
|
||||||
dateView.setText("");
|
|
||||||
dateView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
|
||||||
archivedBadgeView.setVisibility(GONE);
|
|
||||||
requestBadgeView.setVisibility(GONE);
|
|
||||||
unreadIndicator.setVisibility(GONE);
|
|
||||||
deliveryStatusIndicator.setNone();
|
|
||||||
|
|
||||||
setBatchState(false);
|
|
||||||
|
|
||||||
if (inviteData.getContactId() > 0) {
|
|
||||||
DcContext dcContext = DcHelper.getContext(getContext());
|
|
||||||
DcContact contact = dcContext.getContact(inviteData.getContactId());
|
|
||||||
Recipient recipient = new Recipient(getContext(), contact);
|
|
||||||
avatar.setAvatar(glideRequests, recipient, false);
|
|
||||||
avatar.setSeenRecently(contact.wasSeenRecently());
|
|
||||||
} else {
|
|
||||||
avatar.setImageDrawable(
|
|
||||||
new GeneratedContactPhoto("+")
|
|
||||||
.asDrawable(getContext(), ThemeUtil.getDummyContactColor(getContext())));
|
|
||||||
avatar.setSeenRecently(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unbind() {}
|
public void unbind() {}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
public class CreateProfileActivity extends BaseActionBarActivity {
|
public class CreateProfileActivity extends BaseActionBarActivity {
|
||||||
|
|
||||||
private static final String TAG = "CreateProfileActivity";
|
private static final String TAG = CreateProfileActivity.class.getSimpleName();
|
||||||
|
|
||||||
private static final int REQUEST_CODE_AVATAR = 1;
|
private static final int REQUEST_CODE_AVATAR = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
|
|
||||||
public class EphemeralMessagesDialog {
|
public class EphemeralMessagesDialog {
|
||||||
|
|
||||||
private static final String TAG = "EphemeralMessagesDialog";
|
private static final String TAG = EphemeralMessagesDialog.class.getSimpleName();
|
||||||
|
|
||||||
public static void show(
|
public static void show(
|
||||||
final Context context,
|
final Context context,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||||
implements ItemClickListener {
|
implements ItemClickListener {
|
||||||
|
|
||||||
private static final String TAG = "GroupCreateActivity";
|
private static final String TAG = GroupCreateActivity.class.getSimpleName();
|
||||||
public static final String EDIT_GROUP_CHAT_ID = "edit_group_chat_id";
|
public static final String EDIT_GROUP_CHAT_ID = "edit_group_chat_id";
|
||||||
public static final String CREATE_BROADCAST = "create_broadcast";
|
public static final String CREATE_BROADCAST = "create_broadcast";
|
||||||
public static final String UNENCRYPTED = "unencrypted";
|
public static final String UNENCRYPTED = "unencrypted";
|
||||||
@@ -307,7 +307,8 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
private boolean showGroupNameEmptyToast(String groupName) {
|
private boolean showGroupNameEmptyToast(String groupName) {
|
||||||
if (groupName == null) {
|
if (groupName == null) {
|
||||||
Toast.makeText(this, getString(R.string.please_enter_chat_name), Toast.LENGTH_LONG).show();
|
Toast.makeText(this, getString(R.string.group_please_enter_group_name), Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ import org.thoughtcrime.securesms.util.views.ProgressDialog;
|
|||||||
public class InstantOnboardingActivity extends BaseActionBarActivity
|
public class InstantOnboardingActivity extends BaseActionBarActivity
|
||||||
implements DcEventCenter.DcEventDelegate {
|
implements DcEventCenter.DcEventDelegate {
|
||||||
|
|
||||||
private static final String TAG = "InstantOnboardingActivity";
|
private static final String TAG = InstantOnboardingActivity.class.getSimpleName();
|
||||||
private static final String DCACCOUNT = "dcaccount";
|
private static final String DCACCOUNT = "dcaccount";
|
||||||
private static final String DCLOGIN = "dclogin";
|
private static final String DCLOGIN = "dclogin";
|
||||||
private static final String INSTANCES_URL = "https://chatmail.at/relays";
|
private static final String INSTANCES_URL = "https://chatmail.at/relays";
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import org.thoughtcrime.securesms.util.FileProviderUtil;
|
|||||||
|
|
||||||
public class LogViewActivity extends BaseActionBarActivity {
|
public class LogViewActivity extends BaseActionBarActivity {
|
||||||
|
|
||||||
private static final String TAG = "LogViewActivity";
|
private static final String TAG = LogViewActivity.class.getSimpleName();
|
||||||
|
|
||||||
LogViewFragment logViewFragment;
|
LogViewFragment logViewFragment;
|
||||||
|
|
||||||
|
|||||||
@@ -33,13 +33,14 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.loader.app.LoaderManager;
|
import androidx.loader.app.LoaderManager;
|
||||||
import androidx.loader.content.Loader;
|
import androidx.loader.content.Loader;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.viewpager.widget.PagerAdapter;
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
import com.b44t.messenger.DcChat;
|
import com.b44t.messenger.DcChat;
|
||||||
import com.b44t.messenger.DcContext;
|
import com.b44t.messenger.DcContext;
|
||||||
import com.b44t.messenger.DcMediaGalleryElement;
|
import com.b44t.messenger.DcMediaGalleryElement;
|
||||||
@@ -47,6 +48,7 @@ import com.b44t.messenger.DcMsg;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
import org.thoughtcrime.securesms.components.MediaView;
|
import org.thoughtcrime.securesms.components.MediaView;
|
||||||
|
import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener;
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
|
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
|
||||||
@@ -67,7 +69,7 @@ import org.thoughtcrime.securesms.util.Util;
|
|||||||
public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||||
implements RecipientModifiedListener, LoaderManager.LoaderCallbacks<DcMediaGalleryElement> {
|
implements RecipientModifiedListener, LoaderManager.LoaderCallbacks<DcMediaGalleryElement> {
|
||||||
|
|
||||||
private static final String TAG = "MediaPreviewActivity";
|
private static final String TAG = MediaPreviewActivity.class.getSimpleName();
|
||||||
|
|
||||||
public static final String ACTIVITY_TITLE_EXTRA = "activity_title";
|
public static final String ACTIVITY_TITLE_EXTRA = "activity_title";
|
||||||
public static final String EDIT_AVATAR_CHAT_ID = "avatar_for_chat_id";
|
public static final String EDIT_AVATAR_CHAT_ID = "avatar_for_chat_id";
|
||||||
@@ -86,7 +88,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
|||||||
@Nullable private DcMsg messageRecord;
|
@Nullable private DcMsg messageRecord;
|
||||||
private DcContext dcContext;
|
private DcContext dcContext;
|
||||||
private MediaItem initialMedia;
|
private MediaItem initialMedia;
|
||||||
private ViewPager2 mediaPager;
|
private ViewPager mediaPager;
|
||||||
private Recipient conversationRecipient;
|
private Recipient conversationRecipient;
|
||||||
private boolean leftIsRecent;
|
private boolean leftIsRecent;
|
||||||
|
|
||||||
@@ -188,7 +190,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
|||||||
private void initializeViews() {
|
private void initializeViews() {
|
||||||
mediaPager = findViewById(R.id.media_pager);
|
mediaPager = findViewById(R.id.media_pager);
|
||||||
mediaPager.setOffscreenPageLimit(1);
|
mediaPager.setOffscreenPageLimit(1);
|
||||||
mediaPager.registerOnPageChangeCallback(new ViewPagerListener());
|
mediaPager.addOnPageChangeListener(new ViewPagerListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeResources() {
|
private void initializeResources() {
|
||||||
@@ -258,7 +260,10 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
private int cleanupMedia() {
|
private int cleanupMedia() {
|
||||||
int restartItem = mediaPager.getCurrentItem();
|
int restartItem = mediaPager.getCurrentItem();
|
||||||
|
|
||||||
|
mediaPager.removeAllViews();
|
||||||
mediaPager.setAdapter(null);
|
mediaPager.setAdapter(null);
|
||||||
|
|
||||||
return restartItem;
|
return restartItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,25 +483,22 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
|||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
DcMediaPagerAdapter adapter =
|
DcMediaPagerAdapter adapter =
|
||||||
new DcMediaPagerAdapter(this, GlideApp.with(this), getWindow(), data, leftIsRecent);
|
new DcMediaPagerAdapter(this, GlideApp.with(this), getWindow(), data, leftIsRecent);
|
||||||
adapter.setActive(true);
|
|
||||||
mediaPager.setAdapter(adapter);
|
mediaPager.setAdapter(adapter);
|
||||||
|
adapter.setActive(true);
|
||||||
|
|
||||||
if (restartItem < 0) mediaPager.setCurrentItem(data.getPosition(), false);
|
if (restartItem < 0) mediaPager.setCurrentItem(data.getPosition());
|
||||||
else mediaPager.setCurrentItem(restartItem, false);
|
else mediaPager.setCurrentItem(restartItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoaderReset(Loader<DcMediaGalleryElement> loader) {}
|
public void onLoaderReset(Loader<DcMediaGalleryElement> loader) {}
|
||||||
|
|
||||||
private class ViewPagerListener extends ViewPager2.OnPageChangeCallback {
|
private class ViewPagerListener extends ExtendedOnPageChangedListener {
|
||||||
|
|
||||||
private Integer currentPage = null;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPageSelected(int position) {
|
public void onPageSelected(int position) {
|
||||||
if (currentPage != null && currentPage != position) onPageUnselected(currentPage);
|
super.onPageSelected(position);
|
||||||
currentPage = position;
|
|
||||||
|
|
||||||
MediaItemAdapter adapter = (MediaItemAdapter) mediaPager.getAdapter();
|
MediaItemAdapter adapter = (MediaItemAdapter) mediaPager.getAdapter();
|
||||||
|
|
||||||
@@ -508,7 +510,8 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPageUnselected(int position) {
|
@Override
|
||||||
|
public void onPageUnselected(int position) {
|
||||||
MediaItemAdapter adapter = (MediaItemAdapter) mediaPager.getAdapter();
|
MediaItemAdapter adapter = (MediaItemAdapter) mediaPager.getAdapter();
|
||||||
|
|
||||||
if (adapter != null) {
|
if (adapter != null) {
|
||||||
@@ -523,9 +526,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class SingleItemPagerAdapter
|
private static class SingleItemPagerAdapter extends PagerAdapter implements MediaItemAdapter {
|
||||||
extends RecyclerView.Adapter<SingleItemPagerAdapter.MediaViewHolder>
|
|
||||||
implements MediaItemAdapter {
|
|
||||||
|
|
||||||
private final GlideRequests glideRequests;
|
private final GlideRequests glideRequests;
|
||||||
private final Window window;
|
private final Window window;
|
||||||
@@ -554,29 +555,37 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getCount() {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
@Override
|
||||||
public MediaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
||||||
return new MediaViewHolder(inflater.inflate(R.layout.media_view_page, parent, false));
|
return view == object;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull MediaViewHolder holder, int position) {
|
public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) {
|
||||||
|
View itemView = inflater.inflate(R.layout.media_view_page, container, false);
|
||||||
|
MediaView mediaView = itemView.findViewById(R.id.media_view);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
holder.mediaView.set(glideRequests, window, uri, name, mediaType, size, true);
|
mediaView.set(glideRequests, window, uri, name, mediaType, size, true);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
container.addView(itemView);
|
||||||
|
|
||||||
|
return itemView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewRecycled(@NonNull MediaViewHolder holder) {
|
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||||
super.onViewRecycled(holder);
|
MediaView mediaView = ((FrameLayout) object).findViewById(R.id.media_view);
|
||||||
holder.mediaView.cleanup();
|
mediaView.cleanup();
|
||||||
|
|
||||||
|
container.removeView((FrameLayout) object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -586,20 +595,9 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pause(int position) {}
|
public void pause(int position) {}
|
||||||
|
|
||||||
static class MediaViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
final MediaView mediaView;
|
|
||||||
|
|
||||||
MediaViewHolder(@NonNull View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
mediaView = itemView.findViewById(R.id.media_view);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DcMediaPagerAdapter
|
private static class DcMediaPagerAdapter extends PagerAdapter implements MediaItemAdapter {
|
||||||
extends RecyclerView.Adapter<DcMediaPagerAdapter.MediaViewHolder>
|
|
||||||
implements MediaItemAdapter {
|
|
||||||
|
|
||||||
private final WeakHashMap<Integer, MediaView> mediaViews = new WeakHashMap<>();
|
private final WeakHashMap<Integer, MediaView> mediaViews = new WeakHashMap<>();
|
||||||
|
|
||||||
@@ -632,20 +630,21 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getCount() {
|
||||||
if (!active) return 0;
|
if (!active) return 0;
|
||||||
else return gallery.getCount();
|
else return gallery.getCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
@Override
|
||||||
public MediaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
||||||
View itemView = LayoutInflater.from(context).inflate(R.layout.media_view_page, parent, false);
|
return view == object;
|
||||||
return new MediaViewHolder(itemView);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull MediaViewHolder holder, int position) {
|
public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) {
|
||||||
|
View itemView =
|
||||||
|
LayoutInflater.from(context).inflate(R.layout.media_view_page, container, false);
|
||||||
|
MediaView mediaView = itemView.findViewById(R.id.media_view);
|
||||||
boolean autoplay = position == autoPlayPosition;
|
boolean autoplay = position == autoPlayPosition;
|
||||||
int cursorPosition = getCursorPosition(position);
|
int cursorPosition = getCursorPosition(position);
|
||||||
|
|
||||||
@@ -657,7 +656,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
holder.mediaView.set(
|
mediaView.set(
|
||||||
glideRequests,
|
glideRequests,
|
||||||
window,
|
window,
|
||||||
Uri.fromFile(msg.getFileAsFile()),
|
Uri.fromFile(msg.getFileAsFile()),
|
||||||
@@ -669,17 +668,19 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
|||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaViews.put(position, holder.mediaView);
|
mediaViews.put(position, mediaView);
|
||||||
|
container.addView(itemView);
|
||||||
|
|
||||||
|
return itemView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewRecycled(@NonNull MediaViewHolder holder) {
|
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||||
super.onViewRecycled(holder);
|
MediaView mediaView = ((FrameLayout) object).findViewById(R.id.media_view);
|
||||||
int pos = holder.getBindingAdapterPosition();
|
mediaView.cleanup();
|
||||||
if (pos != RecyclerView.NO_POSITION) {
|
|
||||||
mediaViews.remove(pos);
|
mediaViews.remove(position);
|
||||||
}
|
container.removeView((FrameLayout) object);
|
||||||
holder.mediaView.cleanup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaItem getMediaItemFor(int position) {
|
public MediaItem getMediaItemFor(int position) {
|
||||||
@@ -709,15 +710,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
|||||||
if (leftIsRecent) return position;
|
if (leftIsRecent) return position;
|
||||||
else return gallery.getCount() - 1 - position;
|
else return gallery.getCount() - 1 - position;
|
||||||
}
|
}
|
||||||
|
|
||||||
static class MediaViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
final MediaView mediaView;
|
|
||||||
|
|
||||||
MediaViewHolder(@NonNull View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
mediaView = itemView.findViewById(R.id.media_view);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MediaItem {
|
private static class MediaItem {
|
||||||
@@ -750,7 +742,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface MediaItemAdapter {
|
interface MediaItemAdapter {
|
||||||
MediaItem getMediaItemFor(int position);
|
MediaItem getMediaItemFor(int position);
|
||||||
|
|
||||||
void pause(int position);
|
void pause(int position);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import org.thoughtcrime.securesms.qr.QrCodeHandler;
|
|||||||
*/
|
*/
|
||||||
public class NewConversationActivity extends ContactSelectionActivity {
|
public class NewConversationActivity extends ContactSelectionActivity {
|
||||||
|
|
||||||
private static final String TAG = "NewConversationActivity";
|
private static final String TAG = NewConversationActivity.class.getSimpleName();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle, boolean ready) {
|
public void onCreate(Bundle bundle, boolean ready) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import org.thoughtcrime.securesms.connect.DcHelper;
|
|||||||
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
||||||
|
|
||||||
public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarActivity {
|
public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarActivity {
|
||||||
private static final String TAG = "PassphraseRequiredActionBarActivity";
|
private static final String TAG = PassphraseRequiredActionBarActivity.class.getSimpleName();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final void onCreate(Bundle savedInstanceState) {
|
protected final void onCreate(Bundle savedInstanceState) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.net.Uri;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@@ -26,11 +25,8 @@ import com.b44t.messenger.DcContact;
|
|||||||
import com.b44t.messenger.DcContext;
|
import com.b44t.messenger.DcContext;
|
||||||
import com.b44t.messenger.DcEvent;
|
import com.b44t.messenger.DcEvent;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
import org.thoughtcrime.securesms.util.Prefs;
|
import org.thoughtcrime.securesms.util.Prefs;
|
||||||
import org.thoughtcrime.securesms.util.ShareUtil;
|
import org.thoughtcrime.securesms.util.ShareUtil;
|
||||||
@@ -40,8 +36,6 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||||
implements DcEventCenter.DcEventDelegate {
|
implements DcEventCenter.DcEventDelegate {
|
||||||
|
|
||||||
private static final String TAG = "ProfileActivity";
|
|
||||||
|
|
||||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||||
public static final String CONTACT_ID_EXTRA = "contact_id";
|
public static final String CONTACT_ID_EXTRA = "contact_id";
|
||||||
|
|
||||||
@@ -404,18 +398,7 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
|||||||
Intent composeIntent = new Intent();
|
Intent composeIntent = new Intent();
|
||||||
DcContact dcContact = dcContext.getContact(contactId);
|
DcContact dcContact = dcContext.getContact(contactId);
|
||||||
if (dcContact.isKeyContact()) {
|
if (dcContact.isKeyContact()) {
|
||||||
try {
|
ShareUtil.setSharedContactId(composeIntent, contactId);
|
||||||
byte[] vcard =
|
|
||||||
rpc.makeVcard(rpc.getSelectedAccountId(), Collections.singletonList(contactId))
|
|
||||||
.getBytes();
|
|
||||||
Uri vcardUri =
|
|
||||||
PersistentBlobProvider.getInstance().create(this, vcard, "text/vcard", "contact.vcf");
|
|
||||||
ArrayList<Uri> uris = new ArrayList<>();
|
|
||||||
uris.add(vcardUri);
|
|
||||||
ShareUtil.setSharedUris(composeIntent, uris);
|
|
||||||
} catch (RpcException e) {
|
|
||||||
Log.e(TAG, "Failed to create vCard for sharing contactId=" + contactId, e);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ShareUtil.setSharedText(composeIntent, dcContact.getAddr());
|
ShareUtil.setSharedText(composeIntent, dcContact.getAddr());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
|||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
public class ProfileAdapter extends RecyclerView.Adapter {
|
public class ProfileAdapter extends RecyclerView.Adapter {
|
||||||
private static final String TAG = "ProfileAdapter";
|
private static final String TAG = ProfileAdapter.class.getSimpleName();
|
||||||
|
|
||||||
public static final int ITEM_AVATAR = 10;
|
public static final int ITEM_AVATAR = 10;
|
||||||
public static final int ITEM_DIVIDER = 20;
|
public static final int ITEM_DIVIDER = 20;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
public class ProfileFragment extends Fragment
|
public class ProfileFragment extends Fragment
|
||||||
implements ProfileAdapter.ItemClickListener, DcEventCenter.DcEventDelegate {
|
implements ProfileAdapter.ItemClickListener, DcEventCenter.DcEventDelegate {
|
||||||
|
|
||||||
private static final String TAG = "ProfileFragment";
|
private static final String TAG = ProfileFragment.class.getSimpleName();
|
||||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||||
public static final String CONTACT_ID_EXTRA = "contact_id";
|
public static final String CONTACT_ID_EXTRA = "contact_id";
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
|||||||
|
|
||||||
public class ResolveMediaTask extends AsyncTask<Uri, Void, Uri> {
|
public class ResolveMediaTask extends AsyncTask<Uri, Void, Uri> {
|
||||||
|
|
||||||
private static final String TAG = "ResolveMediaTask";
|
private static final String TAG = ResolveMediaTask.class.getSimpleName();
|
||||||
|
|
||||||
interface OnMediaResolvedListener {
|
interface OnMediaResolvedListener {
|
||||||
void onMediaResolved(Uri uri);
|
void onMediaResolved(Uri uri);
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ import org.thoughtcrime.securesms.util.ShareUtil;
|
|||||||
*/
|
*/
|
||||||
public class ShareActivity extends PassphraseRequiredActionBarActivity
|
public class ShareActivity extends PassphraseRequiredActionBarActivity
|
||||||
implements ResolveMediaTask.OnMediaResolvedListener {
|
implements ResolveMediaTask.OnMediaResolvedListener {
|
||||||
private static final String TAG = "ShareActivity";
|
private static final String TAG = ShareActivity.class.getSimpleName();
|
||||||
|
|
||||||
public static final String EXTRA_ACC_ID = "acc_id";
|
public static final String EXTRA_ACC_ID = "acc_id";
|
||||||
public static final String EXTRA_CHAT_ID = "chat_id";
|
public static final String EXTRA_CHAT_ID = "chat_id";
|
||||||
@@ -260,12 +260,13 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
|
|||||||
final String addr = extraEmail[0];
|
final String addr = extraEmail[0];
|
||||||
int contactId = dcContext.lookupContactIdByAddr(addr);
|
int contactId = dcContext.lookupContactIdByAddr(addr);
|
||||||
|
|
||||||
if (contactId != 0 || dcContext.getConfigInt(DcHelper.CONFIG_FORCE_ENCRYPTION) == 0) {
|
if (contactId == 0) {
|
||||||
if (contactId == 0) contactId = dcContext.createContact(null, addr);
|
contactId = dcContext.createContact(null, addr);
|
||||||
|
}
|
||||||
|
|
||||||
chatId = dcContext.createChatByContactId(contactId);
|
chatId = dcContext.createChatByContactId(contactId);
|
||||||
accId = dcContext.getAccountId();
|
accId = dcContext.getAccountId();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Intent composeIntent;
|
Intent composeIntent;
|
||||||
if (accId != -1 && chatId > 0) {
|
if (accId != -1 && chatId > 0) {
|
||||||
composeIntent = getBaseShareIntent(ConversationActivity.class);
|
composeIntent = getBaseShareIntent(ConversationActivity.class);
|
||||||
|
|||||||
@@ -18,18 +18,21 @@ public class ShareLocationDialog {
|
|||||||
switch (which) {
|
switch (which) {
|
||||||
default:
|
default:
|
||||||
case 0:
|
case 0:
|
||||||
shareLocationUnit = 30 * 60;
|
shareLocationUnit = 5 * 60;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
shareLocationUnit = 60 * 60;
|
shareLocationUnit = 30 * 60;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
shareLocationUnit = 2 * 60 * 60;
|
shareLocationUnit = 60 * 60;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
shareLocationUnit = 6 * 60 * 60;
|
shareLocationUnit = 2 * 60 * 60;
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
|
shareLocationUnit = 6 * 60 * 60;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
shareLocationUnit = 24 * 60 * 60;
|
shareLocationUnit = 24 * 60 * 60;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
|
|
||||||
public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
||||||
implements SearchView.OnQueryTextListener, WebView.FindListener {
|
implements SearchView.OnQueryTextListener, WebView.FindListener {
|
||||||
private static final String TAG = "WebViewActivity";
|
private static final String TAG = WebViewActivity.class.getSimpleName();
|
||||||
// Regex to extract the host from a URL for IDN conversion.
|
// Regex to extract the host from a URL for IDN conversion.
|
||||||
private static final Pattern URL_PATTERN =
|
private static final Pattern URL_PATTERN =
|
||||||
Pattern.compile("^((?:[a-zA-Z0-9]+://)?)([^/?#]+)(.*)$");
|
Pattern.compile("^((?:[a-zA-Z0-9]+://)?)([^/?#]+)(.*)$");
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import androidx.core.content.pm.ShortcutManagerCompat;
|
|||||||
import androidx.core.graphics.drawable.IconCompat;
|
import androidx.core.graphics.drawable.IconCompat;
|
||||||
import chat.delta.rpc.Rpc;
|
import chat.delta.rpc.Rpc;
|
||||||
import chat.delta.rpc.RpcException;
|
import chat.delta.rpc.RpcException;
|
||||||
import chat.delta.rpc.types.WebxdcMessageInfo;
|
|
||||||
import com.b44t.messenger.DcChat;
|
import com.b44t.messenger.DcChat;
|
||||||
import com.b44t.messenger.DcContext;
|
import com.b44t.messenger.DcContext;
|
||||||
import com.b44t.messenger.DcEvent;
|
import com.b44t.messenger.DcEvent;
|
||||||
@@ -51,6 +50,7 @@ import java.util.HashMap;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import org.thoughtcrime.securesms.connect.AccountManager;
|
||||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
import org.thoughtcrime.securesms.util.IntentUtils;
|
import org.thoughtcrime.securesms.util.IntentUtils;
|
||||||
@@ -59,7 +59,7 @@ import org.thoughtcrime.securesms.util.MediaUtil;
|
|||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcEventDelegate {
|
public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcEventDelegate {
|
||||||
private static final String TAG = "WebxdcActivity";
|
private static final String TAG = WebxdcActivity.class.getSimpleName();
|
||||||
private static final String EXTRA_ACCOUNT_ID = "accountId";
|
private static final String EXTRA_ACCOUNT_ID = "accountId";
|
||||||
private static final String EXTRA_APP_MSG_ID = "appMessageId";
|
private static final String EXTRA_APP_MSG_ID = "appMessageId";
|
||||||
private static final String EXTRA_HIDE_ACTION_BAR = "hideActionBar";
|
private static final String EXTRA_HIDE_ACTION_BAR = "hideActionBar";
|
||||||
@@ -69,14 +69,11 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
|||||||
|
|
||||||
private ValueCallback<Uri[]> filePathCallback;
|
private ValueCallback<Uri[]> filePathCallback;
|
||||||
private DcContext dcContext;
|
private DcContext dcContext;
|
||||||
private int accountId;
|
|
||||||
private Rpc rpc;
|
private Rpc rpc;
|
||||||
private DcMsg dcAppMsg;
|
private DcMsg dcAppMsg;
|
||||||
private String baseURL;
|
private String baseURL;
|
||||||
private String sourceCodeUrl = "";
|
private String sourceCodeUrl = "";
|
||||||
private String selfAddr;
|
private String selfAddr;
|
||||||
private boolean isAppSender;
|
|
||||||
private boolean isBroadcast;
|
|
||||||
private int sendUpdateMaxSize;
|
private int sendUpdateMaxSize;
|
||||||
private int sendUpdateInterval;
|
private int sendUpdateInterval;
|
||||||
private boolean internetAccess = false;
|
private boolean internetAccess = false;
|
||||||
@@ -133,16 +130,12 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
|||||||
private static Intent getWebxdcIntent(
|
private static Intent getWebxdcIntent(
|
||||||
Context context, int msgId, boolean hideActionBar, String href) {
|
Context context, int msgId, boolean hideActionBar, String href) {
|
||||||
DcContext dcContext = DcHelper.getContext(context);
|
DcContext dcContext = DcHelper.getContext(context);
|
||||||
int accountId = dcContext.getAccountId();
|
|
||||||
Intent intent = new Intent(context, WebxdcActivity.class);
|
Intent intent = new Intent(context, WebxdcActivity.class);
|
||||||
intent.setAction(Intent.ACTION_VIEW);
|
intent.setAction(Intent.ACTION_VIEW);
|
||||||
// Unique URI per webxdc instance so FLAG_ACTIVITY_NEW_DOCUMENT can identify the document:
|
intent.putExtra(EXTRA_ACCOUNT_ID, dcContext.getAccountId());
|
||||||
intent.setData(Uri.parse("webxdc://" + accountId + "/" + msgId));
|
|
||||||
intent.putExtra(EXTRA_ACCOUNT_ID, accountId);
|
|
||||||
intent.putExtra(EXTRA_APP_MSG_ID, msgId);
|
intent.putExtra(EXTRA_APP_MSG_ID, msgId);
|
||||||
intent.putExtra(EXTRA_HIDE_ACTION_BAR, hideActionBar);
|
intent.putExtra(EXTRA_HIDE_ACTION_BAR, hideActionBar);
|
||||||
intent.putExtra(EXTRA_HREF, href);
|
intent.putExtra(EXTRA_HREF, href);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,15 +195,16 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
|||||||
|
|
||||||
DcEventCenter eventCenter =
|
DcEventCenter eventCenter =
|
||||||
DcHelper.getEventCenter(WebxdcActivity.this.getApplicationContext());
|
DcHelper.getEventCenter(WebxdcActivity.this.getApplicationContext());
|
||||||
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE, this);
|
eventCenter.addObserver(DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE, this);
|
||||||
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_MSGS_CHANGED, this);
|
eventCenter.addObserver(DcContext.DC_EVENT_MSGS_CHANGED, this);
|
||||||
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_WEBXDC_REALTIME_DATA, this);
|
eventCenter.addObserver(DcContext.DC_EVENT_WEBXDC_REALTIME_DATA, this);
|
||||||
|
|
||||||
int appMessageId = b.getInt(EXTRA_APP_MSG_ID);
|
int appMessageId = b.getInt(EXTRA_APP_MSG_ID);
|
||||||
accountId = b.getInt(EXTRA_ACCOUNT_ID);
|
int accountId = b.getInt(EXTRA_ACCOUNT_ID);
|
||||||
this.dcContext = DcHelper.getContext(getApplicationContext());
|
this.dcContext = DcHelper.getContext(getApplicationContext());
|
||||||
if (accountId != dcContext.getAccountId()) {
|
if (accountId != dcContext.getAccountId()) {
|
||||||
this.dcContext = DcHelper.getAccounts(getApplicationContext()).getAccount(accountId);
|
AccountManager.getInstance().switchAccount(getApplicationContext(), accountId);
|
||||||
|
this.dcContext = DcHelper.getContext(getApplicationContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dcAppMsg = this.dcContext.getMsg(appMessageId);
|
this.dcAppMsg = this.dcContext.getMsg(appMessageId);
|
||||||
@@ -226,13 +220,11 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
|||||||
// (a random-id would also work, but would need maintenance and does not add benefits as we
|
// (a random-id would also work, but would need maintenance and does not add benefits as we
|
||||||
// regard the file-part interceptRequest() only,
|
// regard the file-part interceptRequest() only,
|
||||||
// also a random-id is not that useful for debugging)
|
// also a random-id is not that useful for debugging)
|
||||||
this.baseURL = "https://acc" + accountId + "-msg" + appMessageId + ".localhost";
|
this.baseURL = "https://acc" + dcContext.getAccountId() + "-msg" + appMessageId + ".localhost";
|
||||||
|
|
||||||
WebxdcMessageInfo info;
|
final JSONObject info = this.dcAppMsg.getWebxdcInfo();
|
||||||
try {
|
internetAccess = JsonUtils.optBoolean(info, "internet_access");
|
||||||
info = rpc.getWebxdcInfo(accountId, appMessageId);
|
if ("landscape".equals(JsonUtils.optString(info, "orientation"))) {
|
||||||
|
|
||||||
if ("landscape".equals(info.orientation)) {
|
|
||||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||||
} else {
|
} else {
|
||||||
// enter fullscreen mode if necessary,
|
// enter fullscreen mode if necessary,
|
||||||
@@ -240,18 +232,9 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
|||||||
// onConfigurationChanged() is not triggered
|
// onConfigurationChanged() is not triggered
|
||||||
setScreenMode(getResources().getConfiguration());
|
setScreenMode(getResources().getConfiguration());
|
||||||
}
|
}
|
||||||
|
selfAddr = info.optString("self_addr");
|
||||||
internetAccess = info.internetAccess;
|
sendUpdateMaxSize = info.optInt("send_update_max_size");
|
||||||
selfAddr = info.selfAddr;
|
sendUpdateInterval = info.optInt("send_update_interval");
|
||||||
isAppSender = info.isAppSender;
|
|
||||||
isBroadcast = info.isBroadcast;
|
|
||||||
sendUpdateMaxSize = info.sendUpdateMaxSize;
|
|
||||||
sendUpdateInterval = info.sendUpdateInterval;
|
|
||||||
} catch (RpcException e) { // unexpected error, log it and finish
|
|
||||||
Log.e(TAG, "RPC Error", e);
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleFakeProxy(!internetAccess);
|
toggleFakeProxy(!internetAccess);
|
||||||
|
|
||||||
@@ -332,7 +315,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
|||||||
super.onOptionsItemSelected(item);
|
super.onOptionsItemSelected(item);
|
||||||
int itemId = item.getItemId();
|
int itemId = item.getItemId();
|
||||||
if (itemId == R.id.menu_add_to_home_screen) {
|
if (itemId == R.id.menu_add_to_home_screen) {
|
||||||
addToHomeScreen(this, dcContext, dcAppMsg.getId());
|
addToHomeScreen(this, dcAppMsg.getId());
|
||||||
return true;
|
return true;
|
||||||
} else if (itemId == R.id.webxdc_help) {
|
} else if (itemId == R.id.webxdc_help) {
|
||||||
DcHelper.openHelp(this, "#webxdc");
|
DcHelper.openHelp(this, "#webxdc");
|
||||||
@@ -485,8 +468,6 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleEvent(@NonNull DcEvent event) {
|
public void handleEvent(@NonNull DcEvent event) {
|
||||||
if (event.getAccountId() != accountId) return;
|
|
||||||
|
|
||||||
int eventId = event.getId();
|
int eventId = event.getId();
|
||||||
if ((eventId == DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE
|
if ((eventId == DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE
|
||||||
&& event.getData1Int() == dcAppMsg.getId())) {
|
&& event.getData1Int() == dcAppMsg.getId())) {
|
||||||
@@ -506,22 +487,22 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
|||||||
this.dcContext.getMsg(event.getData2Int()); // msg changed, reload data from db
|
this.dcContext.getMsg(event.getData2Int()); // msg changed, reload data from db
|
||||||
Util.runOnAnyBackgroundThread(
|
Util.runOnAnyBackgroundThread(
|
||||||
() -> {
|
() -> {
|
||||||
try {
|
final JSONObject info = dcAppMsg.getWebxdcInfo();
|
||||||
final WebxdcMessageInfo info =
|
|
||||||
rpc.getWebxdcInfo(dcContext.getAccountId(), dcAppMsg.getId());
|
|
||||||
final DcChat chat = dcContext.getChat(dcAppMsg.getChatId());
|
final DcChat chat = dcContext.getChat(dcAppMsg.getChatId());
|
||||||
Util.runOnMain(() -> updateTitleAndMenu(info, chat));
|
Util.runOnMain(
|
||||||
} catch (RpcException e) {
|
() -> {
|
||||||
Log.e(TAG, "RPC Error", e);
|
updateTitleAndMenu(info, chat);
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTitleAndMenu(WebxdcMessageInfo info, DcChat chat) {
|
private void updateTitleAndMenu(JSONObject info, DcChat chat) {
|
||||||
final String docName = TextUtils.isEmpty(info.document) ? info.name : info.document;
|
final String docName = JsonUtils.optString(info, "document");
|
||||||
getSupportActionBar().setTitle(docName + " – " + chat.getName());
|
final String xdcName = JsonUtils.optString(info, "name");
|
||||||
String currSourceCodeUrl = info.sourceCodeUrl != null ? info.sourceCodeUrl : "";
|
final String currSourceCodeUrl = JsonUtils.optString(info, "source_code_url");
|
||||||
|
getSupportActionBar()
|
||||||
|
.setTitle((docName.isEmpty() ? xdcName : docName) + " – " + chat.getName());
|
||||||
if (!sourceCodeUrl.equals(currSourceCodeUrl)) {
|
if (!sourceCodeUrl.equals(currSourceCodeUrl)) {
|
||||||
sourceCodeUrl = currSourceCodeUrl;
|
sourceCodeUrl = currSourceCodeUrl;
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
@@ -530,7 +511,6 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
|||||||
|
|
||||||
private void showInChat() {
|
private void showInChat() {
|
||||||
Intent intent = new Intent(this, ConversationActivity.class);
|
Intent intent = new Intent(this, ConversationActivity.class);
|
||||||
intent.putExtra(ConversationActivity.ACCOUNT_ID_EXTRA, accountId);
|
|
||||||
intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcAppMsg.getChatId());
|
intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcAppMsg.getChatId());
|
||||||
intent.putExtra(
|
intent.putExtra(
|
||||||
ConversationActivity.STARTING_POSITION_EXTRA,
|
ConversationActivity.STARTING_POSITION_EXTRA,
|
||||||
@@ -539,53 +519,35 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void addToHomeScreen(Activity activity, int msgId) {
|
public static void addToHomeScreen(Activity activity, int msgId) {
|
||||||
addToHomeScreen(activity, DcHelper.getContext(activity), msgId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void addToHomeScreen(Activity activity, DcContext dcContext, int msgId) {
|
|
||||||
Context context = activity.getApplicationContext();
|
Context context = activity.getApplicationContext();
|
||||||
try {
|
try {
|
||||||
Rpc rpc = DcHelper.getRpc(context);
|
DcContext dcContext = DcHelper.getContext(context);
|
||||||
int accountId = dcContext.getAccountId();
|
|
||||||
DcMsg msg = dcContext.getMsg(msgId);
|
DcMsg msg = dcContext.getMsg(msgId);
|
||||||
WebxdcMessageInfo info = rpc.getWebxdcInfo(accountId, msgId);
|
final JSONObject info = msg.getWebxdcInfo();
|
||||||
|
|
||||||
final String docName = TextUtils.isEmpty(info.document) ? info.name : info.document;
|
final String docName = JsonUtils.optString(info, "document");
|
||||||
byte[] blob = msg.getWebxdcBlob(info.icon);
|
final String xdcName = JsonUtils.optString(info, "name");
|
||||||
|
byte[] blob = msg.getWebxdcBlob(JsonUtils.optString(info, "icon"));
|
||||||
ByteArrayInputStream is = new ByteArrayInputStream(blob);
|
ByteArrayInputStream is = new ByteArrayInputStream(blob);
|
||||||
BitmapDrawable drawable = (BitmapDrawable) Drawable.createFromStream(is, "icon");
|
BitmapDrawable drawable = (BitmapDrawable) Drawable.createFromStream(is, "icon");
|
||||||
Bitmap bitmap = drawable.getBitmap();
|
Bitmap bitmap = drawable.getBitmap();
|
||||||
|
|
||||||
ShortcutInfoCompat shortcutInfoCompat =
|
ShortcutInfoCompat shortcutInfoCompat =
|
||||||
new ShortcutInfoCompat.Builder(context, "xdc-" + accountId + "-" + msgId)
|
new ShortcutInfoCompat.Builder(context, "xdc-" + dcContext.getAccountId() + "-" + msgId)
|
||||||
.setShortLabel(docName)
|
.setShortLabel(docName.isEmpty() ? xdcName : docName)
|
||||||
.setIcon(
|
.setIcon(
|
||||||
IconCompat.createWithBitmap(
|
IconCompat.createWithBitmap(
|
||||||
bitmap)) // createWithAdaptiveBitmap() removes decorations but cuts out a too
|
bitmap)) // createWithAdaptiveBitmap() removes decorations but cuts out a too
|
||||||
// small circle and defamiliarize the icon too much
|
// small circle and defamiliarize the icon too much
|
||||||
.setIntent(getWebxdcIntent(context, msgId, false, ""))
|
.setIntents(getWebxdcIntentWithParentStack(context, msgId))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Toast.makeText(context, R.string.one_moment, Toast.LENGTH_SHORT).show();
|
Toast.makeText(context, R.string.one_moment, Toast.LENGTH_SHORT).show();
|
||||||
Util.runOnAnyBackgroundThread(
|
if (!ShortcutManagerCompat.requestPinShortcut(context, shortcutInfoCompat, null)) {
|
||||||
() -> {
|
|
||||||
boolean success;
|
|
||||||
try {
|
|
||||||
success = ShortcutManagerCompat.requestPinShortcut(context, shortcutInfoCompat, null);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "ErrAddToHomescreen: requestPinShortcut() failed", e);
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
if (!success) {
|
|
||||||
Util.runOnMain(
|
|
||||||
() ->
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context, "ErrAddToHomescreen: requestPinShortcut() failed", Toast.LENGTH_LONG)
|
||||||
"ErrAddToHomescreen: requestPinShortcut() failed",
|
.show();
|
||||||
Toast.LENGTH_LONG)
|
|
||||||
.show());
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Toast.makeText(context, "ErrAddToHomescreen: " + e, Toast.LENGTH_LONG).show();
|
Toast.makeText(context, "ErrAddToHomescreen: " + e, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
@@ -655,16 +617,6 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
|||||||
return WebxdcActivity.this.dcContext.getName();
|
return WebxdcActivity.this.dcContext.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@JavascriptInterface
|
|
||||||
public boolean isAppSender() {
|
|
||||||
return WebxdcActivity.this.isAppSender;
|
|
||||||
}
|
|
||||||
|
|
||||||
@JavascriptInterface
|
|
||||||
public boolean isBroadcast() {
|
|
||||||
return WebxdcActivity.this.isBroadcast;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @noinspection unused
|
* @noinspection unused
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import org.thoughtcrime.securesms.util.Util;
|
|||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
public class WebxdcStoreActivity extends PassphraseRequiredActionBarActivity {
|
public class WebxdcStoreActivity extends PassphraseRequiredActionBarActivity {
|
||||||
private static final String TAG = "WebxdcStoreActivity";
|
private static final String TAG = WebxdcStoreActivity.class.getSimpleName();
|
||||||
|
|
||||||
private DcContext dcContext;
|
private DcContext dcContext;
|
||||||
private Rpc rpc;
|
private Rpc rpc;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class WelcomeActivity extends BaseActionBarActivity
|
|||||||
implements DcEventCenter.DcEventDelegate {
|
implements DcEventCenter.DcEventDelegate {
|
||||||
public static final String BACKUP_QR_EXTRA = "backup_qr_extra";
|
public static final String BACKUP_QR_EXTRA = "backup_qr_extra";
|
||||||
public static final int PICK_BACKUP = 20574;
|
public static final int PICK_BACKUP = 20574;
|
||||||
private static final String TAG = "WelcomeActivity";
|
private static final String TAG = WelcomeActivity.class.getSimpleName();
|
||||||
public static final String TMP_BACKUP_FILE = "tmp-backup-file";
|
public static final String TMP_BACKUP_FILE = "tmp-backup-file";
|
||||||
|
|
||||||
private ProgressDialog progressDialog = null;
|
private ProgressDialog progressDialog = null;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
|
|
||||||
public class AccountSelectionListFragment extends DialogFragment
|
public class AccountSelectionListFragment extends DialogFragment
|
||||||
implements DcEventCenter.DcEventDelegate {
|
implements DcEventCenter.DcEventDelegate {
|
||||||
private static final String TAG = "AccountSelectionListFragment";
|
private static final String TAG = AccountSelectionListFragment.class.getSimpleName();
|
||||||
private static final String ARG_SELECT_ONLY = "select_only";
|
private static final String ARG_SELECT_ONLY = "select_only";
|
||||||
private RecyclerView recyclerView;
|
private RecyclerView recyclerView;
|
||||||
private AccountSelectionListAdapter adapter;
|
private AccountSelectionListAdapter adapter;
|
||||||
@@ -157,12 +157,6 @@ public class AccountSelectionListFragment extends DialogFragment
|
|||||||
onSetTag(accountId);
|
onSetTag(accountId);
|
||||||
} else if (itemId == R.id.menu_move_to_top) {
|
} else if (itemId == R.id.menu_move_to_top) {
|
||||||
onMoveToTop(accountId);
|
onMoveToTop(accountId);
|
||||||
} else if (itemId == R.id.menu_mark_all_as_read) {
|
|
||||||
try {
|
|
||||||
DcHelper.getRpc(requireActivity()).marknoticedAllChats(accountId);
|
|
||||||
} catch (RpcException e) {
|
|
||||||
Log.e(TAG, "Error calling rpc.marknoticedAllChats()", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,15 @@ import android.content.Context;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
public abstract class Attachment {
|
public abstract class Attachment {
|
||||||
|
|
||||||
@@ -111,7 +116,14 @@ public abstract class Attachment {
|
|||||||
filename = filename.substring(0, i);
|
filename = filename.substring(0, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return DcHelper.copyToBlobdir(context, getDataUri(), filename, ext);
|
String path = DcHelper.getBlobdirFile(DcHelper.getContext(context), filename, ext);
|
||||||
|
|
||||||
|
// copy content to this file
|
||||||
|
InputStream inputStream = PartAuthority.getAttachmentStream(context, getDataUri());
|
||||||
|
OutputStream outputStream = new FileOutputStream(path);
|
||||||
|
Util.copy(inputStream, outputStream);
|
||||||
|
|
||||||
|
return path;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.util.Prefs;
|
|||||||
|
|
||||||
public class AudioCodec {
|
public class AudioCodec {
|
||||||
|
|
||||||
private static final String TAG = "AudioCodec";
|
private static final String TAG = AudioCodec.class.getSimpleName();
|
||||||
|
|
||||||
private static final int SAMPLE_RATE_BALANCED = 44100;
|
private static final int SAMPLE_RATE_BALANCED = 44100;
|
||||||
private static final int BIT_RATE_BALANCED = 32000;
|
private static final int BIT_RATE_BALANCED = 32000;
|
||||||
@@ -196,7 +196,7 @@ public class AudioCodec {
|
|||||||
setFinished();
|
setFinished();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AudioCodec")
|
AudioCodec.class.getSimpleName())
|
||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import org.thoughtcrime.securesms.util.Util;
|
|||||||
|
|
||||||
public class AudioRecorder {
|
public class AudioRecorder {
|
||||||
|
|
||||||
private static final String TAG = "AudioRecorder";
|
private static final String TAG = AudioRecorder.class.getSimpleName();
|
||||||
|
|
||||||
private static final ExecutorService executor = ThreadUtil.newDynamicSingleThreadedExecutor();
|
private static final ExecutorService executor = ThreadUtil.newDynamicSingleThreadedExecutor();
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.R;
|
|||||||
/** Bottom sheet dialog for selecting audio output device */
|
/** Bottom sheet dialog for selecting audio output device */
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
public class AudioDevicePickerDialog extends BottomSheetDialog {
|
public class AudioDevicePickerDialog extends BottomSheetDialog {
|
||||||
private static final String TAG = "AudioDevicePickerDialog";
|
private static final String TAG = AudioDevicePickerDialog.class.getSimpleName();
|
||||||
|
|
||||||
public interface OnDeviceSelectedListener {
|
public interface OnDeviceSelectedListener {
|
||||||
void onDeviceSelected(CallEndpointCompat endpoint);
|
void onDeviceSelected(CallEndpointCompat endpoint);
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.calls;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.util.Log;
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
|
||||||
public class CallActionReceiver extends BroadcastReceiver {
|
|
||||||
private static final String TAG = "CallActionReceiver";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
if (intent == null) return;
|
|
||||||
|
|
||||||
String action = intent.getAction();
|
|
||||||
Log.d(TAG, "Received action: " + action);
|
|
||||||
|
|
||||||
if (CallActivity.ACTION_DECLINE_CALL.equals(action)) {
|
|
||||||
CallCoordinator.getInstance(context).declineCall();
|
|
||||||
} else if (CallActivity.ACTION_HANGUP_CALL.equals(action)) {
|
|
||||||
CallCoordinator.getInstance(context).hangUp();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,13 +6,11 @@ import android.app.PictureInPictureParams;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.provider.Settings;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Rational;
|
import android.util.Rational;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -24,9 +22,7 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import androidx.activity.OnBackPressedCallback;
|
import androidx.activity.OnBackPressedCallback;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.cardview.widget.CardView;
|
import androidx.cardview.widget.CardView;
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
@@ -55,10 +51,9 @@ import org.webrtc.VideoTrack;
|
|||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
public class CallActivity extends AppCompatActivity {
|
public class CallActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private static final String TAG = "CallActivity";
|
private static final String TAG = CallActivity.class.getSimpleName();
|
||||||
private static final int MIC_PERMISSION_REQUEST_CODE = 1001;
|
private static final int MIC_PERMISSION_REQUEST_CODE = 1001;
|
||||||
private static final int CAMERA_PERMISSION_REQUEST_CODE = 1002;
|
private static final int CAMERA_PERMISSION_REQUEST_CODE = 1002;
|
||||||
private static final int CAMERA_MID_CALL_PERMISSION_REQUEST_CODE = 1003;
|
|
||||||
|
|
||||||
public static final String ACTION_ANSWER_CALL = BuildConfig.APPLICATION_ID + ".ANSWER_CALL";
|
public static final String ACTION_ANSWER_CALL = BuildConfig.APPLICATION_ID + ".ANSWER_CALL";
|
||||||
public static final String ACTION_DECLINE_CALL = BuildConfig.APPLICATION_ID + ".DECLINE_CALL";
|
public static final String ACTION_DECLINE_CALL = BuildConfig.APPLICATION_ID + ".DECLINE_CALL";
|
||||||
@@ -104,7 +99,6 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
private boolean awaitingPermissionResult = false;
|
private boolean awaitingPermissionResult = false;
|
||||||
private boolean pausedWhileAwaitingPermission = false;
|
private boolean pausedWhileAwaitingPermission = false;
|
||||||
private boolean intentHandled = false;
|
private boolean intentHandled = false;
|
||||||
private boolean doNotAutoFinish = false;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -233,7 +227,6 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
Log.d(TAG, "Resuming existing call");
|
Log.d(TAG, "Resuming existing call");
|
||||||
} else if (!coordinator.isIncomingCall()) {
|
} else if (!coordinator.isIncomingCall()) {
|
||||||
Log.d(TAG, "Starting outgoing call");
|
Log.d(TAG, "Starting outgoing call");
|
||||||
coordinator.ensureServiceStarted();
|
|
||||||
viewModel.startOutgoingCallWhenReady();
|
viewModel.startOutgoingCallWhenReady();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -381,18 +374,6 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
videoButton.setOnClickListener(
|
videoButton.setOnClickListener(
|
||||||
v -> {
|
v -> {
|
||||||
if (viewModel != null) {
|
if (viewModel != null) {
|
||||||
Boolean currentEnabled = viewModel.getVideoEnabled().getValue();
|
|
||||||
boolean needToEnable = currentEnabled == null || !currentEnabled;
|
|
||||||
|
|
||||||
if (needToEnable && !hasCameraPermission()) {
|
|
||||||
awaitingPermissionResult = true;
|
|
||||||
ActivityCompat.requestPermissions(
|
|
||||||
this,
|
|
||||||
new String[] {Manifest.permission.CAMERA},
|
|
||||||
CAMERA_MID_CALL_PERMISSION_REQUEST_CODE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.toggleVideo();
|
viewModel.toggleVideo();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -653,7 +634,7 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
new Handler(Looper.getMainLooper())
|
new Handler(Looper.getMainLooper())
|
||||||
.postDelayed(
|
.postDelayed(
|
||||||
() -> {
|
() -> {
|
||||||
if (!isFinishing() && !doNotAutoFinish) {
|
if (!isFinishing()) {
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -662,9 +643,7 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
case ENDED:
|
case ENDED:
|
||||||
statusText.setText(R.string.call_ended);
|
statusText.setText(R.string.call_ended);
|
||||||
if (!doNotAutoFinish) {
|
|
||||||
finish();
|
finish();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ERROR:
|
case ERROR:
|
||||||
@@ -677,7 +656,7 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
new Handler(Looper.getMainLooper())
|
new Handler(Looper.getMainLooper())
|
||||||
.postDelayed(
|
.postDelayed(
|
||||||
() -> {
|
() -> {
|
||||||
if (!isFinishing() && !doNotAutoFinish) {
|
if (!isFinishing()) {
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -863,28 +842,16 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleMicPermissionDenied() {
|
private void handleMicPermissionDenied() {
|
||||||
CallCoordinator coordinator = CallCoordinator.getInstance(getApplication());
|
|
||||||
|
|
||||||
if (coordinator.hasActiveCall() && !coordinator.hasOngoingCall()) {
|
|
||||||
if (coordinator.isIncomingCall()) {
|
|
||||||
coordinator.declineCall();
|
|
||||||
} else {
|
|
||||||
coordinator.hangUp();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO)) {
|
|
||||||
doNotAutoFinish = true;
|
|
||||||
showPermissionSettingsDialog(
|
|
||||||
getString(R.string.call_requires_mic_permission),
|
|
||||||
() -> {
|
|
||||||
doNotAutoFinish = false;
|
|
||||||
if (!isFinishing()) finish();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this, R.string.call_requires_mic_permission, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.call_requires_mic_permission, Toast.LENGTH_LONG).show();
|
||||||
finish();
|
|
||||||
|
CallCoordinator coordinator = CallCoordinator.getInstance(getApplication());
|
||||||
|
if (coordinator.hasActiveCall()
|
||||||
|
&& coordinator.isIncomingCall()
|
||||||
|
&& !coordinator.hasOngoingCall()) {
|
||||||
|
coordinator.declineCall();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void proceedAfterPermissions() {
|
private void proceedAfterPermissions() {
|
||||||
@@ -911,22 +878,6 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
CallCoordinator coordinator = CallCoordinator.getInstance(getApplication());
|
CallCoordinator coordinator = CallCoordinator.getInstance(getApplication());
|
||||||
|
|
||||||
if (requestCode == CAMERA_MID_CALL_PERMISSION_REQUEST_CODE) {
|
|
||||||
boolean cameraGranted =
|
|
||||||
grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
|
||||||
|
|
||||||
if (cameraGranted && viewModel != null) {
|
|
||||||
viewModel.toggleVideo();
|
|
||||||
} else {
|
|
||||||
if (!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
|
|
||||||
showPermissionSettingsDialog(getString(R.string.call_requires_camera_permission), null);
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this, R.string.call_requires_camera_permission, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestCode == MIC_PERMISSION_REQUEST_CODE) {
|
if (requestCode == MIC_PERMISSION_REQUEST_CODE) {
|
||||||
boolean micGranted =
|
boolean micGranted =
|
||||||
grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
||||||
@@ -942,17 +893,9 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
if (!cameraGranted) {
|
if (!cameraGranted) {
|
||||||
Log.w(TAG, "Camera permission denied, switching to audio-only");
|
Log.w(TAG, "Camera permission denied, switching to audio-only");
|
||||||
if (!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
|
|
||||||
Toast.makeText(
|
|
||||||
this,
|
|
||||||
"Camera permission permanently denied. Enable in app settings for video calls.",
|
|
||||||
Toast.LENGTH_LONG)
|
|
||||||
.show();
|
|
||||||
} else {
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this, "Starting audio-only call (camera permission denied)", Toast.LENGTH_SHORT)
|
this, "Starting audio-only call (camera permission denied)", Toast.LENGTH_SHORT)
|
||||||
.show();
|
.show();
|
||||||
}
|
|
||||||
coordinator.setStartsWithVideo(false);
|
coordinator.setStartsWithVideo(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -960,34 +903,6 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
proceedAfterPermissions();
|
proceedAfterPermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showPermissionSettingsDialog(String message, @Nullable Runnable onDismissAction) {
|
|
||||||
if (isFinishing() || isDestroyed()) {
|
|
||||||
if (onDismissAction != null) onDismissAction.run();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
new AlertDialog.Builder(this)
|
|
||||||
.setTitle("Permission Required")
|
|
||||||
.setMessage(message)
|
|
||||||
.setPositiveButton(
|
|
||||||
"Open Settings",
|
|
||||||
(dialog, which) -> {
|
|
||||||
Intent intent =
|
|
||||||
new Intent(
|
|
||||||
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
|
||||||
Uri.fromParts("package", getPackageName(), null));
|
|
||||||
startActivity(intent);
|
|
||||||
})
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.setOnDismissListener(
|
|
||||||
dialog -> {
|
|
||||||
if (onDismissAction != null) {
|
|
||||||
onDismissAction.run();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Picture-in-Picture
|
// Picture-in-Picture
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import android.os.Looper;
|
|||||||
import android.telecom.DisconnectCause;
|
import android.telecom.DisconnectCause;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
@@ -33,7 +32,6 @@ import androidx.core.telecom.CallControlScope;
|
|||||||
import androidx.core.telecom.CallEndpointCompat;
|
import androidx.core.telecom.CallEndpointCompat;
|
||||||
import androidx.core.telecom.CallException;
|
import androidx.core.telecom.CallException;
|
||||||
import androidx.core.telecom.CallsManager;
|
import androidx.core.telecom.CallsManager;
|
||||||
import androidx.core.util.Pair;
|
|
||||||
import androidx.lifecycle.FlowLiveDataConversions;
|
import androidx.lifecycle.FlowLiveDataConversions;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MediatorLiveData;
|
import androidx.lifecycle.MediatorLiveData;
|
||||||
@@ -65,7 +63,7 @@ import org.webrtc.VideoTrack;
|
|||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||||
private static final String TAG = "CallCoordinator";
|
private static final String TAG = CallCoordinator.class.getSimpleName();
|
||||||
|
|
||||||
// Notification channels
|
// Notification channels
|
||||||
private static final String CHANNEL_ID_INCOMING = "voip_incoming_calls";
|
private static final String CHANNEL_ID_INCOMING = "voip_incoming_calls";
|
||||||
@@ -99,7 +97,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
private final MutableLiveData<Boolean> outgoingCallPlaced = new MutableLiveData<>(false);
|
private final MutableLiveData<Boolean> outgoingCallPlaced = new MutableLiveData<>(false);
|
||||||
private final MutableLiveData<Boolean> answeredElsewhere = new MutableLiveData<>(false);
|
private final MutableLiveData<Boolean> answeredElsewhere = new MutableLiveData<>(false);
|
||||||
private final MutableLiveData<Boolean> isFrontCamera = new MutableLiveData<>(true);
|
private final MutableLiveData<Boolean> isFrontCamera = new MutableLiveData<>(true);
|
||||||
private final MutableLiveData<Boolean> mediaCaptureReady = new MutableLiveData<>(false);
|
|
||||||
|
|
||||||
// Audio Routing Support
|
// Audio Routing Support
|
||||||
private final MediatorLiveData<CallEndpointCompat> currentAudioEndpoint =
|
private final MediatorLiveData<CallEndpointCompat> currentAudioEndpoint =
|
||||||
@@ -122,7 +119,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
private String pendingOfferSdp;
|
private String pendingOfferSdp;
|
||||||
private boolean hasNotifiedBackend = false;
|
private boolean hasNotifiedBackend = false;
|
||||||
private boolean hasAutoSelectedEarpiece = false;
|
private boolean hasAutoSelectedEarpiece = false;
|
||||||
private boolean pendingMediaCapture = false;
|
|
||||||
|
|
||||||
private CallControlScope activeCallControlScope;
|
private CallControlScope activeCallControlScope;
|
||||||
private CallViewModel activeCallViewModel;
|
private CallViewModel activeCallViewModel;
|
||||||
@@ -184,29 +180,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void disconnectTelecom(DisconnectCause cause) {
|
|
||||||
CallControlScope scope = activeCallControlScope;
|
|
||||||
if (scope == null) {
|
|
||||||
Log.d(TAG, "No active CallControlScope, skipping disconnect");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.disconnect(
|
|
||||||
cause,
|
|
||||||
new Continuation<CallControlResult>() {
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public CoroutineContext getContext() {
|
|
||||||
return EmptyCoroutineContext.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resumeWith(@NonNull Object result) {
|
|
||||||
Log.d(TAG, "Telecom disconnect completed: " + result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addEventListeners() {
|
private void addEventListeners() {
|
||||||
DcEventCenter eventCenter = DcHelper.getEventCenter(this.appContext);
|
DcEventCenter eventCenter = DcHelper.getEventCenter(this.appContext);
|
||||||
eventCenter.removeObservers(this);
|
eventCenter.removeObservers(this);
|
||||||
@@ -242,8 +215,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
calleeName = "Unknown";
|
calleeName = "Unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
showOrUpdateOngoingNotification(
|
showOrUpdateOngoingNotification("Calling " + calleeName + "...");
|
||||||
appContext.getString(R.string.calling_person, calleeName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize call
|
// Initialize call
|
||||||
@@ -255,11 +227,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
mainHandler.removeCallbacks(outgoingRingtoneRunnable);
|
mainHandler.removeCallbacks(outgoingRingtoneRunnable);
|
||||||
mainHandler.postDelayed(outgoingRingtoneRunnable, 1500);
|
mainHandler.postDelayed(outgoingRingtoneRunnable, 1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pendingMediaCapture) {
|
|
||||||
pendingMediaCapture = false;
|
|
||||||
callService.startMediaCapture();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,10 +335,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
return isFrontCamera;
|
return isFrontCamera;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Boolean> getMediaCaptureReady() {
|
|
||||||
return mediaCaptureReady;
|
|
||||||
}
|
|
||||||
|
|
||||||
// State Update Methods (CallService)
|
// State Update Methods (CallService)
|
||||||
|
|
||||||
public void updateConnectionState(PeerConnection.PeerConnectionState state) {
|
public void updateConnectionState(PeerConnection.PeerConnectionState state) {
|
||||||
@@ -411,11 +374,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
isFrontCamera.postValue(front);
|
isFrontCamera.postValue(front);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateMediaCaptureReady(boolean ready) {
|
|
||||||
Log.d(TAG, "updateMediaCaptureReady: " + ready);
|
|
||||||
mediaCaptureReady.postValue(ready);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reportError(String error) {
|
public void reportError(String error) {
|
||||||
Log.e(TAG, "reportError: " + error);
|
Log.e(TAG, "reportError: " + error);
|
||||||
errorMessage.postValue(error);
|
errorMessage.postValue(error);
|
||||||
@@ -428,8 +386,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
if (callService != null) {
|
if (callService != null) {
|
||||||
callService.startMediaCapture();
|
callService.startMediaCapture();
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Service not ready, deferring media capture");
|
Log.w(TAG, "Cannot start media capture, service not ready");
|
||||||
pendingMediaCapture = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,15 +402,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
callService.stopRingtone();
|
callService.stopRingtone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Promote the service to foreground immediately. Waiting until onIncomingCallAccepted
|
|
||||||
// on executor pool thread is too late on stricter OEM.
|
|
||||||
//
|
|
||||||
// Do not cancel() but use showOrUpdateOngoingNotification to replace incoming
|
|
||||||
// notification without a gap.
|
|
||||||
String callerName = displayName.getValue();
|
|
||||||
if (callerName == null) callerName = "Unknown";
|
|
||||||
showOrUpdateOngoingNotification(appContext.getString(R.string.call_with, callerName));
|
|
||||||
|
|
||||||
// Notify Android system with CallControlScope
|
// Notify Android system with CallControlScope
|
||||||
CallControlScope scope = activeCallControlScope;
|
CallControlScope scope = activeCallControlScope;
|
||||||
if (scope != null) {
|
if (scope != null) {
|
||||||
@@ -477,6 +425,8 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notificationManager.cancel(NOTIFICATION_ID_CALL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void answerWebRTC() {
|
public void answerWebRTC() {
|
||||||
@@ -604,7 +554,28 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
|
|
||||||
notifyBackendCallEnded();
|
notifyBackendCallEnded();
|
||||||
|
|
||||||
disconnectTelecom(new DisconnectCause(DisconnectCause.REJECTED));
|
// Disconnect with CallControlScope
|
||||||
|
CallControlScope scope = activeCallControlScope;
|
||||||
|
if (scope != null) {
|
||||||
|
scope.disconnect(
|
||||||
|
new DisconnectCause(DisconnectCause.REJECTED),
|
||||||
|
new Continuation<CallControlResult>() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public CoroutineContext getContext() {
|
||||||
|
return EmptyCoroutineContext.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resumeWith(@NonNull Object result) {
|
||||||
|
if (result instanceof CallControlResult) {
|
||||||
|
Log.d(TAG, "Decline succeeded with CallControlScope");
|
||||||
|
} else if (result instanceof kotlin.Result.Failure) {
|
||||||
|
Log.e(TAG, "Decline failed", ((kotlin.Result.Failure) result).exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// End call on service
|
// End call on service
|
||||||
if (callService != null) {
|
if (callService != null) {
|
||||||
@@ -625,7 +596,28 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
|
|
||||||
notifyBackendCallEnded();
|
notifyBackendCallEnded();
|
||||||
|
|
||||||
disconnectTelecom(new DisconnectCause(DisconnectCause.LOCAL));
|
// Disconnect with CallControlScope
|
||||||
|
CallControlScope scope = activeCallControlScope;
|
||||||
|
if (scope != null) {
|
||||||
|
scope.disconnect(
|
||||||
|
new DisconnectCause(DisconnectCause.LOCAL),
|
||||||
|
new Continuation<CallControlResult>() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public CoroutineContext getContext() {
|
||||||
|
return EmptyCoroutineContext.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resumeWith(@NonNull Object result) {
|
||||||
|
if (result instanceof CallControlResult) {
|
||||||
|
Log.d(TAG, "Hang up succeeded with CallControlScope");
|
||||||
|
} else if (result instanceof kotlin.Result.Failure) {
|
||||||
|
Log.e(TAG, "Hang up failed", ((kotlin.Result.Failure) result).exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// End call on service
|
// End call on service
|
||||||
if (callService != null) {
|
if (callService != null) {
|
||||||
@@ -651,17 +643,11 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
public synchronized void setVideoEnabled(boolean enabled) {
|
public synchronized void setVideoEnabled(boolean enabled) {
|
||||||
Log.d(TAG, "setVideoEnabled: " + enabled);
|
Log.d(TAG, "setVideoEnabled: " + enabled);
|
||||||
|
|
||||||
if (callService != null) {
|
|
||||||
boolean success = callService.setVideoEnabled(enabled);
|
|
||||||
if (!success && enabled) {
|
|
||||||
enabled = false;
|
|
||||||
reportError("Camera unavailable");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
localVideoEnabled.postValue(enabled);
|
localVideoEnabled.postValue(enabled);
|
||||||
|
|
||||||
if (callService != null) {
|
if (callService != null) {
|
||||||
|
callService.setVideoEnabled(enabled);
|
||||||
|
|
||||||
callService.sendMutedState(Boolean.TRUE.equals(localAudioEnabled.getValue()), enabled);
|
callService.sendMutedState(Boolean.TRUE.equals(localAudioEnabled.getValue()), enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -881,15 +867,32 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
int accId, int callId, String offerSdp, boolean startsWithVideo) {
|
int accId, int callId, String offerSdp, boolean startsWithVideo) {
|
||||||
Log.d(TAG, "onIncomingCall: accId=" + accId + ", callId=" + callId);
|
Log.d(TAG, "onIncomingCall: accId=" + accId + ", callId=" + callId);
|
||||||
|
|
||||||
Pair<DcChat, String> result = setupIncomingCallState(accId, callId, offerSdp, startsWithVideo);
|
if (hasActiveCall()) {
|
||||||
if (result == null) return;
|
Log.w(TAG, "Already have an active call, ignoring incoming call");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
DcChat dcChat = result.first;
|
resetLiveDataForNewCall();
|
||||||
String callerName = result.second;
|
|
||||||
|
this.activeAccId = accId;
|
||||||
|
this.activeCallId = callId;
|
||||||
|
this.isIncomingCall = true;
|
||||||
|
this.startsWithVideo = startsWithVideo;
|
||||||
|
this.pendingOfferSdp = offerSdp;
|
||||||
|
|
||||||
|
// Get caller info
|
||||||
|
DcContext dcContext = ApplicationContext.getDcAccounts().getAccount(accId);
|
||||||
|
int chatId = dcContext.getMsg(callId).getChatId();
|
||||||
|
this.activeChatId = chatId;
|
||||||
|
DcChat dcChat = dcContext.getChat(chatId);
|
||||||
|
String callerName = getNameFromChat(dcChat);
|
||||||
Icon callerIcon = getIconFromChat(this.appContext, dcChat);
|
Icon callerIcon = getIconFromChat(this.appContext, dcChat);
|
||||||
|
|
||||||
|
displayName.postValue(callerName);
|
||||||
displayIcon.postValue(callerIcon);
|
displayIcon.postValue(callerIcon);
|
||||||
|
|
||||||
|
this.preferredStartingEndpoint = getPreferredStartingEndpoint(startsWithVideo);
|
||||||
|
|
||||||
// Add to CallsManager
|
// Add to CallsManager
|
||||||
CallAttributesCompat callAttributes = createCallAttributes(callerName, callId, true);
|
CallAttributesCompat callAttributes = createCallAttributes(callerName, callId, true);
|
||||||
addCallToTelecom(callAttributes, callerName, callerIcon);
|
addCallToTelecom(callAttributes, callerName, callerIcon);
|
||||||
@@ -900,37 +903,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
startAndBindService();
|
startAndBindService();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void handleIncomingCallFromConversation(
|
|
||||||
int accId, int callId, String offerSdp, boolean hasVideo) {
|
|
||||||
Log.d(TAG, "handleIncomingCallFromConversation: accId=" + accId + ", callId=" + callId);
|
|
||||||
|
|
||||||
if (offerSdp == null || offerSdp.isEmpty()) {
|
|
||||||
Log.e(TAG, "Cannot start incoming call: no SDP offer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Pair<DcChat, String> result = setupIncomingCallState(accId, callId, offerSdp, hasVideo);
|
|
||||||
if (result == null) return;
|
|
||||||
|
|
||||||
DcChat dcChat = result.first;
|
|
||||||
String callerName = result.second;
|
|
||||||
|
|
||||||
new Thread(
|
|
||||||
() -> {
|
|
||||||
Icon callerIcon = getIconFromChat(this.appContext, dcChat);
|
|
||||||
displayIcon.postValue(callerIcon);
|
|
||||||
})
|
|
||||||
.start();
|
|
||||||
|
|
||||||
// Add to CallsManager
|
|
||||||
CallAttributesCompat callAttributes = createCallAttributes(callerName, callId, true);
|
|
||||||
addCallToTelecom(callAttributes, callerName, null);
|
|
||||||
|
|
||||||
startAndBindService();
|
|
||||||
|
|
||||||
launchCallActivity();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void onIncomingCallAccepted(int callId, boolean fromThisDevice) {
|
private synchronized void onIncomingCallAccepted(int callId, boolean fromThisDevice) {
|
||||||
Log.d(TAG, "onIncomingCallAccepted: callId=" + callId + ", fromThisDevice=" + fromThisDevice);
|
Log.d(TAG, "onIncomingCallAccepted: callId=" + callId + ", fromThisDevice=" + fromThisDevice);
|
||||||
|
|
||||||
@@ -949,7 +921,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
callerName = "Unknown";
|
callerName = "Unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
showOrUpdateOngoingNotification(appContext.getString(R.string.call_with, callerName));
|
showOrUpdateOngoingNotification("Call with " + callerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void onCallAnsweredOnOtherDevice() {
|
private synchronized void onCallAnsweredOnOtherDevice() {
|
||||||
@@ -972,7 +944,24 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
|
|
||||||
answeredElsewhere.postValue(true);
|
answeredElsewhere.postValue(true);
|
||||||
|
|
||||||
disconnectTelecom(new DisconnectCause(DisconnectCause.REMOTE));
|
// Disconnect from Telecom CallControlScope
|
||||||
|
CallControlScope scope = activeCallControlScope;
|
||||||
|
if (scope != null) {
|
||||||
|
scope.disconnect(
|
||||||
|
new DisconnectCause(DisconnectCause.REMOTE),
|
||||||
|
new Continuation<CallControlResult>() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public CoroutineContext getContext() {
|
||||||
|
return EmptyCoroutineContext.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resumeWith(@NonNull Object result) {
|
||||||
|
Log.d(TAG, "Disconnect (answered elsewhere) completed");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (callService != null) {
|
if (callService != null) {
|
||||||
callService.endCall();
|
callService.endCall();
|
||||||
@@ -1019,7 +1008,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
if (calleeName == null) {
|
if (calleeName == null) {
|
||||||
calleeName = "Unknown";
|
calleeName = "Unknown";
|
||||||
}
|
}
|
||||||
showOrUpdateOngoingNotification(appContext.getString(R.string.call_with, calleeName));
|
showOrUpdateOngoingNotification("Call with " + calleeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void onCallEnded(int accId, int callId) {
|
private synchronized void onCallEnded(int accId, int callId) {
|
||||||
@@ -1050,7 +1039,25 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
callService.stopRingtone();
|
callService.stopRingtone();
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectTelecom(new DisconnectCause(DisconnectCause.REMOTE));
|
// Disconnect from CallControlScope
|
||||||
|
if (activeCallControlScope != null) {
|
||||||
|
activeCallControlScope.disconnect(
|
||||||
|
// We actually don't know if this is incoming or outgoing
|
||||||
|
// But we have to provide one of LOCAL, REMOTE, MISSED, REJECTED
|
||||||
|
new DisconnectCause(DisconnectCause.REMOTE),
|
||||||
|
new Continuation<CallControlResult>() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public CoroutineContext getContext() {
|
||||||
|
return EmptyCoroutineContext.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resumeWith(@NonNull Object result) {
|
||||||
|
Log.d(TAG, "Disconnect completed");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (callService != null) {
|
if (callService != null) {
|
||||||
callService.endCall();
|
callService.endCall();
|
||||||
@@ -1074,18 +1081,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
|
|
||||||
notifyBackendCallEnded();
|
notifyBackendCallEnded();
|
||||||
|
|
||||||
DisconnectCause cause;
|
|
||||||
if (state == PeerConnection.PeerConnectionState.FAILED) {
|
|
||||||
cause = new DisconnectCause(DisconnectCause.REMOTE, "PeerConnection failed");
|
|
||||||
} else {
|
|
||||||
cause = new DisconnectCause(DisconnectCause.LOCAL, "PeerConnection closed");
|
|
||||||
}
|
|
||||||
disconnectTelecom(cause);
|
|
||||||
|
|
||||||
if (callService != null) {
|
|
||||||
callService.endCall();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
if (hasActiveCall()) {
|
if (hasActiveCall()) {
|
||||||
cleanupCall(activeAccId, activeCallId);
|
cleanupCall(activeAccId, activeCallId);
|
||||||
@@ -1106,14 +1101,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callService != null) {
|
|
||||||
try {
|
|
||||||
callService.stopForegroundAndDismiss();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, "stopForegroundAndDismiss failed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear state
|
// Clear state
|
||||||
this.activeAccId = null;
|
this.activeAccId = null;
|
||||||
this.activeCallId = null;
|
this.activeCallId = null;
|
||||||
@@ -1126,7 +1113,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
this.pendingOfferSdp = null;
|
this.pendingOfferSdp = null;
|
||||||
this.hasNotifiedBackend = false;
|
this.hasNotifiedBackend = false;
|
||||||
this.hasAutoSelectedEarpiece = false;
|
this.hasAutoSelectedEarpiece = false;
|
||||||
this.pendingMediaCapture = false;
|
|
||||||
|
|
||||||
mainHandler.removeCallbacks(outgoingRingtoneRunnable);
|
mainHandler.removeCallbacks(outgoingRingtoneRunnable);
|
||||||
|
|
||||||
@@ -1192,7 +1178,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
private void resetLiveDataForNewCall() {
|
private void resetLiveDataForNewCall() {
|
||||||
connectionState.postValue(PeerConnection.PeerConnectionState.NEW);
|
connectionState.postValue(PeerConnection.PeerConnectionState.NEW);
|
||||||
answeredElsewhere.postValue(false); // clearLiveData() must not reset answeredElsewhere
|
answeredElsewhere.postValue(false); // clearLiveData() must not reset answeredElsewhere
|
||||||
mediaCaptureReady.postValue(false);
|
|
||||||
clearLiveData();
|
clearLiveData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1208,7 +1193,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
outgoingCallPlaced.postValue(false);
|
outgoingCallPlaced.postValue(false);
|
||||||
currentAudioEndpoint.postValue(null);
|
currentAudioEndpoint.postValue(null);
|
||||||
availableAudioEndpoints.postValue(null);
|
availableAudioEndpoints.postValue(null);
|
||||||
mediaCaptureReady.postValue(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void initiateOutgoingCall(int accId, int chatId, boolean startsWithVideo) {
|
public synchronized void initiateOutgoingCall(int accId, int chatId, boolean startsWithVideo) {
|
||||||
@@ -1219,6 +1203,16 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check microphone permission
|
||||||
|
if (!hasMicrophonePermission()) {
|
||||||
|
Log.e(TAG, "Microphone permission not granted");
|
||||||
|
|
||||||
|
Intent intent = new Intent(appContext, CallActivity.class);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
appContext.startActivity(intent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
resetLiveDataForNewCall();
|
resetLiveDataForNewCall();
|
||||||
|
|
||||||
this.activeCallId = -1; // Placeholder call ID for Intent
|
this.activeCallId = -1; // Placeholder call ID for Intent
|
||||||
@@ -1244,9 +1238,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
|
|
||||||
this.preferredStartingEndpoint = getPreferredStartingEndpoint(startsWithVideo);
|
this.preferredStartingEndpoint = getPreferredStartingEndpoint(startsWithVideo);
|
||||||
|
|
||||||
if (hasMicrophonePermission()) {
|
|
||||||
startAndBindService();
|
startAndBindService();
|
||||||
}
|
|
||||||
|
|
||||||
launchCallActivity();
|
launchCallActivity();
|
||||||
}
|
}
|
||||||
@@ -1278,43 +1270,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
addCallToTelecom(callAttributes, calleeName, calleeIcon);
|
addCallToTelecom(callAttributes, calleeName, calleeIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private Pair<DcChat, String> setupIncomingCallState(
|
|
||||||
int accId, int callId, String offerSdp, boolean startsWithVideo) {
|
|
||||||
if (hasActiveCall()) {
|
|
||||||
Log.w(TAG, "Already have an active call, ignoring incoming call");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
resetLiveDataForNewCall();
|
|
||||||
|
|
||||||
this.activeAccId = accId;
|
|
||||||
this.activeCallId = callId;
|
|
||||||
this.isIncomingCall = true;
|
|
||||||
this.startsWithVideo = startsWithVideo;
|
|
||||||
this.pendingOfferSdp = offerSdp;
|
|
||||||
|
|
||||||
DcContext dcContext = ApplicationContext.getDcAccounts().getAccount(accId);
|
|
||||||
int chatId = dcContext.getMsg(callId).getChatId();
|
|
||||||
this.activeChatId = chatId;
|
|
||||||
DcChat dcChat = dcContext.getChat(chatId);
|
|
||||||
String callerName = getNameFromChat(dcChat);
|
|
||||||
|
|
||||||
displayName.postValue(callerName);
|
|
||||||
|
|
||||||
this.preferredStartingEndpoint = getPreferredStartingEndpoint(startsWithVideo);
|
|
||||||
|
|
||||||
return new Pair<>(dcChat, callerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void ensureServiceStarted() {
|
|
||||||
if (isServiceBound || !hasActiveCall()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Log.d(TAG, "Starting service after permission grant");
|
|
||||||
startAndBindService();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addCallToTelecom(
|
private void addCallToTelecom(
|
||||||
CallAttributesCompat callAttributes, String displayName, Icon icon) {
|
CallAttributesCompat callAttributes, String displayName, Icon icon) {
|
||||||
try {
|
try {
|
||||||
@@ -1422,11 +1377,12 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
|
||||||
// Decline intent
|
// Decline intent
|
||||||
Intent declineIntent = new Intent(this.appContext, CallActionReceiver.class);
|
Intent declineIntent = new Intent(this.appContext, CallActivity.class);
|
||||||
declineIntent.setAction(CallActivity.ACTION_DECLINE_CALL);
|
declineIntent.setAction(CallActivity.ACTION_DECLINE_CALL);
|
||||||
|
declineIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
|
||||||
PendingIntent declinePendingIntent =
|
PendingIntent declinePendingIntent =
|
||||||
PendingIntent.getBroadcast(
|
PendingIntent.getActivity(
|
||||||
this.appContext,
|
this.appContext,
|
||||||
1,
|
1,
|
||||||
declineIntent,
|
declineIntent,
|
||||||
@@ -1495,11 +1451,12 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
|||||||
Intent activityIntent = new Intent(this.appContext, CallActivity.class);
|
Intent activityIntent = new Intent(this.appContext, CallActivity.class);
|
||||||
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
|
||||||
Intent hangupIntent = new Intent(this.appContext, CallActionReceiver.class);
|
Intent hangupIntent = new Intent(this.appContext, CallActivity.class);
|
||||||
hangupIntent.setAction(CallActivity.ACTION_HANGUP_CALL);
|
hangupIntent.setAction(CallActivity.ACTION_HANGUP_CALL);
|
||||||
|
hangupIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
|
||||||
PendingIntent hangupPendingIntent =
|
PendingIntent hangupPendingIntent =
|
||||||
PendingIntent.getBroadcast(
|
PendingIntent.getActivity(
|
||||||
this.appContext,
|
this.appContext,
|
||||||
3,
|
3,
|
||||||
hangupIntent,
|
hangupIntent,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.app.Notification;
|
|||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ServiceInfo;
|
|
||||||
import android.media.AudioAttributes;
|
import android.media.AudioAttributes;
|
||||||
import android.media.AudioFocusRequest;
|
import android.media.AudioFocusRequest;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
@@ -14,9 +13,7 @@ import android.media.ToneGenerator;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Looper;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
@@ -36,7 +33,7 @@ import org.webrtc.VideoTrack;
|
|||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
public class CallService extends Service implements WebRTCClient.Callbacks {
|
public class CallService extends Service implements WebRTCClient.Callbacks {
|
||||||
|
|
||||||
private static final String TAG = "CallService";
|
private static final String TAG = CallService.class.getSimpleName();
|
||||||
|
|
||||||
private final IBinder binder = new LocalBinder();
|
private final IBinder binder = new LocalBinder();
|
||||||
|
|
||||||
@@ -126,13 +123,13 @@ public class CallService extends Service implements WebRTCClient.Callbacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start media capture
|
* Start camera/microphone capture
|
||||||
*
|
*
|
||||||
* <p>Must be called when app is in foreground. Called by coordinator when ViewModel/Activity is
|
* <p>Must be called when app is in foreground. Called by coordinator when ViewModel/Activity is
|
||||||
* ready.
|
* ready.
|
||||||
*/
|
*/
|
||||||
public void startMediaCapture() {
|
public void startMediaCapture() {
|
||||||
Log.d(TAG, "startMediaCapture");
|
Log.d(TAG, "startMediaCapture (Camera/Microphone)");
|
||||||
|
|
||||||
if (webRTCClient != null && webRTCClient.hasLocalMediaStream()) {
|
if (webRTCClient != null && webRTCClient.hasLocalMediaStream()) {
|
||||||
Log.w(TAG, "Media already initialized, skipping");
|
Log.w(TAG, "Media already initialized, skipping");
|
||||||
@@ -153,7 +150,7 @@ public class CallService extends Service implements WebRTCClient.Callbacks {
|
|||||||
|
|
||||||
boolean startsWithVideo = callCoordinator.isStartsWithVideo();
|
boolean startsWithVideo = callCoordinator.isStartsWithVideo();
|
||||||
|
|
||||||
Log.d(TAG, "Creating media stream");
|
Log.d(TAG, "Creating media stream with video: " + startsWithVideo);
|
||||||
|
|
||||||
mediaStreamManager.createMediaStream(
|
mediaStreamManager.createMediaStream(
|
||||||
new MediaStreamManager.Callback() {
|
new MediaStreamManager.Callback() {
|
||||||
@@ -163,14 +160,20 @@ public class CallService extends Service implements WebRTCClient.Callbacks {
|
|||||||
|
|
||||||
webRTCClient.setLocalMediaStream(stream);
|
webRTCClient.setLocalMediaStream(stream);
|
||||||
|
|
||||||
if (!stream.videoTracks.isEmpty()) {
|
callCoordinator.updateFrontCamera(mediaStreamManager.isFrontCamera());
|
||||||
VideoTrack localTrack = stream.videoTracks.get(0);
|
|
||||||
callCoordinator.updateLocalVideoTrack(localTrack);
|
|
||||||
}
|
|
||||||
|
|
||||||
callCoordinator.setVideoEnabled(startsWithVideo);
|
callCoordinator.setVideoEnabled(startsWithVideo);
|
||||||
|
|
||||||
callCoordinator.updateMediaCaptureReady(true);
|
if (!stream.videoTracks.isEmpty()) {
|
||||||
|
VideoTrack localTrack = stream.videoTracks.get(0);
|
||||||
|
callCoordinator.updateLocalVideoTrack(localTrack);
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "No video track in stream, call will be audio-only");
|
||||||
|
if (startsWithVideo) {
|
||||||
|
callCoordinator.reportError("Camera unavailable, using audio only");
|
||||||
|
}
|
||||||
|
callCoordinator.setVideoEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Media capture complete, ready for call");
|
Log.d(TAG, "Media capture complete, ready for call");
|
||||||
}
|
}
|
||||||
@@ -178,8 +181,11 @@ public class CallService extends Service implements WebRTCClient.Callbacks {
|
|||||||
@Override
|
@Override
|
||||||
public void onError(String error) {
|
public void onError(String error) {
|
||||||
Log.e(TAG, "Failed to setup media: " + error);
|
Log.e(TAG, "Failed to setup media: " + error);
|
||||||
|
if (startsWithVideo) {
|
||||||
callCoordinator.reportError("Camera/microphone error: " + error);
|
callCoordinator.reportError("Camera/microphone error: " + error);
|
||||||
}
|
}
|
||||||
|
callCoordinator.setVideoEnabled(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,30 +392,12 @@ public class CallService extends Service implements WebRTCClient.Callbacks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean setVideoEnabled(boolean enabled) {
|
public void setVideoEnabled(boolean enabled) {
|
||||||
Log.d(TAG, "setVideoEnabled: " + enabled);
|
Log.d(TAG, "setVideoEnabled: " + enabled);
|
||||||
|
|
||||||
if (enabled) {
|
|
||||||
if (mediaStreamManager != null) {
|
|
||||||
boolean captureReady = mediaStreamManager.startVideoCapture();
|
|
||||||
if (!captureReady) {
|
|
||||||
Log.w(TAG, "Failed to start video capture");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
callCoordinator.updateFrontCamera(mediaStreamManager.isFrontCamera());
|
|
||||||
}
|
|
||||||
if (webRTCClient != null) {
|
if (webRTCClient != null) {
|
||||||
webRTCClient.setVideoEnabled(true);
|
webRTCClient.setVideoEnabled(enabled);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (webRTCClient != null) {
|
|
||||||
webRTCClient.setVideoEnabled(false);
|
|
||||||
}
|
|
||||||
if (mediaStreamManager != null) {
|
|
||||||
mediaStreamManager.stopVideoCapture();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendMutedState(boolean audioEnabled, boolean videoEnabled) {
|
public void sendMutedState(boolean audioEnabled, boolean videoEnabled) {
|
||||||
@@ -444,12 +432,6 @@ public class CallService extends Service implements WebRTCClient.Callbacks {
|
|||||||
|
|
||||||
disposeWebRTC();
|
disposeWebRTC();
|
||||||
|
|
||||||
try {
|
|
||||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, "stopForeground failed", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
stopService();
|
stopService();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,44 +494,13 @@ public class CallService extends Service implements WebRTCClient.Callbacks {
|
|||||||
// Foreground Notification
|
// Foreground Notification
|
||||||
|
|
||||||
public void startForegroundWithNotification(int id, Notification notification) {
|
public void startForegroundWithNotification(int id, Notification notification) {
|
||||||
// Always run on main thread
|
Log.d(TAG, "Starting foreground with notification id: " + id);
|
||||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
|
||||||
new Handler(Looper.getMainLooper())
|
|
||||||
.post(() -> startForegroundWithNotification(id, notification));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "Starting call FGS with notification id: " + id);
|
|
||||||
try {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
||||||
startForeground(id, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL);
|
|
||||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
int types =
|
|
||||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
|
|
||||||
| ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
|
|
||||||
startForeground(id, notification, types);
|
|
||||||
} else {
|
|
||||||
startForeground(id, notification);
|
startForeground(id, notification);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "startForeground failed", e);
|
|
||||||
if (callCoordinator != null) {
|
|
||||||
callCoordinator.reportError("Failed to activate call FGS: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopForegroundAndDismiss() {
|
public void stopForegroundAndDismiss() {
|
||||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
Log.d(TAG, "Stopping foreground and dismissing notification");
|
||||||
new Handler(Looper.getMainLooper()).post(this::stopForegroundAndDismiss);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Log.d(TAG, "Stopping call FGS and dismissing notification");
|
|
||||||
try {
|
|
||||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, "stopForeground failed", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
|
|||||||
@@ -5,18 +5,13 @@ import android.content.Intent;
|
|||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.Icon;
|
import android.graphics.drawable.Icon;
|
||||||
import android.net.ConnectivityManager;
|
|
||||||
import android.net.Network;
|
|
||||||
import android.net.NetworkCapabilities;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.core.telecom.CallEndpointCompat;
|
import androidx.core.telecom.CallEndpointCompat;
|
||||||
import com.b44t.messenger.DcChat;
|
import com.b44t.messenger.DcChat;
|
||||||
import com.b44t.messenger.DcContext;
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
@@ -26,7 +21,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||||
|
|
||||||
public class CallUtil {
|
public class CallUtil {
|
||||||
private static final String TAG = "CallUtil";
|
private static final String TAG = CallUtil.class.getSimpleName();
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
public static void startAudioCall(Context context, int chatId) {
|
public static void startAudioCall(Context context, int chatId) {
|
||||||
@@ -57,22 +52,8 @@ public class CallUtil {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Runnable proceedWithCall =
|
|
||||||
() -> {
|
|
||||||
int accId = DcHelper.getContext(context).getAccountId();
|
int accId = DcHelper.getContext(context).getAccountId();
|
||||||
coordinator.initiateOutgoingCall(accId, chatId, startsWithVideo);
|
coordinator.initiateOutgoingCall(accId, chatId, startsWithVideo);
|
||||||
};
|
|
||||||
|
|
||||||
if (!isNetworkAvailable(context)) {
|
|
||||||
new AlertDialog.Builder(context)
|
|
||||||
.setMessage(context.getString(R.string.call_requires_connection))
|
|
||||||
.setPositiveButton(R.string.perm_continue, (dialog, which) -> proceedWithCall.run())
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
proceedWithCall.run();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -157,24 +138,4 @@ public class CallUtil {
|
|||||||
}
|
}
|
||||||
return iconRes;
|
return iconRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
|
||||||
private static boolean isNetworkAvailable(Context context) {
|
|
||||||
ConnectivityManager manager =
|
|
||||||
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
||||||
if (manager == null) return true;
|
|
||||||
|
|
||||||
boolean networkAvailable = false;
|
|
||||||
Network network = manager.getActiveNetwork();
|
|
||||||
if (network != null) {
|
|
||||||
NetworkCapabilities caps = manager.getNetworkCapabilities(network);
|
|
||||||
networkAvailable =
|
|
||||||
caps != null && caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean serverConnected =
|
|
||||||
DcHelper.getContext(context).getConnectivity() >= DcContext.DC_CONNECTIVITY_WORKING;
|
|
||||||
|
|
||||||
return networkAvailable || serverConnected;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import org.webrtc.VideoTrack;
|
|||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
public class CallViewModel extends AndroidViewModel {
|
public class CallViewModel extends AndroidViewModel {
|
||||||
|
|
||||||
private static final String TAG = "CallViewModel";
|
private static final String TAG = CallViewModel.class.getSimpleName();
|
||||||
|
|
||||||
private final CallCoordinator callCoordinator;
|
private final CallCoordinator callCoordinator;
|
||||||
|
|
||||||
@@ -46,8 +46,8 @@ public class CallViewModel extends AndroidViewModel {
|
|||||||
private final MediatorLiveData<CallState> callState;
|
private final MediatorLiveData<CallState> callState;
|
||||||
|
|
||||||
// Observer References for one-time observe
|
// Observer References for one-time observe
|
||||||
private Observer<Boolean> answerCallObserver;
|
private Observer<VideoTrack> answerCallObserver;
|
||||||
private Observer<Boolean> startOutgoingCallObserver;
|
private Observer<VideoTrack> startOutgoingCallObserver;
|
||||||
|
|
||||||
private final AtomicBoolean hasCallEnded = new AtomicBoolean(false);
|
private final AtomicBoolean hasCallEnded = new AtomicBoolean(false);
|
||||||
|
|
||||||
@@ -211,24 +211,25 @@ public class CallViewModel extends AndroidViewModel {
|
|||||||
callCoordinator.startMediaCapture();
|
callCoordinator.startMediaCapture();
|
||||||
|
|
||||||
// Create one-time observer
|
// Create one-time observer
|
||||||
LiveData<Boolean> mediaReady = callCoordinator.getMediaCaptureReady();
|
LiveData<VideoTrack> localTrack = callCoordinator.getLocalVideoTrack();
|
||||||
|
|
||||||
answerCallObserver =
|
answerCallObserver =
|
||||||
new Observer<Boolean>() {
|
new Observer<VideoTrack>() {
|
||||||
@Override
|
@Override
|
||||||
public void onChanged(Boolean ready) {
|
public void onChanged(VideoTrack videoTrack) {
|
||||||
if (Boolean.TRUE.equals(ready)) {
|
if (videoTrack != null) {
|
||||||
mediaReady.removeObserver(this);
|
// Media is ready, remove observer
|
||||||
|
localTrack.removeObserver(this);
|
||||||
answerCallObserver = null;
|
answerCallObserver = null;
|
||||||
|
|
||||||
Log.d(TAG, "Media capture ready, answering call (WebRTC)");
|
Log.d(TAG, "Local video ready, answering call (WebRTC)");
|
||||||
|
|
||||||
callCoordinator.answerWebRTC();
|
callCoordinator.answerWebRTC();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mediaReady.observeForever(answerCallObserver);
|
localTrack.observeForever(answerCallObserver);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Start outgoing call with media capture Called by Activity for outgoing calls */
|
/** Start outgoing call with media capture Called by Activity for outgoing calls */
|
||||||
@@ -243,28 +244,30 @@ public class CallViewModel extends AndroidViewModel {
|
|||||||
callCoordinator.startMediaCapture();
|
callCoordinator.startMediaCapture();
|
||||||
|
|
||||||
// Create one-time observer
|
// Create one-time observer
|
||||||
LiveData<Boolean> mediaReady = callCoordinator.getMediaCaptureReady();
|
LiveData<VideoTrack> localTrack = callCoordinator.getLocalVideoTrack();
|
||||||
|
VideoTrack currentValue = localTrack.getValue();
|
||||||
|
|
||||||
if (Boolean.TRUE.equals(mediaReady.getValue())) {
|
if (currentValue != null) {
|
||||||
Log.d(TAG, "Media already ready, starting call immediately");
|
Log.d(TAG, "Media already ready, starting call immediately");
|
||||||
callCoordinator.startOutgoingCall();
|
callCoordinator.startOutgoingCall();
|
||||||
} else {
|
} else {
|
||||||
startOutgoingCallObserver =
|
startOutgoingCallObserver =
|
||||||
new Observer<Boolean>() {
|
new Observer<VideoTrack>() {
|
||||||
@Override
|
@Override
|
||||||
public void onChanged(Boolean ready) {
|
public void onChanged(VideoTrack videoTrack) {
|
||||||
if (Boolean.TRUE.equals(ready)) {
|
if (videoTrack != null) {
|
||||||
mediaReady.removeObserver(this);
|
// Media is ready, remove observer
|
||||||
|
localTrack.removeObserver(this);
|
||||||
startOutgoingCallObserver = null;
|
startOutgoingCallObserver = null;
|
||||||
|
|
||||||
Log.d(TAG, "Media capture ready, starting outgoing call");
|
Log.d(TAG, "Local video ready, starting outgoing call");
|
||||||
|
|
||||||
callCoordinator.startOutgoingCall();
|
callCoordinator.startOutgoingCall();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mediaReady.observeForever(startOutgoingCallObserver);
|
localTrack.observeForever(startOutgoingCallObserver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,12 +460,12 @@ public class CallViewModel extends AndroidViewModel {
|
|||||||
Log.d(TAG, "CallViewModel cleared");
|
Log.d(TAG, "CallViewModel cleared");
|
||||||
|
|
||||||
if (answerCallObserver != null) {
|
if (answerCallObserver != null) {
|
||||||
callCoordinator.getMediaCaptureReady().removeObserver(answerCallObserver);
|
callCoordinator.getLocalVideoTrack().removeObserver(answerCallObserver);
|
||||||
answerCallObserver = null;
|
answerCallObserver = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startOutgoingCallObserver != null) {
|
if (startOutgoingCallObserver != null) {
|
||||||
callCoordinator.getMediaCaptureReady().removeObserver(startOutgoingCallObserver);
|
callCoordinator.getLocalVideoTrack().removeObserver(startOutgoingCallObserver);
|
||||||
startOutgoingCallObserver = null;
|
startOutgoingCallObserver = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,15 +24,11 @@ import org.webrtc.VideoTrack;
|
|||||||
|
|
||||||
public class MediaStreamManager {
|
public class MediaStreamManager {
|
||||||
|
|
||||||
private static final String TAG = "MediaStreamManager";
|
private static final String TAG = MediaStreamManager.class.getSimpleName();
|
||||||
private static final String STREAM_ID = "local_stream";
|
private static final String STREAM_ID = "local_stream";
|
||||||
private static final String AUDIO_TRACK_ID = "audio_track";
|
private static final String AUDIO_TRACK_ID = "audio_track";
|
||||||
private static final String VIDEO_TRACK_ID = "video_track";
|
private static final String VIDEO_TRACK_ID = "video_track";
|
||||||
|
|
||||||
private static final int VIDEO_WIDTH = 1280;
|
|
||||||
private static final int VIDEO_HEIGHT = 720;
|
|
||||||
private static final int VIDEO_FPS = 30;
|
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final PeerConnectionFactory peerConnectionFactory;
|
private final PeerConnectionFactory peerConnectionFactory;
|
||||||
|
|
||||||
@@ -41,7 +37,6 @@ public class MediaStreamManager {
|
|||||||
private AudioSource audioSource;
|
private AudioSource audioSource;
|
||||||
private SurfaceTextureHelper surfaceTextureHelper;
|
private SurfaceTextureHelper surfaceTextureHelper;
|
||||||
private volatile boolean isFrontCamera = true;
|
private volatile boolean isFrontCamera = true;
|
||||||
private volatile boolean isCapturing = false;
|
|
||||||
|
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
void onMediaStreamReady(MediaStream stream);
|
void onMediaStreamReady(MediaStream stream);
|
||||||
@@ -61,8 +56,9 @@ public class MediaStreamManager {
|
|||||||
this.peerConnectionFactory = peerConnectionFactory;
|
this.peerConnectionFactory = peerConnectionFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a media stream with an audio track and a video track. */
|
/** Create media stream with audio and optionally video */
|
||||||
public synchronized void createMediaStream(Callback callback) {
|
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||||
|
public void createMediaStream(Callback callback) {
|
||||||
try {
|
try {
|
||||||
MediaStream mediaStream = peerConnectionFactory.createLocalMediaStream(STREAM_ID);
|
MediaStream mediaStream = peerConnectionFactory.createLocalMediaStream(STREAM_ID);
|
||||||
|
|
||||||
@@ -72,11 +68,24 @@ public class MediaStreamManager {
|
|||||||
AudioTrack audioTrack = peerConnectionFactory.createAudioTrack(AUDIO_TRACK_ID, audioSource);
|
AudioTrack audioTrack = peerConnectionFactory.createAudioTrack(AUDIO_TRACK_ID, audioSource);
|
||||||
mediaStream.addTrack(audioTrack);
|
mediaStream.addTrack(audioTrack);
|
||||||
|
|
||||||
// Create video source and track
|
// Create video track
|
||||||
videoSource = peerConnectionFactory.createVideoSource(false);
|
videoCapturer = createVideoCapturer();
|
||||||
|
if (videoCapturer == null) {
|
||||||
|
callback.onError("No camera available");
|
||||||
|
callback.onMediaStreamReady(mediaStream);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast());
|
||||||
VideoTrack videoTrack = peerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
|
VideoTrack videoTrack = peerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
|
||||||
mediaStream.addTrack(videoTrack);
|
mediaStream.addTrack(videoTrack);
|
||||||
|
|
||||||
|
// Start capturing
|
||||||
|
surfaceTextureHelper =
|
||||||
|
SurfaceTextureHelper.create("CaptureThread", EglUtils.getEglBase().getEglBaseContext());
|
||||||
|
videoCapturer.initialize(surfaceTextureHelper, context, videoSource.getCapturerObserver());
|
||||||
|
videoCapturer.startCapture(1280, 720, 30);
|
||||||
|
|
||||||
callback.onMediaStreamReady(mediaStream);
|
callback.onMediaStreamReady(mediaStream);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -85,62 +94,6 @@ public class MediaStreamManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the camera and start sending frames to VideoSource.
|
|
||||||
*
|
|
||||||
* @return true if the camera is capturing, false if it could not be started
|
|
||||||
*/
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
|
||||||
public synchronized boolean startVideoCapture() {
|
|
||||||
if (isCapturing) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (videoSource == null) {
|
|
||||||
Log.e(TAG, "VideoSource not initialized");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (videoCapturer == null) {
|
|
||||||
videoCapturer = createVideoCapturer();
|
|
||||||
if (videoCapturer == null) {
|
|
||||||
Log.w(TAG, "Cannot start video capture: no camera available");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (surfaceTextureHelper == null) {
|
|
||||||
surfaceTextureHelper =
|
|
||||||
SurfaceTextureHelper.create("CaptureThread", EglUtils.getEglBase().getEglBaseContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
videoCapturer.initialize(surfaceTextureHelper, context, videoSource.getCapturerObserver());
|
|
||||||
}
|
|
||||||
|
|
||||||
videoCapturer.startCapture(VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_FPS);
|
|
||||||
isCapturing = true;
|
|
||||||
Log.d(TAG, "Video capture started");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Stop the camera. The capturer is kept alive. */
|
|
||||||
public synchronized void stopVideoCapture() {
|
|
||||||
if (!isCapturing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (videoCapturer != null) {
|
|
||||||
try {
|
|
||||||
videoCapturer.stopCapture();
|
|
||||||
Log.d(TAG, "Video capture stopped");
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(TAG, "Interrupted while stopping capture", e);
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isCapturing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private VideoCapturer createVideoCapturer() {
|
private VideoCapturer createVideoCapturer() {
|
||||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
|
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
|
||||||
@@ -176,14 +129,6 @@ public class MediaStreamManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void switchCamera(@Nullable CameraSwitchCallback callback) {
|
public void switchCamera(@Nullable CameraSwitchCallback callback) {
|
||||||
if (!isCapturing) {
|
|
||||||
Log.w(TAG, "Cannot switch camera while not capturing");
|
|
||||||
if (callback != null) {
|
|
||||||
callback.onError("Camera not active");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(videoCapturer instanceof CameraVideoCapturer)) {
|
if (!(videoCapturer instanceof CameraVideoCapturer)) {
|
||||||
Log.e(TAG, "switchCamera called but videoCapturer is not a CameraVideoCapturer");
|
Log.e(TAG, "switchCamera called but videoCapturer is not a CameraVideoCapturer");
|
||||||
return;
|
return;
|
||||||
@@ -241,12 +186,10 @@ public class MediaStreamManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Cleanup resources */
|
/** Cleanup resources */
|
||||||
public synchronized void dispose() {
|
public void dispose() {
|
||||||
if (videoCapturer != null) {
|
if (videoCapturer != null) {
|
||||||
try {
|
try {
|
||||||
if (isCapturing) {
|
|
||||||
videoCapturer.stopCapture();
|
videoCapturer.stopCapture();
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Log.e(TAG, "Error stopping capture", e);
|
Log.e(TAG, "Error stopping capture", e);
|
||||||
}
|
}
|
||||||
@@ -254,8 +197,6 @@ public class MediaStreamManager {
|
|||||||
videoCapturer = null;
|
videoCapturer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
isCapturing = false;
|
|
||||||
|
|
||||||
if (surfaceTextureHelper != null) {
|
if (surfaceTextureHelper != null) {
|
||||||
surfaceTextureHelper.dispose();
|
surfaceTextureHelper.dispose();
|
||||||
surfaceTextureHelper = null;
|
surfaceTextureHelper = null;
|
||||||
|
|||||||
@@ -92,10 +92,6 @@ public class AttachmentTypeSelector extends PopupWindow {
|
|||||||
ViewUtil.findById(layout, R.id.location_linear_layout).setVisibility(View.GONE);
|
ViewUtil.findById(layout, R.id.location_linear_layout).setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DcHelper.getContext(context).getChat(chatId).isOutBroadcast()) {
|
|
||||||
ViewUtil.findById(layout, R.id.webxdc_linear_layout).setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
setLocationButtonImage(context);
|
setLocationButtonImage(context);
|
||||||
|
|
||||||
setContentView(layout);
|
setContentView(layout);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.R;
|
|||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
|
|
||||||
public class CallItemView extends FrameLayout {
|
public class CallItemView extends FrameLayout {
|
||||||
private static final String TAG = "CallItemView";
|
private static final String TAG = CallItemView.class.getSimpleName();
|
||||||
|
|
||||||
private final @NonNull ImageView icon;
|
private final @NonNull ImageView icon;
|
||||||
private final @NonNull TextView title;
|
private final @NonNull TextView title;
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package org.thoughtcrime.securesms.components;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
import org.thoughtcrime.securesms.components.viewpager.HackyViewPager;
|
||||||
|
|
||||||
|
/** An implementation of {@link ViewPager} that disables swiping when the view is disabled. */
|
||||||
|
public class ControllableViewPager extends HackyViewPager {
|
||||||
|
|
||||||
|
public ControllableViewPager(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControllableViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent ev) {
|
||||||
|
return isEnabled() && super.onTouchEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||||
|
return isEnabled() && super.onInterceptTouchEvent(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ public class InputPanel extends ConstraintLayout
|
|||||||
KeyboardAwareLinearLayout.OnKeyboardShownListener,
|
KeyboardAwareLinearLayout.OnKeyboardShownListener,
|
||||||
MediaKeyboard.MediaKeyboardListener {
|
MediaKeyboard.MediaKeyboardListener {
|
||||||
|
|
||||||
private static final String TAG = "InputPanel";
|
private static final String TAG = InputPanel.class.getSimpleName();
|
||||||
|
|
||||||
private static final int FADE_TIME = 150;
|
private static final int FADE_TIME = 150;
|
||||||
|
|
||||||
@@ -360,31 +360,6 @@ public class InputPanel extends ConstraintLayout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRecording() {
|
|
||||||
return microphoneRecorderView.isRecording();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getRecordingDuration() {
|
|
||||||
return recordTime.getElapsedTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetRecordingUI() {
|
|
||||||
microphoneRecorderView.resetState();
|
|
||||||
recordLockCancel.setVisibility(View.GONE);
|
|
||||||
recordTime.hide();
|
|
||||||
slideToCancel.hide();
|
|
||||||
|
|
||||||
emojiToggle.setVisibility(View.VISIBLE);
|
|
||||||
emojiToggle.setAlpha(1f);
|
|
||||||
composeText.setVisibility(View.VISIBLE);
|
|
||||||
composeText.setAlpha(1f);
|
|
||||||
quickCameraToggle.setVisibility(View.VISIBLE);
|
|
||||||
quickCameraToggle.setAlpha(1f);
|
|
||||||
quickAudioToggle.setVisibility(View.VISIBLE);
|
|
||||||
quickAudioToggle.setAlpha(1f);
|
|
||||||
buttonToggle.setAlpha(1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
void onRecorderStarted();
|
void onRecorderStarted();
|
||||||
|
|
||||||
@@ -477,10 +452,10 @@ public class InputPanel extends ConstraintLayout
|
|||||||
}
|
}
|
||||||
|
|
||||||
public long hide() {
|
public long hide() {
|
||||||
long elapsedTime = getElapsedTime();
|
long elapsedtime = System.currentTimeMillis() - startTime.get();
|
||||||
this.startTime.set(0);
|
this.startTime.set(0);
|
||||||
ViewUtil.fadeOut(this.recordTimeView, FADE_TIME, View.INVISIBLE);
|
ViewUtil.fadeOut(this.recordTimeView, FADE_TIME, View.INVISIBLE);
|
||||||
return elapsedTime;
|
return elapsedtime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -493,11 +468,6 @@ public class InputPanel extends ConstraintLayout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getElapsedTime() {
|
|
||||||
long start = startTime.get();
|
|
||||||
return start > 0 ? System.currentTimeMillis() - start : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String formatElapsedTime(long ms) {
|
private String formatElapsedTime(long ms) {
|
||||||
return DateUtils.formatElapsedTime(TimeUnit.MILLISECONDS.toSeconds(ms))
|
return DateUtils.formatElapsedTime(TimeUnit.MILLISECONDS.toSeconds(ms))
|
||||||
+ String.format(".%01d", ((ms / 100) % 10));
|
+ String.format(".%01d", ((ms / 100) % 10));
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
* been opened and what its height would be.
|
* been opened and what its height would be.
|
||||||
*/
|
*/
|
||||||
public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
|
public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
|
||||||
private static final String TAG = "KeyboardAwareLinearLayout";
|
private static final String TAG = KeyboardAwareLinearLayout.class.getSimpleName();
|
||||||
|
|
||||||
private static final long KEYBOARD_DEBOUNCE = 150;
|
private static final long KEYBOARD_DEBOUNCE = 150;
|
||||||
|
|
||||||
|
|||||||
@@ -53,11 +53,10 @@ public class MediaView extends FrameLayout {
|
|||||||
@NonNull Window window,
|
@NonNull Window window,
|
||||||
@NonNull Uri source,
|
@NonNull Uri source,
|
||||||
@Nullable String fileName,
|
@Nullable String fileName,
|
||||||
@Nullable String mediaType,
|
@NonNull String mediaType,
|
||||||
long size,
|
long size,
|
||||||
boolean autoplay)
|
boolean autoplay)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
mediaType = mediaType == null ? "null" : mediaType;
|
|
||||||
if (mediaType.startsWith("image/")) {
|
if (mediaType.startsWith("image/")) {
|
||||||
imageView.setVisibility(View.VISIBLE);
|
imageView.setVisibility(View.VISIBLE);
|
||||||
if (videoView.resolved()) videoView.get().setVisibility(View.GONE);
|
if (videoView.resolved()) videoView.get().setVisibility(View.GONE);
|
||||||
|
|||||||
@@ -287,15 +287,4 @@ public final class MicrophoneRecorderView extends FrameLayout implements View.On
|
|||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRecording() {
|
|
||||||
return state != State.NOT_RUNNING;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetState() {
|
|
||||||
if (state != State.NOT_RUNNING) {
|
|
||||||
state = State.NOT_RUNNING;
|
|
||||||
hideUi();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import org.thoughtcrime.securesms.util.Util;
|
|||||||
|
|
||||||
public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
||||||
|
|
||||||
private static final String TAG = "QuoteView";
|
private static final String TAG = QuoteView.class.getSimpleName();
|
||||||
|
|
||||||
private static final int MESSAGE_TYPE_PREVIEW = 0;
|
private static final int MESSAGE_TYPE_PREVIEW = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class ScaleStableImageView extends AppCompatImageView
|
|||||||
implements KeyboardAwareLinearLayout.OnKeyboardShownListener,
|
implements KeyboardAwareLinearLayout.OnKeyboardShownListener,
|
||||||
KeyboardAwareLinearLayout.OnKeyboardHiddenListener {
|
KeyboardAwareLinearLayout.OnKeyboardHiddenListener {
|
||||||
|
|
||||||
private static final String TAG = "ScaleStableImageView";
|
private static final String TAG = ScaleStableImageView.class.getSimpleName();
|
||||||
|
|
||||||
private Drawable defaultDrawable;
|
private Drawable defaultDrawable;
|
||||||
private Drawable currentDrawable;
|
private Drawable currentDrawable;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
|||||||
|
|
||||||
public class SearchToolbar extends LinearLayout {
|
public class SearchToolbar extends LinearLayout {
|
||||||
|
|
||||||
private static final String TAG = "SearchToolbar";
|
private static final String TAG = SearchToolbar.class.getSimpleName();
|
||||||
private float x, y;
|
private float x, y;
|
||||||
private MenuItem searchItem;
|
private MenuItem searchItem;
|
||||||
private EditText searchText;
|
private EditText searchText;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import org.thoughtcrime.securesms.util.Util;
|
|||||||
|
|
||||||
public class ThumbnailView extends FrameLayout {
|
public class ThumbnailView extends FrameLayout {
|
||||||
|
|
||||||
private static final String TAG = "ThumbnailView";
|
private static final String TAG = ThumbnailView.class.getSimpleName();
|
||||||
private static final int WIDTH = 0;
|
private static final int WIDTH = 0;
|
||||||
private static final int HEIGHT = 1;
|
private static final int HEIGHT = 1;
|
||||||
private static final int MIN_WIDTH = 0;
|
private static final int MIN_WIDTH = 0;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.mms.VcardSlide;
|
|||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
|
||||||
public class VcardView extends FrameLayout {
|
public class VcardView extends FrameLayout {
|
||||||
private static final String TAG = "VcardView";
|
private static final String TAG = VcardView.class.getSimpleName();
|
||||||
|
|
||||||
private final @NonNull AvatarView avatar;
|
private final @NonNull AvatarView avatar;
|
||||||
private final @NonNull TextView name;
|
private final @NonNull TextView name;
|
||||||
|
|||||||
+41
-123
@@ -6,38 +6,34 @@ import android.net.Uri;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.PlaybackException;
|
|
||||||
import androidx.media3.common.Player;
|
import androidx.media3.common.Player;
|
||||||
import androidx.media3.session.MediaController;
|
import androidx.media3.session.MediaController;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
public class AudioPlaybackViewModel extends ViewModel {
|
public class AudioPlaybackViewModel extends ViewModel {
|
||||||
private static final String TAG = "AudioPlaybackViewModel";
|
private static final String TAG = AudioPlaybackViewModel.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final int NON_MESSAGE_AUDIO_MSG_ID =
|
||||||
|
0; // Audios not attached to a message doesn't have message id.
|
||||||
|
|
||||||
private final MutableLiveData<AudioPlaybackState> playbackState;
|
private final MutableLiveData<AudioPlaybackState> playbackState;
|
||||||
|
|
||||||
private final MutableLiveData<Map<Integer, Long>> durations =
|
private final MutableLiveData<Map<Integer, Long>> durations =
|
||||||
new MutableLiveData<>(new HashMap<>());
|
new MutableLiveData<>(new HashMap<>());
|
||||||
private final Map<Integer, Uri> durationUris = new HashMap<>();
|
|
||||||
private final Set<Integer> extractionInProgress = new HashSet<>();
|
private final Set<Integer> extractionInProgress = new HashSet<>();
|
||||||
private final ExecutorService extractionExecutor = Executors.newFixedThreadPool(2);
|
private final ExecutorService extractionExecutor = Executors.newFixedThreadPool(2);
|
||||||
|
|
||||||
private @Nullable MediaController mediaController;
|
private @Nullable MediaController mediaController;
|
||||||
private @Nullable ChatAudioQueueProvider queueProvider;
|
|
||||||
private @Nullable Player.Listener playerListener;
|
|
||||||
private final Handler handler;
|
private final Handler handler;
|
||||||
private boolean isUserSeeking = false;
|
private boolean isUserSeeking = false;
|
||||||
|
|
||||||
@@ -51,11 +47,6 @@ public class AudioPlaybackViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setMediaController(@Nullable MediaController controller) {
|
public void setMediaController(@Nullable MediaController controller) {
|
||||||
if (this.mediaController != null && playerListener != null) {
|
|
||||||
this.mediaController.removeListener(playerListener);
|
|
||||||
}
|
|
||||||
playerListener = null;
|
|
||||||
|
|
||||||
this.mediaController = controller;
|
this.mediaController = controller;
|
||||||
if (mediaController != null && mediaController.isPlaying()) {
|
if (mediaController != null && mediaController.isPlaying()) {
|
||||||
startUpdateProgress();
|
startUpdateProgress();
|
||||||
@@ -68,41 +59,30 @@ public class AudioPlaybackViewModel extends ViewModel {
|
|||||||
public void loadAudioAndPlay(int msgId, Uri audioUri) {
|
public void loadAudioAndPlay(int msgId, Uri audioUri) {
|
||||||
if (mediaController == null) return;
|
if (mediaController == null) return;
|
||||||
|
|
||||||
String mediaId = String.valueOf(msgId);
|
// Set media item if we have a different audio.
|
||||||
|
if (isDifferentAudio(msgId, audioUri)) {
|
||||||
MediaItem current = mediaController.getCurrentMediaItem();
|
|
||||||
if (current != null && mediaId.equals(current.mediaId)) {
|
|
||||||
mediaController.play();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateState(msgId, audioUri, AudioPlaybackState.PlaybackStatus.LOADING, 0, 0);
|
updateState(msgId, audioUri, AudioPlaybackState.PlaybackStatus.LOADING, 0, 0);
|
||||||
|
|
||||||
List<MediaItem> items = null;
|
MediaItem mediaItem =
|
||||||
int startIndex = -1;
|
new MediaItem.Builder().setMediaId(String.valueOf(msgId)).setUri(audioUri).build();
|
||||||
|
mediaController.setMediaItem(mediaItem);
|
||||||
if (queueProvider != null) {
|
|
||||||
items = queueProvider.buildAudioQueue();
|
|
||||||
startIndex = indexOfMediaId(items, mediaId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startIndex < 0) {
|
|
||||||
items =
|
|
||||||
Collections.singletonList(
|
|
||||||
new MediaItem.Builder().setMediaId(mediaId).setUri(audioUri).build());
|
|
||||||
startIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaController.setMediaItems(items, startIndex, 0);
|
|
||||||
mediaController.prepare();
|
mediaController.prepare();
|
||||||
mediaController.play();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int indexOfMediaId(List<MediaItem> items, String mediaId) {
|
play(msgId, audioUri);
|
||||||
for (int i = 0; i < items.size(); i++) {
|
|
||||||
if (mediaId.equals(items.get(i).mediaId)) return i;
|
|
||||||
}
|
}
|
||||||
return -1;
|
|
||||||
|
private boolean isSameAudio(int msgId, Uri audioUri) {
|
||||||
|
return !isDifferentAudio(msgId, audioUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDifferentAudio(int msgId, Uri audioUri) {
|
||||||
|
AudioPlaybackState currentState = playbackState.getValue();
|
||||||
|
|
||||||
|
return currentState != null
|
||||||
|
&& (msgId != currentState.getMsgId()
|
||||||
|
|| currentState.getAudioUri() == null
|
||||||
|
|| currentState.getAudioUri() != null && !currentState.getAudioUri().equals(audioUri));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Map<Integer, Long>> getDurations() {
|
public LiveData<Map<Integer, Long>> getDurations() {
|
||||||
@@ -113,18 +93,8 @@ public class AudioPlaybackViewModel extends ViewModel {
|
|||||||
// Check cache
|
// Check cache
|
||||||
Map<Integer, Long> currentDurations = durations.getValue();
|
Map<Integer, Long> currentDurations = durations.getValue();
|
||||||
if (currentDurations != null && currentDurations.containsKey(msgId)) {
|
if (currentDurations != null && currentDurations.containsKey(msgId)) {
|
||||||
Uri cachedUri = durationUris.get(msgId);
|
|
||||||
if (audioUri.equals(cachedUri)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Map<Integer, Long> updated = new HashMap<>(currentDurations);
|
|
||||||
updated.remove(msgId);
|
|
||||||
durations.setValue(updated);
|
|
||||||
durationUris.remove(msgId);
|
|
||||||
synchronized (extractionInProgress) {
|
|
||||||
extractionInProgress.remove(msgId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check extracting
|
// Check extracting
|
||||||
synchronized (extractionInProgress) {
|
synchronized (extractionInProgress) {
|
||||||
@@ -144,7 +114,6 @@ public class AudioPlaybackViewModel extends ViewModel {
|
|||||||
Map<Integer, Long> updatedDurations = new HashMap<>(durations.getValue());
|
Map<Integer, Long> updatedDurations = new HashMap<>(durations.getValue());
|
||||||
updatedDurations.put(msgId, duration);
|
updatedDurations.put(msgId, duration);
|
||||||
durations.setValue(updatedDurations);
|
durations.setValue(updatedDurations);
|
||||||
durationUris.put(msgId, audioUri);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
synchronized (extractionInProgress) {
|
synchronized (extractionInProgress) {
|
||||||
@@ -169,68 +138,46 @@ public class AudioPlaybackViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pause(int msgId) {
|
public void pause(int msgId, Uri audioUri) {
|
||||||
if (isCurrentItem(msgId)) {
|
if (mediaController != null && isSameAudio(msgId, audioUri)) {
|
||||||
mediaController.pause();
|
mediaController.pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void play(int msgId) {
|
public void play(int msgId, Uri audioUri) {
|
||||||
if (isCurrentItem(msgId)) {
|
if (mediaController != null && isSameAudio(msgId, audioUri)) {
|
||||||
mediaController.play();
|
mediaController.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void seekTo(long position, int msgId) {
|
public void seekTo(long position, int msgId, Uri audioUri) {
|
||||||
if (isCurrentItem(msgId)) {
|
if (mediaController != null && isSameAudio(msgId, audioUri)) {
|
||||||
mediaController.seekTo(position);
|
mediaController.seekTo(position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop(int msgId) {
|
public void stop(int msgId, Uri audioUri) {
|
||||||
if (isCurrentItem(msgId)) {
|
if (mediaController != null && isSameAudio(msgId, audioUri)) {
|
||||||
mediaController.stop();
|
mediaController.stop();
|
||||||
mediaController.clearMediaItems();
|
|
||||||
stopUpdateProgress();
|
stopUpdateProgress();
|
||||||
playbackState.setValue(AudioPlaybackState.idle());
|
playbackState.setValue(AudioPlaybackState.idle());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isCurrentItem(int msgId) {
|
public void stopNonMessageAudioPlayback() {
|
||||||
if (mediaController == null) return false;
|
stopByIds(NON_MESSAGE_AUDIO_MSG_ID);
|
||||||
MediaItem current = mediaController.getCurrentMediaItem();
|
|
||||||
return current != null && String.valueOf(msgId).equals(current.mediaId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A special method for deleting message, where we only use message Ids
|
// A special method for deleting message, where we only use message Ids
|
||||||
public void stopByIds(int... msgIds) {
|
public void stopByIds(int... msgIds) {
|
||||||
if (mediaController == null) return;
|
|
||||||
|
|
||||||
AudioPlaybackState currentState = playbackState.getValue();
|
AudioPlaybackState currentState = playbackState.getValue();
|
||||||
boolean stoppedCurrent = false;
|
|
||||||
|
|
||||||
if (currentState != null) {
|
if (mediaController != null && currentState != null) {
|
||||||
for (int msgId : msgIds) {
|
for (int msgId : msgIds) {
|
||||||
if (msgId == currentState.getMsgId()) {
|
if (msgId == currentState.getMsgId()) {
|
||||||
mediaController.stop();
|
mediaController.stop();
|
||||||
mediaController.clearMediaItems();
|
|
||||||
stopUpdateProgress();
|
stopUpdateProgress();
|
||||||
playbackState.setValue(AudioPlaybackState.idle());
|
playbackState.setValue(AudioPlaybackState.idle());
|
||||||
stoppedCurrent = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stoppedCurrent) {
|
|
||||||
Set<String> deletedMediaIds = new HashSet<>();
|
|
||||||
for (int msgId : msgIds) {
|
|
||||||
deletedMediaIds.add(String.valueOf(msgId));
|
|
||||||
}
|
|
||||||
for (int i = mediaController.getMediaItemCount() - 1; i >= 0; i--) {
|
|
||||||
MediaItem item = mediaController.getMediaItemAt(i);
|
|
||||||
if (deletedMediaIds.contains(item.mediaId)) {
|
|
||||||
mediaController.removeMediaItem(i);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,10 +191,10 @@ public class AudioPlaybackViewModel extends ViewModel {
|
|||||||
private void setupPlayerListener() {
|
private void setupPlayerListener() {
|
||||||
if (mediaController == null) return;
|
if (mediaController == null) return;
|
||||||
|
|
||||||
playerListener =
|
mediaController.addListener(
|
||||||
new Player.Listener() {
|
new Player.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onEvents(@NonNull Player player, @NonNull Player.Events events) {
|
public void onEvents(Player player, Player.Events events) {
|
||||||
if (events.containsAny(Player.EVENT_IS_PLAYING_CHANGED)) {
|
if (events.containsAny(Player.EVENT_IS_PLAYING_CHANGED)) {
|
||||||
if (player.isPlaying()) {
|
if (player.isPlaying()) {
|
||||||
startUpdateProgress();
|
startUpdateProgress();
|
||||||
@@ -256,43 +203,20 @@ public class AudioPlaybackViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
updateCurrentState(false);
|
updateCurrentState(false);
|
||||||
}
|
}
|
||||||
if (events.containsAny(Player.EVENT_MEDIA_ITEM_TRANSITION)) {
|
|
||||||
updateCurrentState(true);
|
|
||||||
}
|
|
||||||
if (events.containsAny(Player.EVENT_PLAYBACK_STATE_CHANGED)) {
|
if (events.containsAny(Player.EVENT_PLAYBACK_STATE_CHANGED)) {
|
||||||
if (player.getPlaybackState() == Player.STATE_READY) {
|
if (player.getPlaybackState() == Player.STATE_READY) {
|
||||||
updateCurrentState(false);
|
updateCurrentState(false);
|
||||||
} else if (player.getPlaybackState() == Player.STATE_ENDED
|
} else if (player.getPlaybackState() == Player.STATE_ENDED) {
|
||||||
&& !player.hasNextMediaItem()) {
|
// This is to prevent automatically playing after the audio
|
||||||
mediaController.stop();
|
// has been play to the end once, then user dragged the seek bar again
|
||||||
mediaController.clearMediaItems();
|
mediaController.setPlayWhenReady(false);
|
||||||
stopUpdateProgress();
|
|
||||||
playbackState.setValue(AudioPlaybackState.idle());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (events.containsAny(Player.EVENT_PLAYER_ERROR)) {
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlayerError(@NonNull PlaybackException error) {
|
|
||||||
Log.w(
|
|
||||||
TAG,
|
|
||||||
"Playback error on msgId="
|
|
||||||
+ (mediaController.getCurrentMediaItem() != null
|
|
||||||
? mediaController.getCurrentMediaItem().mediaId
|
|
||||||
: "null"),
|
|
||||||
error);
|
|
||||||
|
|
||||||
if (mediaController.hasNextMediaItem()) {
|
|
||||||
mediaController.seekToNextMediaItem();
|
|
||||||
mediaController.prepare();
|
|
||||||
mediaController.play();
|
|
||||||
} else {
|
|
||||||
updateCurrentAudioState(AudioPlaybackState.PlaybackStatus.ERROR, 0, 0);
|
updateCurrentAudioState(AudioPlaybackState.PlaybackStatus.ERROR, 0, 0);
|
||||||
mediaController.clearMediaItems();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
mediaController.addListener(playerListener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCurrentState(boolean queryPlaying) {
|
private void updateCurrentState(boolean queryPlaying) {
|
||||||
@@ -361,11 +285,6 @@ public class AudioPlaybackViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Playing Queue
|
|
||||||
public void setQueueProvider(@Nullable ChatAudioQueueProvider provider) {
|
|
||||||
this.queueProvider = provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Progress tracking
|
// Progress tracking
|
||||||
private final Runnable progressRunnable =
|
private final Runnable progressRunnable =
|
||||||
new Runnable() {
|
new Runnable() {
|
||||||
@@ -394,7 +313,6 @@ public class AudioPlaybackViewModel extends ViewModel {
|
|||||||
protected void onCleared() {
|
protected void onCleared() {
|
||||||
stopUpdateProgress();
|
stopUpdateProgress();
|
||||||
extractionExecutor.shutdown();
|
extractionExecutor.shutdown();
|
||||||
durationUris.clear();
|
|
||||||
super.onCleared();
|
super.onCleared();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import org.thoughtcrime.securesms.util.DateUtils;
|
|||||||
|
|
||||||
public class AudioView extends FrameLayout {
|
public class AudioView extends FrameLayout {
|
||||||
|
|
||||||
private static final String TAG = "AudioView";
|
private static final String TAG = AudioView.class.getSimpleName();
|
||||||
|
|
||||||
private final @NonNull ImageView playPauseButton;
|
private final @NonNull ImageView playPauseButton;
|
||||||
private final AnimatedVectorDrawableCompat playToPauseDrawable;
|
private final AnimatedVectorDrawableCompat playToPauseDrawable;
|
||||||
@@ -109,15 +109,12 @@ public class AudioView extends FrameLayout {
|
|||||||
|
|
||||||
AudioPlaybackState state = viewModel.getPlaybackState().getValue();
|
AudioPlaybackState state = viewModel.getPlaybackState().getValue();
|
||||||
|
|
||||||
if (state != null
|
if (state != null && msgId == state.getMsgId() && audioUri.equals(state.getAudioUri())) {
|
||||||
&& msgId == state.getMsgId()
|
|
||||||
&& (state.getStatus() == AudioPlaybackState.PlaybackStatus.PLAYING
|
|
||||||
|| state.getStatus() == AudioPlaybackState.PlaybackStatus.PAUSED)) {
|
|
||||||
// Same audio
|
// Same audio
|
||||||
if (state.getStatus() == AudioPlaybackState.PlaybackStatus.PLAYING) {
|
if (state.getStatus() == AudioPlaybackState.PlaybackStatus.PLAYING) {
|
||||||
viewModel.pause(msgId);
|
viewModel.pause(msgId, audioUri);
|
||||||
} else {
|
} else {
|
||||||
viewModel.play(msgId);
|
viewModel.play(msgId, audioUri);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Different audio
|
// Different audio
|
||||||
@@ -148,7 +145,7 @@ public class AudioView extends FrameLayout {
|
|||||||
@Override
|
@Override
|
||||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
viewModel.setUserSeeking(false);
|
viewModel.setUserSeeking(false);
|
||||||
viewModel.seekTo(seekBar.getProgress(), msgId);
|
viewModel.seekTo(seekBar.getProgress(), msgId, audioUri);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -197,17 +194,14 @@ public class AudioView extends FrameLayout {
|
|||||||
|
|
||||||
seekBar.setEnabled(true);
|
seekBar.setEnabled(true);
|
||||||
|
|
||||||
this.progress = 0;
|
|
||||||
this.duration = 0;
|
|
||||||
|
|
||||||
viewModel.ensureDurationLoaded(getContext(), msgId, audioUri);
|
|
||||||
|
|
||||||
// Get duration
|
// Get duration
|
||||||
Map<Integer, Long> durations = viewModel.getDurations().getValue();
|
Map<Integer, Long> durations = viewModel.getDurations().getValue();
|
||||||
if (durations != null && durations.containsKey(msgId)) {
|
if (durations != null && durations.containsKey(msgId)) {
|
||||||
this.duration = Math.toIntExact(durations.get(msgId));
|
this.duration = Math.toIntExact(durations.get(msgId));
|
||||||
}
|
|
||||||
updateTimestampsAndSeekBar();
|
updateTimestampsAndSeekBar();
|
||||||
|
} else {
|
||||||
|
viewModel.ensureDurationLoaded(getContext(), msgId, audioUri);
|
||||||
|
}
|
||||||
|
|
||||||
if (audio.asAttachment().isVoiceNote() || !audio.getFileName().isPresent()) {
|
if (audio.asAttachment().isVoiceNote() || !audio.getFileName().isPresent()) {
|
||||||
title.setVisibility(View.GONE);
|
title.setVisibility(View.GONE);
|
||||||
@@ -311,7 +305,7 @@ public class AudioView extends FrameLayout {
|
|||||||
if (audioUri == null || state == null) return;
|
if (audioUri == null || state == null) return;
|
||||||
|
|
||||||
// Check if this state is about this message
|
// Check if this state is about this message
|
||||||
boolean isThisMessage = msgId == state.getMsgId();
|
boolean isThisMessage = msgId == state.getMsgId() && audioUri.equals(state.getAudioUri());
|
||||||
|
|
||||||
if (isThisMessage) {
|
if (isThisMessage) {
|
||||||
updateUIForPlaybackState(state);
|
updateUIForPlaybackState(state);
|
||||||
@@ -346,18 +340,20 @@ public class AudioView extends FrameLayout {
|
|||||||
private void onDurationsChanged(Map<Integer, Long> durations) {
|
private void onDurationsChanged(Map<Integer, Long> durations) {
|
||||||
AudioPlaybackState state = viewModel.getPlaybackState().getValue();
|
AudioPlaybackState state = viewModel.getPlaybackState().getValue();
|
||||||
|
|
||||||
|
// When there is no playback happening, msgId can be -1 and audioUri is null
|
||||||
if (state != null
|
if (state != null
|
||||||
&& msgId >= 0
|
&& msgId >= 0
|
||||||
&& msgId == state.getMsgId()
|
&& msgId == state.getMsgId()
|
||||||
&& (state.getStatus() == AudioPlaybackState.PlaybackStatus.PLAYING
|
&& audioUri != null
|
||||||
|| state.getStatus() == AudioPlaybackState.PlaybackStatus.PAUSED)) {
|
&& audioUri.equals(state.getAudioUri())) {
|
||||||
return;
|
return; // Is playing this message
|
||||||
}
|
}
|
||||||
|
|
||||||
Long duration = durations.get(msgId);
|
Long duration = durations.get(msgId);
|
||||||
if (duration != null) {
|
if (duration != null && seekBar.getMax() <= 100) {
|
||||||
this.duration = Math.toIntExact(duration);
|
this.duration = Math.toIntExact(duration);
|
||||||
updateTimestampsAndSeekBar();
|
updateTimestampsAndSeekBar();
|
||||||
|
seekBar.setMax(this.duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-41
@@ -1,41 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.components.audioplay;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.Uri;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.media3.common.MediaItem;
|
|
||||||
import com.b44t.messenger.DcContext;
|
|
||||||
import com.b44t.messenger.DcMsg;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
|
||||||
|
|
||||||
public class ChatAudioQueueProvider {
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final int chatId;
|
|
||||||
private final int accountId;
|
|
||||||
|
|
||||||
public ChatAudioQueueProvider(@NonNull Context context, int chatId, int accountId) {
|
|
||||||
this.context = context.getApplicationContext();
|
|
||||||
this.chatId = chatId;
|
|
||||||
this.accountId = accountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public List<MediaItem> buildAudioQueue() {
|
|
||||||
DcContext dcContext = DcHelper.getContext(context);
|
|
||||||
int[] msgIds = dcContext.getChatMedia(chatId, DcMsg.DC_MSG_AUDIO, DcMsg.DC_MSG_VOICE, 0);
|
|
||||||
|
|
||||||
List<MediaItem> items = new ArrayList<>(msgIds.length);
|
|
||||||
for (int msgId : msgIds) {
|
|
||||||
String id = String.valueOf(msgId);
|
|
||||||
items.add(
|
|
||||||
new MediaItem.Builder()
|
|
||||||
.setMediaId(id)
|
|
||||||
.setUri(Uri.parse("dcmsg://" + accountId + "/" + id))
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user