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'
|
||||
- 'scripts/ndk-make.sh'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
|
||||
- uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
|
||||
- uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0
|
||||
- uses: android-actions/setup-android@v3
|
||||
- uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: "r29"
|
||||
ndk-version: r27
|
||||
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: jni/deltachat-core-rust
|
||||
|
||||
@@ -37,7 +34,7 @@ jobs:
|
||||
|
||||
- name: Cache compiled core
|
||||
id: cache-core
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
jni/arm64-v8a
|
||||
|
||||
@@ -5,8 +5,6 @@ on:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
@@ -18,16 +16,12 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
- name: Restore Gradle cache
|
||||
id: gradle-cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -36,14 +30,6 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/actions/wrapper-validation@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/wrapper-validation@v4
|
||||
- name: Check formatting
|
||||
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 }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Upload Preview APK
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
|
||||
- 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:
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
|
||||
- uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -39,15 +33,15 @@ jobs:
|
||||
${{ runner.os }}-gradle-
|
||||
|
||||
- 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: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0
|
||||
- uses: android-actions/setup-android@v3
|
||||
- uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: "r29"
|
||||
ndk-version: r27
|
||||
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: jni/deltachat-core-rust
|
||||
|
||||
@@ -57,7 +51,7 @@ jobs:
|
||||
|
||||
- name: Restore compiled core
|
||||
id: core-cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
jni/arm64-v8a
|
||||
@@ -76,24 +70,7 @@ jobs:
|
||||
run: ./gradlew --no-daemon -PABI_FILTER=arm64-v8a assembleFossDebug
|
||||
|
||||
- name: Upload APK
|
||||
id: upload
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: app-preview.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
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
working-directory: jni/deltachat-core-rust
|
||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
- uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
|
||||
- uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0
|
||||
- uses: android-actions/setup-android@v3
|
||||
- uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: r27
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
mv build/outputs/mapping/gplayRelease/mapping.txt build/outputs/mapping/fossRelease/mapping-gplay.txt
|
||||
|
||||
- name: Release on GitHub
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
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)'
|
||||
|
||||
@@ -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.
|
||||
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.
|
||||
```
|
||||
$ scripts/ndk-make.sh
|
||||
@@ -56,7 +56,7 @@ $ ./gradlew assembleDebug
|
||||
|
||||
Resulting APK files can be found in
|
||||
`build/outputs/apk/gplay/debug/` and
|
||||
`build/outputs/apk/foss/debug/`.
|
||||
`build/outputs/apk/fat/debug/`.
|
||||
|
||||
## 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"`.
|
||||
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
|
||||
|
||||
@@ -135,8 +135,8 @@ See https://wiki.archlinux.org/index.php/Podman#Rootless_Podman for more informa
|
||||
|
||||
To setup build environment manually:
|
||||
- _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".
|
||||
- _Or_ read [Dockerfile](./Dockerfile) and mimic what it does.
|
||||
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](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/)
|
||||
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)
|
||||
|
||||
|
||||
### Deobfuscating Java Stack Traces
|
||||
### Deobfuscating Java/Kotlin Stack Traces
|
||||
|
||||
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:
|
||||
|
||||
+1
-58
@@ -1,69 +1,12 @@
|
||||
# Delta Chat Android Changelog
|
||||
|
||||
## v2.53.0
|
||||
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
|
||||
## Unreleased
|
||||
|
||||
* Better incoming call system integration
|
||||
* 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
|
||||
* Autoplay all voice messages in a chat
|
||||
* Allow to share location for 24 hours
|
||||
* 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)
|
||||
* 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
|
||||
2026-04
|
||||
|
||||
+2
-2
@@ -34,8 +34,8 @@ android {
|
||||
useLibrary 'org.apache.http.legacy'
|
||||
|
||||
defaultConfig {
|
||||
versionCode 30000746
|
||||
versionName "2.53.0"
|
||||
versionCode 30000742
|
||||
versionName "2.49.0"
|
||||
|
||||
applicationId "chat.delta.lite"
|
||||
multiDexEnabled true
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
buildInputs = [
|
||||
android-sdk
|
||||
pkgs.openjdk17
|
||||
pkgs.perl
|
||||
(pkgs.buildPackages.rust-bin.stable."${rust-version}".minimal.override {
|
||||
targets = [
|
||||
"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)
|
||||
{
|
||||
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!
|
||||
// ==============================================================================================
|
||||
|
||||
private static final String TAG = "EnterChatsBenchmark";
|
||||
private static final String TAG = EnterChatsBenchmark.class.getSimpleName();
|
||||
|
||||
@Rule
|
||||
public ActivityScenarioRule<ConversationListActivity> activityRule =
|
||||
|
||||
@@ -6,7 +6,7 @@ import android.util.Log;
|
||||
/** Non-GMS, always uses the platform LocationManager. */
|
||||
public final class LocationSourceFactory {
|
||||
|
||||
private static final String TAG = "LocationSourceFactory";
|
||||
private static final String TAG = LocationSourceFactory.class.getSimpleName();
|
||||
|
||||
private LocationSourceFactory() {}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import com.google.android.gms.location.Priority;
|
||||
|
||||
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 FASTEST_INTERVAL_MS = 1_000;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.google.android.gms.common.GoogleApiAvailability;
|
||||
*/
|
||||
public final class LocationSourceFactory {
|
||||
|
||||
private static final String TAG = "LocationSourceFactory";
|
||||
private static final String TAG = LocationSourceFactory.class.getSimpleName();
|
||||
|
||||
private LocationSourceFactory() {}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.service.FetchForegroundService;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
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 boolean initialized;
|
||||
private static volatile boolean triedRegistering;
|
||||
|
||||
@@ -71,7 +71,6 @@
|
||||
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_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.MANAGE_OWN_CALLS" />
|
||||
|
||||
@@ -491,12 +490,7 @@
|
||||
android:name=".calls.CallService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="camera|microphone|phoneCall" />
|
||||
|
||||
<receiver
|
||||
android:name=".calls.CallActionReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
android:foregroundServiceType="camera|microphone" />
|
||||
|
||||
<receiver
|
||||
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>
|
||||
</ul>
|
||||
</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>
|
||||
<ul>
|
||||
<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>.
|
||||
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">
|
||||
|
||||
|
||||
@@ -1631,20 +1425,23 @@ can not be identified easily.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
||||
<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="#experiments">call</a>
|
||||
or use <a href="#webxdc">apps</a> together.</p>
|
||||
|
||||
<p>IP Addresses are needed for connectivity and efficiency.
|
||||
Delta Chat neither persists nor exposes them.
|
||||
Note that IP Addresses
|
||||
are not like an address you give to a delivery service,
|
||||
but typically less precise, often defining city or region only.</p>
|
||||
They are neither persisted nor exposed.
|
||||
Note that the IP Address
|
||||
is not like a detailed address you give to a delivery service,
|
||||
but much more coarse, often defining region or country only.</p>
|
||||
|
||||
<p>If you see your IP Address as a risk,
|
||||
we recommend to use a VPN for the whole system.
|
||||
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>As this is just how the internet and other messengers work by default,
|
||||
we do not offer options here or ask upfront questions.</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">
|
||||
|
||||
|
||||
@@ -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>
|
||||
</ul>
|
||||
</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>
|
||||
<ul>
|
||||
<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>
|
||||
|
||||
<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">
|
||||
|
||||
|
||||
@@ -1535,16 +1329,23 @@ im Falle einer Beschlagnahmung des Geräts nicht ohne Weiteres identifiziert wer
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Die verwendeten <a href="#relays">Relays</a> müssen deine IP-Adresse kennen,
|
||||
sowie manchmal auch die Geräte deiner Kontakte, wenn du einen <a href="#calls">Anruf</a> tätigst
|
||||
oder ihr gemeinsam <a href="#webxdc">Apps</a> verwendet.</p>
|
||||
<p>Das verwendete <a href="#relays">Relay</a> muss Ihre IP-Adresse kennen,
|
||||
sowie manchmal auch die Geräte Ihrer Kontakte, wenn Sie einen <a href="#experiments">Anruf</a> tätigen
|
||||
oder gemeinsam <a href="#webxdc">Apps</a> verwenden.</p>
|
||||
|
||||
<p>IP-Adressen sind für Verbindungen und für Effizienz erforderlich.
|
||||
Sie werden von Delta Chat 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>
|
||||
Sie werden weder gespeichert noch offengelegt.
|
||||
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.
|
||||
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>
|
||||
<p>Da dies die Standardfunktion des Internets und anderer Messenger ist,
|
||||
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">
|
||||
|
||||
|
||||
@@ -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>
|
||||
</ul>
|
||||
</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>
|
||||
<ul>
|
||||
<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>.
|
||||
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">
|
||||
|
||||
|
||||
@@ -1631,20 +1425,23 @@ can not be identified easily.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
||||
<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="#experiments">call</a>
|
||||
or use <a href="#webxdc">apps</a> together.</p>
|
||||
|
||||
<p>IP Addresses are needed for connectivity and efficiency.
|
||||
Delta Chat neither persists nor exposes them.
|
||||
Note that IP Addresses
|
||||
are not like an address you give to a delivery service,
|
||||
but typically less precise, often defining city or region only.</p>
|
||||
They are neither persisted nor exposed.
|
||||
Note that the IP Address
|
||||
is not like a detailed address you give to a delivery service,
|
||||
but much more coarse, often defining region or country only.</p>
|
||||
|
||||
<p>If you see your IP Address as a risk,
|
||||
we recommend to use a VPN for the whole system.
|
||||
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>As this is just how the internet and other messengers work by default,
|
||||
we do not offer options here or ask upfront questions.</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">
|
||||
|
||||
|
||||
+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>
|
||||
</ul>
|
||||
</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>
|
||||
<ul>
|
||||
<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.
|
||||
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">
|
||||
|
||||
|
||||
@@ -1637,20 +1431,26 @@ txat-kontaktuak ezin izango dira erraz identifikatu.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
||||
or use <a href="#webxdc">apps</a> together.</p>
|
||||
<p>Erabiltzen duzun <a href="#relays">erreleak</a> zure IP helbidea jakin behar du,
|
||||
eta, batzuetan, baita zure kontaktuen gailuena ere; adibidez,
|
||||
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.
|
||||
Delta Chat neither persists nor exposes them.
|
||||
Note that IP Addresses
|
||||
are not like an address you give to a delivery service,
|
||||
but typically less precise, often defining city or region only.</p>
|
||||
<p>IP helbideak ezinbestekoak dira konektibitaterako eta eraginkortasunerako.
|
||||
Ez dira gordetzen, ez argitara ematen.
|
||||
Kontuan izan IP helbidea ez dela mezularitzako zerbitzuei-eta eman ohi zaiena
|
||||
bezalako helbide zehatz bat; askoz orokorragoa da, eta askotan eskualdea edo
|
||||
herrialdea baizik ez du identifikatzen.</p>
|
||||
|
||||
<p>If you see your IP Address as a risk,
|
||||
we recommend to use a VPN for the whole system.
|
||||
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>Internetek eta beste mezularitza-aplikazioek horrela funtzionatzen dutenez,
|
||||
ez dugu horren gaineko bestelako aukerarik eskaintzen, ez galderarik egiten.</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">
|
||||
|
||||
|
||||
@@ -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>
|
||||
</ul>
|
||||
</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>
|
||||
<ul>
|
||||
<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>.
|
||||
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">
|
||||
|
||||
|
||||
@@ -1544,20 +1338,23 @@ can not be identified easily.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
||||
<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="#experiments">call</a>
|
||||
or use <a href="#webxdc">apps</a> together.</p>
|
||||
|
||||
<p>IP Addresses are needed for connectivity and efficiency.
|
||||
Delta Chat neither persists nor exposes them.
|
||||
Note that IP Addresses
|
||||
are not like an address you give to a delivery service,
|
||||
but typically less precise, often defining city or region only.</p>
|
||||
They are neither persisted nor exposed.
|
||||
Note that the IP Address
|
||||
is not like a detailed address you give to a delivery service,
|
||||
but much more coarse, often defining region or country only.</p>
|
||||
|
||||
<p>If you see your IP Address as a risk,
|
||||
we recommend to use a VPN for the whole system.
|
||||
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>As this is just how the internet and other messengers work by default,
|
||||
we do not offer options here or ask upfront questions.</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">
|
||||
|
||||
|
||||
@@ -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>
|
||||
</ul>
|
||||
</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>
|
||||
<ul>
|
||||
<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>.
|
||||
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">
|
||||
|
||||
|
||||
@@ -1631,20 +1425,23 @@ can not be identified easily.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
||||
<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="#experiments">call</a>
|
||||
or use <a href="#webxdc">apps</a> together.</p>
|
||||
|
||||
<p>IP Addresses are needed for connectivity and efficiency.
|
||||
Delta Chat neither persists nor exposes them.
|
||||
Note that IP Addresses
|
||||
are not like an address you give to a delivery service,
|
||||
but typically less precise, often defining city or region only.</p>
|
||||
They are neither persisted nor exposed.
|
||||
Note that the IP Address
|
||||
is not like a detailed address you give to a delivery service,
|
||||
but much more coarse, often defining region or country only.</p>
|
||||
|
||||
<p>If you see your IP Address as a risk,
|
||||
we recommend to use a VPN for the whole system.
|
||||
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>As this is just how the internet and other messengers work by default,
|
||||
we do not offer options here or ask upfront questions.</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">
|
||||
|
||||
|
||||
@@ -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>
|
||||
</ul>
|
||||
</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>
|
||||
<ul>
|
||||
<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>.
|
||||
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">
|
||||
|
||||
|
||||
@@ -1617,20 +1412,23 @@ non possono essere identificati facilmente.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
||||
or use <a href="#webxdc">apps</a> together.</p>
|
||||
<p>Il <a href="#relays">ripetitore</a> utilizzato deve conoscere il tuo indirizzo IP,
|
||||
e talvolta anche i dispositivi dei tuoi contatti se avete una <a href="#experiments">chiamata</a>
|
||||
o utilizzate <a href="#webxdc">apps</a> insieme.</p>
|
||||
|
||||
<p>IP Addresses are needed for connectivity and efficiency.
|
||||
Delta Chat neither persists nor exposes them.
|
||||
Note that IP Addresses
|
||||
are not like an address you give to a delivery service,
|
||||
but typically less precise, often defining city or region only.</p>
|
||||
<p>Gli indirizzi IP sono necessari per la connettività e l’efficienza.
|
||||
Non sono né persistenti né esposti.
|
||||
Si noti che l’indirizzo IP
|
||||
non è come un indirizzo dettagliato che si fornisce a un servizio di consegna,
|
||||
ma molto più generico, che spesso definisce solo la regione o il paese.</p>
|
||||
|
||||
<p>If you see your IP Address as a risk,
|
||||
we recommend to use a VPN for the whole system.
|
||||
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>Poiché questo è il modo in cui Internet e altri servizi di messaggistica funzionano di default,
|
||||
non offriamo opzioni né poniamo domande in anticipo.</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">
|
||||
|
||||
|
||||
@@ -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>
|
||||
</ul>
|
||||
</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>
|
||||
<ul>
|
||||
<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>.
|
||||
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">
|
||||
|
||||
|
||||
@@ -1625,20 +1419,23 @@ can not be identified easily.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
||||
<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="#experiments">call</a>
|
||||
or use <a href="#webxdc">apps</a> together.</p>
|
||||
|
||||
<p>IP Addresses are needed for connectivity and efficiency.
|
||||
Delta Chat neither persists nor exposes them.
|
||||
Note that IP Addresses
|
||||
are not like an address you give to a delivery service,
|
||||
but typically less precise, often defining city or region only.</p>
|
||||
They are neither persisted nor exposed.
|
||||
Note that the IP Address
|
||||
is not like a detailed address you give to a delivery service,
|
||||
but much more coarse, often defining region or country only.</p>
|
||||
|
||||
<p>If you see your IP Address as a risk,
|
||||
we recommend to use a VPN for the whole system.
|
||||
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>As this is just how the internet and other messengers work by default,
|
||||
we do not offer options here or ask upfront questions.</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">
|
||||
|
||||
|
||||
+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">
|
||||
<li><a href="#czym-jest-delta-chat">Czym jest Delta Chat?</a>
|
||||
<ul>
|
||||
<li><a href="#howtoe2ee">Jak znaleźć osoby do czatu?</a></li>
|
||||
<li><a href="#dlaczego-czat-jest-oznaczony-jako-prośba">Dlaczego czat jest oznaczony jako „Prośba”?</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="#howtoe2ee">How can I find people to chat with?</a></li>
|
||||
<li><a href="#why-is-a-chat-marked-as-request">Why is a chat marked as “Request”?</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="#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="#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-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="#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="#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>
|
||||
</li>
|
||||
<li><a href="#groups">Grupy</a>
|
||||
<li><a href="#groups">Groups</a>
|
||||
<ul>
|
||||
<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="#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="#ilu-członków-może-należeć-do-jednej-grupy">Ilu członków może należeć do jednej grupy?</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>
|
||||
<li><a href="#cloning-a-group">Cloning a 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>
|
||||
</li>
|
||||
<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>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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</ul>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<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.
|
||||
Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą aplikacji Delta Chat.</p>
|
||||
<p>If you are <strong>face to face</strong> with your friend or family,
|
||||
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>
|
||||
<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>
|
||||
</ul>
|
||||
|
||||
<p>Poczekaj, aż połączenie zostanie nawiązane.</p>
|
||||
<p>Now wait while connection gets established.</p>
|
||||
|
||||
<ul>
|
||||
<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>
|
||||
<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>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
<li>
|
||||
@@ -196,17 +201,19 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
||||
</li>
|
||||
</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>
|
||||
|
||||
<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">
|
||||
|
||||
@@ -216,13 +223,15 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
||||
|
||||
</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>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>
|
||||
|
||||
@@ -234,19 +243,22 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
||||
|
||||
</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">
|
||||
|
||||
|
||||
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>
|
||||
|
||||
<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">
|
||||
|
||||
@@ -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>
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
|
||||
<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">
|
||||
|
||||
@@ -328,16 +344,19 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
||||
|
||||
<ul>
|
||||
<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>
|
||||
<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>
|
||||
</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">
|
||||
|
||||
@@ -363,22 +382,26 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</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>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>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">
|
||||
|
||||
@@ -406,35 +439,46 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
||||
|
||||
</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">
|
||||
|
||||
|
||||
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>
|
||||
|
||||
<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">
|
||||
|
||||
|
||||
Grupy <a href="#groups" class="anchor"></a>
|
||||
Groups <a href="#groups" class="anchor"></a>
|
||||
|
||||
|
||||
</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">
|
||||
|
||||
@@ -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>
|
||||
</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>
|
||||
<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>
|
||||
</ul>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<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>.
|
||||
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>
|
||||
<p>If the member is not yet in your contact list, but <strong>face to face</strong> with you,
|
||||
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>
|
||||
<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>
|
||||
</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">
|
||||
|
||||
@@ -489,7 +539,8 @@ Poproś partnera czatu o <strong>zeskanowanie</strong> obrazu QR za pomocą apli
|
||||
|
||||
</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">
|
||||
|
||||
@@ -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>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
|
||||
<h2 id="channels">
|
||||
|
||||
|
||||
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>
|
||||
<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>.
|
||||
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
||||
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</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">
|
||||
|
||||
@@ -1392,20 +1289,23 @@ can not be identified easily.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
||||
<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="#experiments">call</a>
|
||||
or use <a href="#webxdc">apps</a> together.</p>
|
||||
|
||||
<p>IP Addresses are needed for connectivity and efficiency.
|
||||
Delta Chat neither persists nor exposes them.
|
||||
Note that IP Addresses
|
||||
are not like an address you give to a delivery service,
|
||||
but typically less precise, often defining city or region only.</p>
|
||||
They are neither persisted nor exposed.
|
||||
Note that the IP Address
|
||||
is not like a detailed address you give to a delivery service,
|
||||
but much more coarse, often defining region or country only.</p>
|
||||
|
||||
<p>If you see your IP Address as a risk,
|
||||
we recommend to use a VPN for the whole system.
|
||||
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>As this is just how the internet and other messengers work by default,
|
||||
we do not offer options here or ask upfront questions.</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">
|
||||
|
||||
|
||||
@@ -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>
|
||||
</ul>
|
||||
</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>
|
||||
<ul>
|
||||
<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>.
|
||||
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">
|
||||
|
||||
|
||||
@@ -1626,20 +1420,23 @@ can not be identified easily.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
||||
<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="#experiments">call</a>
|
||||
or use <a href="#webxdc">apps</a> together.</p>
|
||||
|
||||
<p>IP Addresses are needed for connectivity and efficiency.
|
||||
Delta Chat neither persists nor exposes them.
|
||||
Note that IP Addresses
|
||||
are not like an address you give to a delivery service,
|
||||
but typically less precise, often defining city or region only.</p>
|
||||
They are neither persisted nor exposed.
|
||||
Note that the IP Address
|
||||
is not like a detailed address you give to a delivery service,
|
||||
but much more coarse, often defining region or country only.</p>
|
||||
|
||||
<p>If you see your IP Address as a risk,
|
||||
we recommend to use a VPN for the whole system.
|
||||
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>As this is just how the internet and other messengers work by default,
|
||||
we do not offer options here or ask upfront questions.</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">
|
||||
|
||||
|
||||
@@ -29,21 +29,6 @@
|
||||
<li><a href="#сколько-участников-может-быть-в-одной-группе">Сколько участников может быть в одной группе?</a></li>
|
||||
</ul>
|
||||
</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>
|
||||
<ul>
|
||||
<li><a href="#где-можно-найти-встроенные-приложения">Где можно найти встроенные приложения?</a></li>
|
||||
@@ -637,197 +622,6 @@
|
||||
в то время как Delta Chat - это приватный мессенджер для общения на <a href="#groups">равных правах</a>.
|
||||
Смотрите <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">
|
||||
|
||||
|
||||
@@ -1624,20 +1418,25 @@ Delta Chat вместо этого использует реализацию Ope
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Используемым <a href="#relays">релеям</a> необходимо знать ваш IP-адрес,
|
||||
а в некоторых случаях — данные устройств ваших контактов, если вы совершаете <a href="#calls">вызов</a>
|
||||
или совместно используете <a href="#webxdc">приложения</a>.</p>
|
||||
<p>Используемый <a href="#relays">релей</a> должен знать ваш IP-адрес,
|
||||
а также иногда устройства ваших контактов, если вы проводите совместные <a href="#experiments">звонки</a>
|
||||
или используете <a href="#webxdc">приложения</a>.</p>
|
||||
|
||||
<p>IP-адреса необходимы для обеспечения связи и эффективной работы.
|
||||
Delta Chat не сохраняет их и не раскрывает третьим лицам.
|
||||
Обратите внимание, что IP-адрес
|
||||
— это не тот же адрес, который вы указываете службе доставки,
|
||||
он, как правило, менее точен и зачастую позволяет определить лишь город или регион.</p>
|
||||
<p>IP-адреса необходимы для обеспечения соединения и эффективности.
|
||||
Они не сохраняются и не передаются третьим лицам.
|
||||
Обратите внимание, что IP-адрес</p>
|
||||
<ul>
|
||||
<li>это не подробный адрес, который вы указываете службе доставки,
|
||||
а скорее приблизительный, обычно определяющий регион или страну.</li>
|
||||
</ul>
|
||||
|
||||
<p>Если вы считаете свой IP-адрес зоной риска,
|
||||
мы рекомендуем использовать VPN для всей системы.
|
||||
Настройка VPN для отдельных приложений оставляет уязвимости в общей защите устройства.
|
||||
Например, нажатие на ссылку может раскрыть ваш IP-адрес неизвестным сторонам, что представляет собой гораздо больший риск.</p>
|
||||
<p>Поскольку именно так по умолчанию работает интернет и другие мессенджеры,
|
||||
мы не предлагаем здесь никаких настроек и не задаём предварительных вопросов</p>
|
||||
|
||||
<p>Если вы считаете свой IP-адрес угрозой безопасности или конфиденциальности,
|
||||
мы рекомендуем использовать VPN в сочетании с режимом блокировки системы.
|
||||
Поиск настроек во всех приложениях на вашем устройстве оставит уязвимости.
|
||||
Например, нажатие на ссылку раскрывает IP-адрес неизвестным лицам и представляет собой гораздо больший риск в данном случае.</p>
|
||||
|
||||
<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>
|
||||
</ul>
|
||||
</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>
|
||||
<ul>
|
||||
<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>.
|
||||
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">
|
||||
|
||||
|
||||
@@ -1631,20 +1425,23 @@ can not be identified easily.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
||||
<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="#experiments">call</a>
|
||||
or use <a href="#webxdc">apps</a> together.</p>
|
||||
|
||||
<p>IP Addresses are needed for connectivity and efficiency.
|
||||
Delta Chat neither persists nor exposes them.
|
||||
Note that IP Addresses
|
||||
are not like an address you give to a delivery service,
|
||||
but typically less precise, often defining city or region only.</p>
|
||||
They are neither persisted nor exposed.
|
||||
Note that the IP Address
|
||||
is not like a detailed address you give to a delivery service,
|
||||
but much more coarse, often defining region or country only.</p>
|
||||
|
||||
<p>If you see your IP Address as a risk,
|
||||
we recommend to use a VPN for the whole system.
|
||||
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>As this is just how the internet and other messengers work by default,
|
||||
we do not offer options here or ask upfront questions.</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">
|
||||
|
||||
|
||||
@@ -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>
|
||||
</ul>
|
||||
</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>
|
||||
<ul>
|
||||
<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>.
|
||||
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">
|
||||
|
||||
|
||||
@@ -1633,20 +1427,23 @@ can not be identified easily.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
||||
<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="#experiments">call</a>
|
||||
or use <a href="#webxdc">apps</a> together.</p>
|
||||
|
||||
<p>IP Addresses are needed for connectivity and efficiency.
|
||||
Delta Chat neither persists nor exposes them.
|
||||
Note that IP Addresses
|
||||
are not like an address you give to a delivery service,
|
||||
but typically less precise, often defining city or region only.</p>
|
||||
They are neither persisted nor exposed.
|
||||
Note that the IP Address
|
||||
is not like a detailed address you give to a delivery service,
|
||||
but much more coarse, often defining region or country only.</p>
|
||||
|
||||
<p>If you see your IP Address as a risk,
|
||||
we recommend to use a VPN for the whole system.
|
||||
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>As this is just how the internet and other messengers work by default,
|
||||
we do not offer options here or ask upfront questions.</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">
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<li><a href="#що-таке-delta-chat">Що таке Delta Chat?</a>
|
||||
<ul>
|
||||
<li><a href="#howtoe2ee">Як мені знайти людей для спілкування?</a></li>
|
||||
<li><a href="#чому-чат-позначений-як-запит">Чому чат позначений як «Запит»?</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="#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="#хто-бачить-моє-зображення-профілю">Хто бачить моє зображення профілю?</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>
|
||||
</ul>
|
||||
</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>
|
||||
<ul>
|
||||
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
||||
@@ -166,7 +151,8 @@
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Якщо обидві сторони перебувають у мережі, незабаром з’явиться вікно чату, і вони зможуть безпечно обмінюватися повідомленнями.</p>
|
||||
<p>If both sides are online, they will soon see a chat
|
||||
and can start messaging securely.</p>
|
||||
</li>
|
||||
<li>
|
||||
<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="#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>
|
||||
|
||||
<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>
|
||||
<li>
|
||||
@@ -202,17 +190,19 @@ the ability to chat is delayed until connectivity is restored.</p>
|
||||
</li>
|
||||
</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>
|
||||
|
||||
<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">
|
||||
|
||||
@@ -222,7 +212,9 @@ the ability to chat is delayed until connectivity is restored.</p>
|
||||
|
||||
</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>
|
||||
|
||||
@@ -252,7 +244,10 @@ the ability to chat is delayed until connectivity is restored.</p>
|
||||
|
||||
</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="що-значить-закріплення-приглушення-архівування">
|
||||
|
||||
@@ -320,7 +315,10 @@ the ability to chat is delayed until connectivity is restored.</p>
|
||||
|
||||
</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
|
||||
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>.
|
||||
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">
|
||||
|
||||
|
||||
@@ -1461,20 +1268,23 @@ can not be identified easily.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
||||
<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="#experiments">call</a>
|
||||
or use <a href="#webxdc">apps</a> together.</p>
|
||||
|
||||
<p>IP Addresses are needed for connectivity and efficiency.
|
||||
Delta Chat neither persists nor exposes them.
|
||||
Note that IP Addresses
|
||||
are not like an address you give to a delivery service,
|
||||
but typically less precise, often defining city or region only.</p>
|
||||
They are neither persisted nor exposed.
|
||||
Note that the IP Address
|
||||
is not like a detailed address you give to a delivery service,
|
||||
but much more coarse, often defining region or country only.</p>
|
||||
|
||||
<p>If you see your IP Address as a risk,
|
||||
we recommend to use a VPN for the whole system.
|
||||
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>As this is just how the internet and other messengers work by default,
|
||||
we do not offer options here or ask upfront questions.</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">
|
||||
|
||||
|
||||
@@ -32,21 +32,6 @@
|
||||
</li>
|
||||
</ul>
|
||||
</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>
|
||||
<ul>
|
||||
<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> 聊天的私人信使。
|
||||
相关知识,请参阅<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">
|
||||
|
||||
|
||||
@@ -1620,20 +1414,22 @@ Delta Chat 应用程序不会在服务器上存储任何有关联系人或群组
|
||||
|
||||
</h4>
|
||||
|
||||
<p>The used <a href="#relays">relays</a> need to know your IP Address,
|
||||
as well as sometimes your contact’s devices if you have a <a href="#calls">call</a>
|
||||
or use <a href="#webxdc">apps</a> together.</p>
|
||||
<p>使用的 <a href="#relays">中继服务器</a> 需要知道您的 IP 地址、
|
||||
有时还需要知道联系人的设备(如果你们有 <a href="#experiments">通话</a>),或一起使用 <a href="#webxdc">Webxdc应用程序</a>。</p>
|
||||
|
||||
<p>IP Addresses are needed for connectivity and efficiency.
|
||||
Delta Chat neither persists nor exposes them.
|
||||
Note that IP Addresses
|
||||
are not like an address you give to a delivery service,
|
||||
but typically less precise, often defining city or region only.</p>
|
||||
<p>IP 地址是连接和提高效率所必需的。
|
||||
它们既不会持久存在,也不会暴露。
|
||||
请注意,IP 地址
|
||||
不像你给快递服务的详细地址、
|
||||
而是更粗略,通常只定义地区或国家。</p>
|
||||
|
||||
<p>If you see your IP Address as a risk,
|
||||
we recommend to use a VPN for the whole system.
|
||||
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>这只是互联网和其他信使的默认工作方式、
|
||||
我们在此不提供选项,也不预先提问。</p>
|
||||
|
||||
<p>如果你认为你的 IP 地址存在安全或隐私风险、
|
||||
我们建议使用 VPN 并结合系统锁定模式。
|
||||
在系统的所有应用程序中查找选项会留下漏洞。
|
||||
例如,点击链接会将 IP 地址暴露给未知方,这是目前最大的风险。</p>
|
||||
|
||||
<p>###Delta Chat 是否支持 “密封发件人”?{#sealedsender}</p>
|
||||
|
||||
|
||||
@@ -443,19 +443,10 @@ public class Rpc {
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimates the number of messages that will be deleted
|
||||
* by the `set_config()`-option `delete_device_after`.
|
||||
* <p>
|
||||
* Estimate the number of messages that will be deleted
|
||||
* by the set_config()-options `delete_device_after` or `delete_server_after`.
|
||||
* This is typically used to show the estimated impact to the user
|
||||
* 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 {
|
||||
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,
|
||||
* which would make it hard to grep for it.
|
||||
* <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.
|
||||
*/
|
||||
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>
|
||||
* 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,
|
||||
* e.g. [1234, 1237, 9, 1239]. The day marker timestamp is the midnight one for the
|
||||
* 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
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));
|
||||
}
|
||||
|
||||
/** 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.
|
||||
* <p>
|
||||
@@ -1351,47 +1328,10 @@ public class Rpc {
|
||||
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 {
|
||||
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 {
|
||||
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));
|
||||
}
|
||||
|
||||
/** @deprecated as of 2026-04; use `send_msg` with `Viewtype::Sticker` instead. */
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a reaction to message.
|
||||
* Send a reaction to message.
|
||||
* <p>
|
||||
* A reaction is a string that represents an emoji.
|
||||
* You can call this function again to change the emoji;
|
||||
* the last sent reaction overrides all previously sent reactions.
|
||||
* It is possible to remove the reaction by sending an empty string.
|
||||
* Reaction is a string of emojis separated by spaces. Reaction to a
|
||||
* single message can be sent multiple times. The last reaction
|
||||
* received overrides all previously received reactions. It is
|
||||
* possible to remove all reactions by sending an empty string.
|
||||
*/
|
||||
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));
|
||||
|
||||
@@ -12,13 +12,6 @@ public class EnteredLoginParam {
|
||||
/** 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)
|
||||
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. */
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||
public Integer imapPort;
|
||||
|
||||
@@ -5,6 +5,6 @@ package chat.delta.rpc.types;
|
||||
public class Reactions {
|
||||
/** Unique reactions and their count, sorted in descending order. */
|
||||
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;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ public enum Viewtype {
|
||||
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>
|
||||
* 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;
|
||||
|
||||
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 */
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||
public String document;
|
||||
@@ -17,10 +15,6 @@ public class WebxdcMessageInfo {
|
||||
public String icon;
|
||||
/** True if full internet access should be granted to the app. */
|
||||
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.
|
||||
* <p>
|
||||
|
||||
@@ -84,7 +84,7 @@ public class DcAccounts {
|
||||
public boolean isAllChatmail() {
|
||||
for (int accountId : getAll()) {
|
||||
DcContext dcContext = getAccount(accountId);
|
||||
if (dcContext.getConfigInt("is_chatmail") == 0) {
|
||||
if (!dcContext.isChatmail()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,6 +362,10 @@ public class DcContext {
|
||||
return displayname;
|
||||
}
|
||||
|
||||
public boolean isChatmail() {
|
||||
return getConfigInt("is_chatmail") == 1;
|
||||
}
|
||||
|
||||
public boolean isMuted() {
|
||||
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_BASICWEBRTC = 1;
|
||||
|
||||
private static final String TAG = DcMsg.class.getSimpleName();
|
||||
|
||||
public DcMsg(DcContext context, int viewtype) {
|
||||
msgCPtr = context.createMsgCPtr(viewtype);
|
||||
}
|
||||
@@ -194,6 +196,8 @@ public class DcMsg {
|
||||
|
||||
public native void setHtml(String text);
|
||||
|
||||
public native void forceSticker();
|
||||
|
||||
public native void setFileAndDeduplicate(String file, String name, String filemime);
|
||||
|
||||
public native void setDimension(int width, int height);
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
@@ -11,23 +12,21 @@ import androidx.appcompat.view.ActionMode;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
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.media3.session.MediaController;
|
||||
import androidx.media3.session.SessionCommand;
|
||||
import androidx.media3.session.SessionToken;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import java.util.ArrayList;
|
||||
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.DcHelper;
|
||||
import org.thoughtcrime.securesms.service.AudioPlaybackService;
|
||||
@@ -36,7 +35,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
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 CONTACT_ID_EXTRA = "contact_id";
|
||||
@@ -55,6 +54,7 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
this.type3 = type3;
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
private DcContext dcContext;
|
||||
private int chatId;
|
||||
@@ -63,7 +63,7 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
private final ArrayList<TabData> tabs = new ArrayList<>();
|
||||
private Toolbar toolbar;
|
||||
private TabLayout tabLayout;
|
||||
private ViewPager2 viewPager;
|
||||
private ViewPager viewPager;
|
||||
|
||||
private @Nullable MediaController mediaController;
|
||||
private ListenableFuture<MediaController> mediaControllerFuture;
|
||||
@@ -97,20 +97,8 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
isGlobalGallery() ? R.string.menu_all_media : R.string.apps_and_media);
|
||||
}
|
||||
|
||||
AllMediaPagerAdapter adapter = new AllMediaPagerAdapter(this);
|
||||
this.viewPager.setAdapter(adapter);
|
||||
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();
|
||||
this.tabLayout.setupWithViewPager(viewPager);
|
||||
this.viewPager.setAdapter(new AllMediaPagerAdapter(getSupportFragmentManager()));
|
||||
if (getIntent().getBooleanExtra(FORCE_GALLERY, 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_CONTACTS_CHANGED, this);
|
||||
|
||||
int accountId = DcHelper.getAccounts(this).getSelectedAccount().getAccountId();
|
||||
playbackViewModel = new ViewModelProvider(this).get(AudioPlaybackViewModel.class);
|
||||
playbackViewModel.setQueueProvider(new ChatAudioQueueProvider(this, chatId, accountId));
|
||||
initializeMediaController();
|
||||
}
|
||||
|
||||
@@ -133,7 +119,6 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
mediaController = null;
|
||||
playbackViewModel.setMediaController(null);
|
||||
}
|
||||
playbackViewModel.setQueueProvider(null);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@@ -197,16 +182,31 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
return contactId == 0 && chatId == 0;
|
||||
}
|
||||
|
||||
private class AllMediaPagerAdapter extends FragmentStateAdapter {
|
||||
private int currentPosition = -1;
|
||||
private class AllMediaPagerAdapter extends FragmentStatePagerAdapter {
|
||||
private Object currentFragment = null;
|
||||
|
||||
AllMediaPagerAdapter(FragmentActivity activity) {
|
||||
super(activity);
|
||||
AllMediaPagerAdapter(FragmentManager fragmentManager) {
|
||||
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
|
||||
@Override
|
||||
public Fragment createFragment(int position) {
|
||||
public Fragment getItem(int position) {
|
||||
TabData data = tabs.get(position);
|
||||
Fragment fragment;
|
||||
Bundle args = new Bundle();
|
||||
@@ -229,24 +229,13 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
public int getCount() {
|
||||
return tabs.size();
|
||||
}
|
||||
|
||||
private void onPageChanged(int newPosition) {
|
||||
if (currentPosition != -1 && currentPosition != newPosition) {
|
||||
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
|
||||
if (!(fragment instanceof MessageSelectorFragment)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ActionMode action = ((MessageSelectorFragment) fragment).getActionMode();
|
||||
if (action != null) {
|
||||
action.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
currentPosition = newPosition;
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
return getString(tabs.get(position).title);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.webxdc.WebxdcGarbageCollectionWorker;
|
||||
|
||||
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 volatile boolean isInitialized = false;
|
||||
|
||||
@@ -218,7 +218,7 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
Log.i(TAG, "DcAccounts created");
|
||||
rpc = new Rpc(new FFITransport(dcAccounts.getJsonrpcInstance()));
|
||||
Log.i(TAG, "Rpc created");
|
||||
AccountManager.getInstance().migrateToDcAccounts(this, dcAccounts);
|
||||
AccountManager.getInstance().migrateToDcAccounts(this);
|
||||
|
||||
int[] allAccounts = dcAccounts.getAll();
|
||||
Log.i(TAG, "Number of profiles: " + allAccounts.length);
|
||||
@@ -255,6 +255,12 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
// 2025-12-16: The setting was removed.
|
||||
// Revert it to the default if it was changed in the past.
|
||||
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) {
|
||||
try {
|
||||
@@ -302,10 +308,7 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
Log.i(
|
||||
"DeltaChat",
|
||||
"++++++++++++++++++ NetworkCallback.onAvailable() #" + debugOnAvailableCount++);
|
||||
// onBlockedStatusChanged is only available on API 29+
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
getDcAccounts().maybeNetwork();
|
||||
}
|
||||
getDcAccounts().maybeNetwork();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -313,13 +316,8 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
@NonNull android.net.Network network, boolean blocked) {
|
||||
Log.i(
|
||||
"DeltaChat",
|
||||
"++++++++++++++++++ NetworkCallback.onBlockedStatusChanged("
|
||||
+ blocked
|
||||
+ ") #"
|
||||
"++++++++++++++++++ NetworkCallback.onBlockedStatusChanged() #"
|
||||
+ debugOnBlockedStatusChangedCount++);
|
||||
if (!blocked) {
|
||||
getDcAccounts().maybeNetwork();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
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 void onPreCreate() {
|
||||
|
||||
@@ -41,12 +41,13 @@ import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.connect.DirectShareUtil;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.SendRelayedMessageUtil;
|
||||
import org.thoughtcrime.securesms.util.ShareUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask;
|
||||
import org.thoughtcrime.securesms.util.views.ProgressDialog;
|
||||
|
||||
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 PulsingFloatingActionButton fab;
|
||||
|
||||
@@ -422,18 +423,9 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
.setIcon(IconCompat.createWithAdaptiveBitmap(avatar))
|
||||
.setIntent(intent)
|
||||
.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(
|
||||
() -> {
|
||||
if (!finalSuccess) {
|
||||
if (!ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfoCompat, null)) {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
"ErrAddToHomescreen: requestPinShortcut() failed",
|
||||
@@ -487,6 +479,10 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
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();
|
||||
if (context != null) {
|
||||
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
|
||||
implements ContactSelectionListFragment.OnContactSelectedListener {
|
||||
private static final String TAG = "ContactSelectionActivity";
|
||||
private static final String TAG = ContactSelectionActivity.class.getSimpleName();
|
||||
|
||||
protected ContactSelectionListFragment contactsFragment;
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
*/
|
||||
public class ContactSelectionListFragment extends Fragment
|
||||
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 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.audioplay.AudioPlaybackViewModel;
|
||||
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.connect.AccountManager;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
@@ -149,7 +148,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
InputPanel.Listener,
|
||||
InputPanel.MediaListener,
|
||||
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 CHAT_ID_EXTRA = "chat_id";
|
||||
@@ -200,6 +199,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private final boolean isSecureText = true;
|
||||
private boolean isDefaultSms = true;
|
||||
private boolean isSecurityInitialized = false;
|
||||
private boolean successfulForwardingAttempt = false;
|
||||
private boolean isEditing = false;
|
||||
private boolean switchedProfile = false;
|
||||
|
||||
@@ -223,9 +223,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
initializeViews();
|
||||
initializeResources();
|
||||
|
||||
int accountId = DcHelper.getAccounts(this).getSelectedAccount().getAccountId();
|
||||
playbackViewModel = new ViewModelProvider(this).get(AudioPlaybackViewModel.class);
|
||||
playbackViewModel.setQueueProvider(new ChatAudioQueueProvider(this, chatId, accountId));
|
||||
initializeMediaController();
|
||||
|
||||
initializeSecurity(false, isDefaultSms)
|
||||
@@ -386,11 +384,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (inputPanel.isRecording() && inputPanel.getRecordingDuration() > 1000) {
|
||||
saveRecording();
|
||||
} else {
|
||||
processComposeControls(ACTION_SAVE_DRAFT);
|
||||
}
|
||||
processComposeControls(ACTION_SAVE_DRAFT);
|
||||
|
||||
DcHelper.getNotificationCenter(this).clearVisibleChat();
|
||||
if (isFinishing()) overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right);
|
||||
@@ -419,7 +413,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
mediaController = null;
|
||||
playbackViewModel.setMediaController(null);
|
||||
}
|
||||
playbackViewModel.setQueueProvider(null);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@@ -639,12 +632,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_start_audio_call) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
CallUtil.startAudioCall(this, chatId);
|
||||
CallUtil.startAudioCall(context, chatId);
|
||||
}
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_start_video_call) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
CallUtil.startVideoCall(this, chatId);
|
||||
CallUtil.startVideoCall(context, chatId);
|
||||
}
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_all_media) {
|
||||
@@ -679,14 +672,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
public void setDraftText(String txt) {
|
||||
try {
|
||||
if (rpc.canSend(rpc.getSelectedAccountId(), chatId)) {
|
||||
composeText.setText(txt);
|
||||
composeText.setSelection(composeText.getText().length());
|
||||
}
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "Rpc error", e);
|
||||
}
|
||||
composeText.setText(txt);
|
||||
composeText.setSelection(composeText.getText().length());
|
||||
}
|
||||
|
||||
public void hideSoftKeyboard() {
|
||||
@@ -716,13 +703,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
extras.putInt(ConversationListFragment.RELOAD_LIST, 1);
|
||||
}
|
||||
|
||||
if (attachmentManager.isAttachmentPresent()) {
|
||||
SlideDeck slideDeck = attachmentManager.buildSlideDeck();
|
||||
int audioDraftId = slideDeck.getAudioDraftId();
|
||||
if (audioDraftId != 0) {
|
||||
playbackViewModel.stop(audioDraftId);
|
||||
}
|
||||
}
|
||||
playbackViewModel.stopNonMessageAudioPlayback();
|
||||
|
||||
boolean archived = getIntent().getBooleanExtra(FROM_ARCHIVED_CHATS_EXTRA, false);
|
||||
Intent intent =
|
||||
@@ -855,17 +836,20 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
if (dcChat.isSelfTalk()) {
|
||||
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
|
||||
} else {
|
||||
int messageIds[] = ShareUtil.getForwardedMessageIDs(this);
|
||||
int messageCount = messageIds == null ? 0 : messageIds.length;
|
||||
String name = dcChat.getName();
|
||||
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)
|
||||
.setMessage(
|
||||
getResources()
|
||||
.getQuantityString(
|
||||
R.plurals.ask_forward_messages, messageCount, messageCount, dcChat.getName()))
|
||||
.setMessage(getString(R.string.ask_forward, name))
|
||||
.setPositiveButton(
|
||||
R.string.forward,
|
||||
R.string.ok,
|
||||
(dialogInterface, i) -> {
|
||||
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
|
||||
successfulForwardingAttempt = true;
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (dialogInterface, i) -> finish())
|
||||
.setOnCancelListener(dialog -> finish())
|
||||
@@ -889,10 +873,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private void handleSharing() {
|
||||
ArrayList<Uri> uriList = ShareUtil.getSharedUris(this);
|
||||
int sharedContactId = ShareUtil.getSharedContactId(this);
|
||||
if (uriList.size() > 1) {
|
||||
askSendingFiles(uriList, () -> SendRelayedMessageUtil.immediatelyRelay(this, chatId));
|
||||
} else {
|
||||
if (ShareUtil.getSharedHtml(this) != null
|
||||
if (sharedContactId != 0) {
|
||||
addAttachmentContactInfo(sharedContactId);
|
||||
} else if (ShareUtil.getSharedHtml(this) != null
|
||||
|| ShareUtil.getSharedSubject(this) != null
|
||||
|| ("sticker".equals(ShareUtil.getSharedType(this)) && !uriList.isEmpty())) {
|
||||
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
|
||||
@@ -980,7 +967,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
setMedia(draft, MediaType.GIF).addListener(listener);
|
||||
break;
|
||||
case DcMsg.DC_MSG_AUDIO:
|
||||
case DcMsg.DC_MSG_VOICE:
|
||||
setMedia(draft, MediaType.AUDIO).addListener(listener);
|
||||
break;
|
||||
case DcMsg.DC_MSG_VIDEO:
|
||||
@@ -1133,7 +1119,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
if (chatId == DcChat.DC_CHAT_NO_CHAT)
|
||||
throw new IllegalStateException("can't display a conversation for no chat.");
|
||||
dcChat = DcHelper.getContext(context).getChat(chatId);
|
||||
attachmentTypeSelector = null;
|
||||
recipient = new Recipient(this, dcChat);
|
||||
glideRequests = GlideApp.with(this);
|
||||
|
||||
@@ -1276,17 +1261,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
inputPanel.clearSubject();
|
||||
}
|
||||
|
||||
// Stop draft audio playback
|
||||
if (slideDeck != null) {
|
||||
int audioDraftId = slideDeck.getAudioDraftId();
|
||||
if (audioDraftId != 0) {
|
||||
playbackViewModel.stop(audioDraftId);
|
||||
}
|
||||
}
|
||||
// Stop draft audio playback regardless, since it is unlikely
|
||||
// we will need background playback for drafts
|
||||
playbackViewModel.stopNonMessageAudioPlayback();
|
||||
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
final int currentChatId = dcChat.getId();
|
||||
final boolean canSend = dcChat.canSend();
|
||||
Util.runOnAnyBackgroundThread(
|
||||
() -> {
|
||||
DcMsg msg = null;
|
||||
@@ -1409,8 +1389,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// set or clear draft if user can't send in chat since they can't delete it otherwise
|
||||
dcContext.setDraft(currentChatId, canSend ? msg : null);
|
||||
dcContext.setDraft(currentChatId, msg);
|
||||
}
|
||||
future.set(currentChatId);
|
||||
});
|
||||
@@ -1632,6 +1611,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
msg.setQuote(quote.get().getQuotedMsg());
|
||||
}
|
||||
msg.setFileAndDeduplicate(path, null, null);
|
||||
msg.forceSticker();
|
||||
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 {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
@@ -80,7 +80,7 @@ import org.thoughtcrime.securesms.util.views.ConversationAdaptiveActionsToolbar;
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
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;
|
||||
|
||||
@@ -944,8 +944,6 @@ public class ConversationFragment extends MessageSelectorFragment {
|
||||
WebxdcActivity.openWebxdcActivity(
|
||||
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) {
|
||||
Intent intent = new Intent(getContext(), ProfileActivity.class);
|
||||
intent.putExtra(ProfileActivity.CHAT_ID_EXTRA, (int) chatId);
|
||||
|
||||
@@ -24,7 +24,6 @@ import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
@@ -37,7 +36,6 @@ import androidx.annotation.DimenRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import chat.delta.rpc.RpcException;
|
||||
import chat.delta.rpc.types.CallInfo;
|
||||
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.DcContact;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
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.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.Linkifier;
|
||||
import org.thoughtcrime.securesms.util.LongClickCopySpan;
|
||||
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||
import org.thoughtcrime.securesms.util.MarkdownUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
@@ -90,7 +86,7 @@ import org.thoughtcrime.securesms.util.views.Stub;
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
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();
|
||||
|
||||
@@ -124,9 +120,6 @@ public class ConversationItem extends BaseConversationItem {
|
||||
private Stub<CallItemView> callViewStub;
|
||||
private @Nullable EventListener eventListener;
|
||||
|
||||
// IDs of accessibility actions registered via ViewCompat.addAccessibilityAction
|
||||
private final List<Integer> linkActionIds = new ArrayList<>();
|
||||
|
||||
private int measureCalls;
|
||||
|
||||
private int incomingBubbleColor;
|
||||
@@ -431,12 +424,6 @@ public class ConversationItem extends BaseConversationItem {
|
||||
bodyText.setClickable(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 text = messageRecord.getText();
|
||||
|
||||
@@ -446,33 +433,12 @@ public class ConversationItem extends BaseConversationItem {
|
||||
if (messageRecord.getType() == DcMsg.DC_MSG_CALL || text.isEmpty()) {
|
||||
bodyText.setVisibility(View.GONE);
|
||||
} else {
|
||||
Spannable spannable = MarkdownUtil.toMarkdown(context, text);
|
||||
Spannable spannable = (Spannable) MarkdownUtil.toMarkdown(context, text);
|
||||
if (batchSelected.isEmpty()) {
|
||||
Linkifier.linkify(spannable);
|
||||
spannable = Linkifier.linkify(spannable);
|
||||
}
|
||||
bodyText.setText(spannable);
|
||||
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();
|
||||
@@ -1096,18 +1062,7 @@ public class ConversationItem extends BaseConversationItem {
|
||||
if (!messageRecord.isOutgoing() && callInfo.state instanceof CallState.Alerting) {
|
||||
int callId = messageRecord.getId();
|
||||
CallCoordinator coordinator = CallCoordinator.getInstance(context);
|
||||
|
||||
if (coordinator.hasActiveCall()) {
|
||||
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);
|
||||
}
|
||||
coordinator.showIncomingCallScreen(callId);
|
||||
} else {
|
||||
if (callInfo.hasVideo) {
|
||||
CallUtil.startVideoCall(getContext(), chatId);
|
||||
|
||||
@@ -91,7 +91,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class ConversationListActivity extends PassphraseRequiredActionBarActivity
|
||||
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 NDK_ARCH_WARNED = "ndk_arch_warned";
|
||||
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() {
|
||||
resetRelayingMessageContent(this);
|
||||
refreshTitle();
|
||||
@@ -727,7 +720,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
refreshAvatar();
|
||||
refreshUnreadIndicator();
|
||||
refreshTitle();
|
||||
conversationListFragment.resetScrollPosition();
|
||||
conversationListFragment.loadChatlistAsync();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -55,16 +56,16 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
public static final String ARCHIVE = "archive";
|
||||
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 View emptyState;
|
||||
private TextView emptySearch;
|
||||
private final String queryFilter = "";
|
||||
private boolean archive;
|
||||
private Timer reloadTimer;
|
||||
private boolean chatlistJustLoaded;
|
||||
private boolean reloadTimerInstantly;
|
||||
private boolean resetScrollPosition;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
@@ -241,9 +242,9 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
|
||||
public void loadChatlistAsync() {
|
||||
synchronized (loadChatlistLock) {
|
||||
needsAnotherLoad = true;
|
||||
if (inLoadChatlist) {
|
||||
Log.i(TAG, "chatlist loading debounced");
|
||||
needsAnotherLoad = true;
|
||||
return;
|
||||
}
|
||||
inLoadChatlist = true;
|
||||
@@ -252,9 +253,6 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
Util.runOnAnyBackgroundThread(
|
||||
() -> {
|
||||
while (true) {
|
||||
Log.i(TAG, "executing debounced chatlist loading");
|
||||
loadChatlist();
|
||||
|
||||
synchronized (loadChatlistLock) {
|
||||
if (!needsAnotherLoad) {
|
||||
inLoadChatlist = false;
|
||||
@@ -263,6 +261,8 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
needsAnotherLoad = false;
|
||||
}
|
||||
|
||||
Log.i(TAG, "executing debounced chatlist loading");
|
||||
loadChatlist();
|
||||
Util.sleep(100);
|
||||
}
|
||||
});
|
||||
@@ -285,17 +285,22 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
Log.w(TAG, "Ignoring call to loadChatlist()");
|
||||
return;
|
||||
}
|
||||
long startMs = System.currentTimeMillis();
|
||||
DcChatlist chatlist = DcHelper.getContext(context).getChatlist(listflags, null, 0);
|
||||
Log.i(TAG, "⏰ getChatlist(): " + (System.currentTimeMillis() - startMs) + "ms");
|
||||
DcChatlist chatlist =
|
||||
DcHelper.getContext(context)
|
||||
.getChatlist(listflags, queryFilter.isEmpty() ? null : queryFilter, 0);
|
||||
|
||||
Util.runOnMain(
|
||||
() -> {
|
||||
if (chatlist.getCnt() <= 0) {
|
||||
if (chatlist.getCnt() <= 0 && TextUtils.isEmpty(queryFilter)) {
|
||||
list.setVisibility(View.INVISIBLE);
|
||||
emptyState.setVisibility(View.VISIBLE);
|
||||
emptySearch.setVisibility(View.INVISIBLE);
|
||||
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 {
|
||||
list.setVisibility(View.VISIBLE);
|
||||
emptyState.setVisibility(View.GONE);
|
||||
@@ -304,11 +309,6 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
}
|
||||
|
||||
((ConversationListAdapter) list.getAdapter()).changeData(chatlist);
|
||||
|
||||
if (resetScrollPosition) {
|
||||
list.scrollToPosition(0);
|
||||
resetScrollPosition = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -365,8 +365,4 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
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.FromTextView;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.search.QrInviteData;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@@ -224,40 +222,6 @@ public class ConversationListItem extends RelativeLayout
|
||||
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
|
||||
public void unbind() {}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
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;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class EphemeralMessagesDialog {
|
||||
|
||||
private static final String TAG = "EphemeralMessagesDialog";
|
||||
private static final String TAG = EphemeralMessagesDialog.class.getSimpleName();
|
||||
|
||||
public static void show(
|
||||
final Context context,
|
||||
|
||||
@@ -46,7 +46,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
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 CREATE_BROADCAST = "create_broadcast";
|
||||
public static final String UNENCRYPTED = "unencrypted";
|
||||
@@ -307,7 +307,8 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private boolean showGroupNameEmptyToast(String groupName) {
|
||||
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 false;
|
||||
|
||||
@@ -65,7 +65,7 @@ import org.thoughtcrime.securesms.util.views.ProgressDialog;
|
||||
public class InstantOnboardingActivity extends BaseActionBarActivity
|
||||
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 DCLOGIN = "dclogin";
|
||||
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 {
|
||||
|
||||
private static final String TAG = "LogViewActivity";
|
||||
private static final String TAG = LogViewActivity.class.getSimpleName();
|
||||
|
||||
LogViewFragment logViewFragment;
|
||||
|
||||
|
||||
@@ -33,13 +33,14 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcMediaGalleryElement;
|
||||
@@ -47,6 +48,7 @@ import com.b44t.messenger.DcMsg;
|
||||
import java.io.IOException;
|
||||
import java.util.WeakHashMap;
|
||||
import org.thoughtcrime.securesms.components.MediaView;
|
||||
import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
|
||||
@@ -67,7 +69,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
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 EDIT_AVATAR_CHAT_ID = "avatar_for_chat_id";
|
||||
@@ -86,7 +88,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
@Nullable private DcMsg messageRecord;
|
||||
private DcContext dcContext;
|
||||
private MediaItem initialMedia;
|
||||
private ViewPager2 mediaPager;
|
||||
private ViewPager mediaPager;
|
||||
private Recipient conversationRecipient;
|
||||
private boolean leftIsRecent;
|
||||
|
||||
@@ -188,7 +190,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
private void initializeViews() {
|
||||
mediaPager = findViewById(R.id.media_pager);
|
||||
mediaPager.setOffscreenPageLimit(1);
|
||||
mediaPager.registerOnPageChangeCallback(new ViewPagerListener());
|
||||
mediaPager.addOnPageChangeListener(new ViewPagerListener());
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
@@ -258,7 +260,10 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private int cleanupMedia() {
|
||||
int restartItem = mediaPager.getCurrentItem();
|
||||
|
||||
mediaPager.removeAllViews();
|
||||
mediaPager.setAdapter(null);
|
||||
|
||||
return restartItem;
|
||||
}
|
||||
|
||||
@@ -478,25 +483,22 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
DcMediaPagerAdapter adapter =
|
||||
new DcMediaPagerAdapter(this, GlideApp.with(this), getWindow(), data, leftIsRecent);
|
||||
adapter.setActive(true);
|
||||
mediaPager.setAdapter(adapter);
|
||||
adapter.setActive(true);
|
||||
|
||||
if (restartItem < 0) mediaPager.setCurrentItem(data.getPosition(), false);
|
||||
else mediaPager.setCurrentItem(restartItem, false);
|
||||
if (restartItem < 0) mediaPager.setCurrentItem(data.getPosition());
|
||||
else mediaPager.setCurrentItem(restartItem);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<DcMediaGalleryElement> loader) {}
|
||||
|
||||
private class ViewPagerListener extends ViewPager2.OnPageChangeCallback {
|
||||
|
||||
private Integer currentPage = null;
|
||||
private class ViewPagerListener extends ExtendedOnPageChangedListener {
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
if (currentPage != null && currentPage != position) onPageUnselected(currentPage);
|
||||
currentPage = position;
|
||||
super.onPageSelected(position);
|
||||
|
||||
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();
|
||||
|
||||
if (adapter != null) {
|
||||
@@ -523,9 +526,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
private static class SingleItemPagerAdapter
|
||||
extends RecyclerView.Adapter<SingleItemPagerAdapter.MediaViewHolder>
|
||||
implements MediaItemAdapter {
|
||||
private static class SingleItemPagerAdapter extends PagerAdapter implements MediaItemAdapter {
|
||||
|
||||
private final GlideRequests glideRequests;
|
||||
private final Window window;
|
||||
@@ -554,29 +555,37 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
public int getCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public MediaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new MediaViewHolder(inflater.inflate(R.layout.media_view_page, parent, false));
|
||||
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
||||
return view == object;
|
||||
}
|
||||
|
||||
@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 {
|
||||
holder.mediaView.set(glideRequests, window, uri, name, mediaType, size, true);
|
||||
mediaView.set(glideRequests, window, uri, name, mediaType, size, true);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
container.addView(itemView);
|
||||
|
||||
return itemView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRecycled(@NonNull MediaViewHolder holder) {
|
||||
super.onViewRecycled(holder);
|
||||
holder.mediaView.cleanup();
|
||||
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
MediaView mediaView = ((FrameLayout) object).findViewById(R.id.media_view);
|
||||
mediaView.cleanup();
|
||||
|
||||
container.removeView((FrameLayout) object);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -586,20 +595,9 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
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
|
||||
extends RecyclerView.Adapter<DcMediaPagerAdapter.MediaViewHolder>
|
||||
implements MediaItemAdapter {
|
||||
private static class DcMediaPagerAdapter extends PagerAdapter implements MediaItemAdapter {
|
||||
|
||||
private final WeakHashMap<Integer, MediaView> mediaViews = new WeakHashMap<>();
|
||||
|
||||
@@ -632,20 +630,21 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
public int getCount() {
|
||||
if (!active) return 0;
|
||||
else return gallery.getCount();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public MediaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View itemView = LayoutInflater.from(context).inflate(R.layout.media_view_page, parent, false);
|
||||
return new MediaViewHolder(itemView);
|
||||
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
||||
return view == object;
|
||||
}
|
||||
|
||||
@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;
|
||||
int cursorPosition = getCursorPosition(position);
|
||||
|
||||
@@ -657,7 +656,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
holder.mediaView.set(
|
||||
mediaView.set(
|
||||
glideRequests,
|
||||
window,
|
||||
Uri.fromFile(msg.getFileAsFile()),
|
||||
@@ -669,17 +668,19 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
mediaViews.put(position, holder.mediaView);
|
||||
mediaViews.put(position, mediaView);
|
||||
container.addView(itemView);
|
||||
|
||||
return itemView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRecycled(@NonNull MediaViewHolder holder) {
|
||||
super.onViewRecycled(holder);
|
||||
int pos = holder.getBindingAdapterPosition();
|
||||
if (pos != RecyclerView.NO_POSITION) {
|
||||
mediaViews.remove(pos);
|
||||
}
|
||||
holder.mediaView.cleanup();
|
||||
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
MediaView mediaView = ((FrameLayout) object).findViewById(R.id.media_view);
|
||||
mediaView.cleanup();
|
||||
|
||||
mediaViews.remove(position);
|
||||
container.removeView((FrameLayout) object);
|
||||
}
|
||||
|
||||
public MediaItem getMediaItemFor(int position) {
|
||||
@@ -709,15 +710,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
if (leftIsRecent) return 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 {
|
||||
@@ -750,7 +742,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
private interface MediaItemAdapter {
|
||||
interface MediaItemAdapter {
|
||||
MediaItem getMediaItemFor(int position);
|
||||
|
||||
void pause(int position);
|
||||
|
||||
@@ -41,7 +41,7 @@ import org.thoughtcrime.securesms.qr.QrCodeHandler;
|
||||
*/
|
||||
public class NewConversationActivity extends ContactSelectionActivity {
|
||||
|
||||
private static final String TAG = "NewConversationActivity";
|
||||
private static final String TAG = NewConversationActivity.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle, boolean ready) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
||||
|
||||
public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarActivity {
|
||||
private static final String TAG = "PassphraseRequiredActionBarActivity";
|
||||
private static final String TAG = PassphraseRequiredActionBarActivity.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
protected final void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -26,11 +25,8 @@ import com.b44t.messenger.DcContact;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.Prefs;
|
||||
import org.thoughtcrime.securesms.util.ShareUtil;
|
||||
@@ -40,8 +36,6 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||
implements DcEventCenter.DcEventDelegate {
|
||||
|
||||
private static final String TAG = "ProfileActivity";
|
||||
|
||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||
public static final String CONTACT_ID_EXTRA = "contact_id";
|
||||
|
||||
@@ -404,18 +398,7 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||
Intent composeIntent = new Intent();
|
||||
DcContact dcContact = dcContext.getContact(contactId);
|
||||
if (dcContact.isKeyContact()) {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
ShareUtil.setSharedContactId(composeIntent, contactId);
|
||||
} else {
|
||||
ShareUtil.setSharedText(composeIntent, dcContact.getAddr());
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
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_DIVIDER = 20;
|
||||
|
||||
@@ -38,7 +38,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
public class ProfileFragment extends Fragment
|
||||
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 CONTACT_ID_EXTRA = "contact_id";
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
|
||||
public class ResolveMediaTask extends AsyncTask<Uri, Void, Uri> {
|
||||
|
||||
private static final String TAG = "ResolveMediaTask";
|
||||
private static final String TAG = ResolveMediaTask.class.getSimpleName();
|
||||
|
||||
interface OnMediaResolvedListener {
|
||||
void onMediaResolved(Uri uri);
|
||||
|
||||
@@ -47,7 +47,7 @@ import org.thoughtcrime.securesms.util.ShareUtil;
|
||||
*/
|
||||
public class ShareActivity extends PassphraseRequiredActionBarActivity
|
||||
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_CHAT_ID = "chat_id";
|
||||
@@ -260,11 +260,12 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
|
||||
final String addr = extraEmail[0];
|
||||
int contactId = dcContext.lookupContactIdByAddr(addr);
|
||||
|
||||
if (contactId != 0 || dcContext.getConfigInt(DcHelper.CONFIG_FORCE_ENCRYPTION) == 0) {
|
||||
if (contactId == 0) contactId = dcContext.createContact(null, addr);
|
||||
chatId = dcContext.createChatByContactId(contactId);
|
||||
accId = dcContext.getAccountId();
|
||||
if (contactId == 0) {
|
||||
contactId = dcContext.createContact(null, addr);
|
||||
}
|
||||
|
||||
chatId = dcContext.createChatByContactId(contactId);
|
||||
accId = dcContext.getAccountId();
|
||||
}
|
||||
Intent composeIntent;
|
||||
if (accId != -1 && chatId > 0) {
|
||||
|
||||
@@ -18,18 +18,21 @@ public class ShareLocationDialog {
|
||||
switch (which) {
|
||||
default:
|
||||
case 0:
|
||||
shareLocationUnit = 30 * 60;
|
||||
shareLocationUnit = 5 * 60;
|
||||
break;
|
||||
case 1:
|
||||
shareLocationUnit = 60 * 60;
|
||||
shareLocationUnit = 30 * 60;
|
||||
break;
|
||||
case 2:
|
||||
shareLocationUnit = 2 * 60 * 60;
|
||||
shareLocationUnit = 60 * 60;
|
||||
break;
|
||||
case 3:
|
||||
shareLocationUnit = 6 * 60 * 60;
|
||||
shareLocationUnit = 2 * 60 * 60;
|
||||
break;
|
||||
case 4:
|
||||
shareLocationUnit = 6 * 60 * 60;
|
||||
break;
|
||||
case 5:
|
||||
shareLocationUnit = 24 * 60 * 60;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
||||
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.
|
||||
private static final Pattern URL_PATTERN =
|
||||
Pattern.compile("^((?:[a-zA-Z0-9]+://)?)([^/?#]+)(.*)$");
|
||||
|
||||
@@ -34,7 +34,6 @@ import androidx.core.content.pm.ShortcutManagerCompat;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
import chat.delta.rpc.types.WebxdcMessageInfo;
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
@@ -51,6 +50,7 @@ import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import org.json.JSONObject;
|
||||
import org.thoughtcrime.securesms.connect.AccountManager;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.util.IntentUtils;
|
||||
@@ -59,7 +59,7 @@ import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
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_APP_MSG_ID = "appMessageId";
|
||||
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 DcContext dcContext;
|
||||
private int accountId;
|
||||
private Rpc rpc;
|
||||
private DcMsg dcAppMsg;
|
||||
private String baseURL;
|
||||
private String sourceCodeUrl = "";
|
||||
private String selfAddr;
|
||||
private boolean isAppSender;
|
||||
private boolean isBroadcast;
|
||||
private int sendUpdateMaxSize;
|
||||
private int sendUpdateInterval;
|
||||
private boolean internetAccess = false;
|
||||
@@ -133,16 +130,12 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
private static Intent getWebxdcIntent(
|
||||
Context context, int msgId, boolean hideActionBar, String href) {
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
int accountId = dcContext.getAccountId();
|
||||
Intent intent = new Intent(context, WebxdcActivity.class);
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
// Unique URI per webxdc instance so FLAG_ACTIVITY_NEW_DOCUMENT can identify the document:
|
||||
intent.setData(Uri.parse("webxdc://" + accountId + "/" + msgId));
|
||||
intent.putExtra(EXTRA_ACCOUNT_ID, accountId);
|
||||
intent.putExtra(EXTRA_ACCOUNT_ID, dcContext.getAccountId());
|
||||
intent.putExtra(EXTRA_APP_MSG_ID, msgId);
|
||||
intent.putExtra(EXTRA_HIDE_ACTION_BAR, hideActionBar);
|
||||
intent.putExtra(EXTRA_HREF, href);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@@ -202,15 +195,16 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
|
||||
DcEventCenter eventCenter =
|
||||
DcHelper.getEventCenter(WebxdcActivity.this.getApplicationContext());
|
||||
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE, this);
|
||||
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_MSGS_CHANGED, this);
|
||||
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_WEBXDC_REALTIME_DATA, this);
|
||||
eventCenter.addObserver(DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE, this);
|
||||
eventCenter.addObserver(DcContext.DC_EVENT_MSGS_CHANGED, this);
|
||||
eventCenter.addObserver(DcContext.DC_EVENT_WEBXDC_REALTIME_DATA, this);
|
||||
|
||||
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());
|
||||
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);
|
||||
@@ -226,32 +220,21 @@ 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
|
||||
// regard the file-part interceptRequest() only,
|
||||
// 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;
|
||||
try {
|
||||
info = rpc.getWebxdcInfo(accountId, appMessageId);
|
||||
|
||||
if ("landscape".equals(info.orientation)) {
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
} else {
|
||||
// enter fullscreen mode if necessary,
|
||||
// this is needed here because if the app is opened while already in landscape mode,
|
||||
// onConfigurationChanged() is not triggered
|
||||
setScreenMode(getResources().getConfiguration());
|
||||
}
|
||||
|
||||
internetAccess = info.internetAccess;
|
||||
selfAddr = info.selfAddr;
|
||||
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;
|
||||
final JSONObject info = this.dcAppMsg.getWebxdcInfo();
|
||||
internetAccess = JsonUtils.optBoolean(info, "internet_access");
|
||||
if ("landscape".equals(JsonUtils.optString(info, "orientation"))) {
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
} else {
|
||||
// enter fullscreen mode if necessary,
|
||||
// this is needed here because if the app is opened while already in landscape mode,
|
||||
// onConfigurationChanged() is not triggered
|
||||
setScreenMode(getResources().getConfiguration());
|
||||
}
|
||||
selfAddr = info.optString("self_addr");
|
||||
sendUpdateMaxSize = info.optInt("send_update_max_size");
|
||||
sendUpdateInterval = info.optInt("send_update_interval");
|
||||
|
||||
toggleFakeProxy(!internetAccess);
|
||||
|
||||
@@ -332,7 +315,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
super.onOptionsItemSelected(item);
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == R.id.menu_add_to_home_screen) {
|
||||
addToHomeScreen(this, dcContext, dcAppMsg.getId());
|
||||
addToHomeScreen(this, dcAppMsg.getId());
|
||||
return true;
|
||||
} else if (itemId == R.id.webxdc_help) {
|
||||
DcHelper.openHelp(this, "#webxdc");
|
||||
@@ -485,8 +468,6 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
|
||||
@Override
|
||||
public void handleEvent(@NonNull DcEvent event) {
|
||||
if (event.getAccountId() != accountId) return;
|
||||
|
||||
int eventId = event.getId();
|
||||
if ((eventId == DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE
|
||||
&& 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
|
||||
Util.runOnAnyBackgroundThread(
|
||||
() -> {
|
||||
try {
|
||||
final WebxdcMessageInfo info =
|
||||
rpc.getWebxdcInfo(dcContext.getAccountId(), dcAppMsg.getId());
|
||||
final DcChat chat = dcContext.getChat(dcAppMsg.getChatId());
|
||||
Util.runOnMain(() -> updateTitleAndMenu(info, chat));
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "RPC Error", e);
|
||||
}
|
||||
final JSONObject info = dcAppMsg.getWebxdcInfo();
|
||||
final DcChat chat = dcContext.getChat(dcAppMsg.getChatId());
|
||||
Util.runOnMain(
|
||||
() -> {
|
||||
updateTitleAndMenu(info, chat);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTitleAndMenu(WebxdcMessageInfo info, DcChat chat) {
|
||||
final String docName = TextUtils.isEmpty(info.document) ? info.name : info.document;
|
||||
getSupportActionBar().setTitle(docName + " – " + chat.getName());
|
||||
String currSourceCodeUrl = info.sourceCodeUrl != null ? info.sourceCodeUrl : "";
|
||||
private void updateTitleAndMenu(JSONObject info, DcChat chat) {
|
||||
final String docName = JsonUtils.optString(info, "document");
|
||||
final String xdcName = JsonUtils.optString(info, "name");
|
||||
final String currSourceCodeUrl = JsonUtils.optString(info, "source_code_url");
|
||||
getSupportActionBar()
|
||||
.setTitle((docName.isEmpty() ? xdcName : docName) + " – " + chat.getName());
|
||||
if (!sourceCodeUrl.equals(currSourceCodeUrl)) {
|
||||
sourceCodeUrl = currSourceCodeUrl;
|
||||
invalidateOptionsMenu();
|
||||
@@ -530,7 +511,6 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
|
||||
private void showInChat() {
|
||||
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.STARTING_POSITION_EXTRA,
|
||||
@@ -539,53 +519,35 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
}
|
||||
|
||||
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();
|
||||
try {
|
||||
Rpc rpc = DcHelper.getRpc(context);
|
||||
int accountId = dcContext.getAccountId();
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
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;
|
||||
byte[] blob = msg.getWebxdcBlob(info.icon);
|
||||
final String docName = JsonUtils.optString(info, "document");
|
||||
final String xdcName = JsonUtils.optString(info, "name");
|
||||
byte[] blob = msg.getWebxdcBlob(JsonUtils.optString(info, "icon"));
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(blob);
|
||||
BitmapDrawable drawable = (BitmapDrawable) Drawable.createFromStream(is, "icon");
|
||||
Bitmap bitmap = drawable.getBitmap();
|
||||
|
||||
ShortcutInfoCompat shortcutInfoCompat =
|
||||
new ShortcutInfoCompat.Builder(context, "xdc-" + accountId + "-" + msgId)
|
||||
.setShortLabel(docName)
|
||||
new ShortcutInfoCompat.Builder(context, "xdc-" + dcContext.getAccountId() + "-" + msgId)
|
||||
.setShortLabel(docName.isEmpty() ? xdcName : docName)
|
||||
.setIcon(
|
||||
IconCompat.createWithBitmap(
|
||||
bitmap)) // createWithAdaptiveBitmap() removes decorations but cuts out a too
|
||||
// small circle and defamiliarize the icon too much
|
||||
.setIntent(getWebxdcIntent(context, msgId, false, ""))
|
||||
.setIntents(getWebxdcIntentWithParentStack(context, msgId))
|
||||
.build();
|
||||
|
||||
Toast.makeText(context, R.string.one_moment, Toast.LENGTH_SHORT).show();
|
||||
Util.runOnAnyBackgroundThread(
|
||||
() -> {
|
||||
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(
|
||||
context,
|
||||
"ErrAddToHomescreen: requestPinShortcut() failed",
|
||||
Toast.LENGTH_LONG)
|
||||
.show());
|
||||
}
|
||||
});
|
||||
if (!ShortcutManagerCompat.requestPinShortcut(context, shortcutInfoCompat, null)) {
|
||||
Toast.makeText(
|
||||
context, "ErrAddToHomescreen: requestPinShortcut() failed", Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
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();
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public boolean isAppSender() {
|
||||
return WebxdcActivity.this.isAppSender;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public boolean isBroadcast() {
|
||||
return WebxdcActivity.this.isBroadcast;
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection unused
|
||||
*/
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class WebxdcStoreActivity extends PassphraseRequiredActionBarActivity {
|
||||
private static final String TAG = "WebxdcStoreActivity";
|
||||
private static final String TAG = WebxdcStoreActivity.class.getSimpleName();
|
||||
|
||||
private DcContext dcContext;
|
||||
private Rpc rpc;
|
||||
|
||||
@@ -48,7 +48,7 @@ public class WelcomeActivity extends BaseActionBarActivity
|
||||
implements DcEventCenter.DcEventDelegate {
|
||||
public static final String BACKUP_QR_EXTRA = "backup_qr_extra";
|
||||
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";
|
||||
|
||||
private ProgressDialog progressDialog = null;
|
||||
|
||||
@@ -41,7 +41,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class AccountSelectionListFragment extends DialogFragment
|
||||
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 RecyclerView recyclerView;
|
||||
private AccountSelectionListAdapter adapter;
|
||||
@@ -157,12 +157,6 @@ public class AccountSelectionListFragment extends DialogFragment
|
||||
onSetTag(accountId);
|
||||
} else if (itemId == R.id.menu_move_to_top) {
|
||||
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 androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public abstract class Attachment {
|
||||
|
||||
@@ -111,7 +116,14 @@ public abstract class Attachment {
|
||||
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) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.util.Prefs;
|
||||
|
||||
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 BIT_RATE_BALANCED = 32000;
|
||||
@@ -196,7 +196,7 @@ public class AudioCodec {
|
||||
setFinished();
|
||||
}
|
||||
},
|
||||
"AudioCodec")
|
||||
AudioCodec.class.getSimpleName())
|
||||
.start();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class AudioRecorder {
|
||||
|
||||
private static final String TAG = "AudioRecorder";
|
||||
private static final String TAG = AudioRecorder.class.getSimpleName();
|
||||
|
||||
private static final ExecutorService executor = ThreadUtil.newDynamicSingleThreadedExecutor();
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.R;
|
||||
/** Bottom sheet dialog for selecting audio output device */
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
public class AudioDevicePickerDialog extends BottomSheetDialog {
|
||||
private static final String TAG = "AudioDevicePickerDialog";
|
||||
private static final String TAG = AudioDevicePickerDialog.class.getSimpleName();
|
||||
|
||||
public interface OnDeviceSelectedListener {
|
||||
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.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.util.Rational;
|
||||
import android.view.View;
|
||||
@@ -24,9 +22,7 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
@@ -55,10 +51,9 @@ import org.webrtc.VideoTrack;
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
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 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_DECLINE_CALL = BuildConfig.APPLICATION_ID + ".DECLINE_CALL";
|
||||
@@ -104,7 +99,6 @@ public class CallActivity extends AppCompatActivity {
|
||||
private boolean awaitingPermissionResult = false;
|
||||
private boolean pausedWhileAwaitingPermission = false;
|
||||
private boolean intentHandled = false;
|
||||
private boolean doNotAutoFinish = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -233,7 +227,6 @@ public class CallActivity extends AppCompatActivity {
|
||||
Log.d(TAG, "Resuming existing call");
|
||||
} else if (!coordinator.isIncomingCall()) {
|
||||
Log.d(TAG, "Starting outgoing call");
|
||||
coordinator.ensureServiceStarted();
|
||||
viewModel.startOutgoingCallWhenReady();
|
||||
}
|
||||
}
|
||||
@@ -381,18 +374,6 @@ public class CallActivity extends AppCompatActivity {
|
||||
videoButton.setOnClickListener(
|
||||
v -> {
|
||||
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();
|
||||
}
|
||||
});
|
||||
@@ -653,7 +634,7 @@ public class CallActivity extends AppCompatActivity {
|
||||
new Handler(Looper.getMainLooper())
|
||||
.postDelayed(
|
||||
() -> {
|
||||
if (!isFinishing() && !doNotAutoFinish) {
|
||||
if (!isFinishing()) {
|
||||
finish();
|
||||
}
|
||||
},
|
||||
@@ -662,9 +643,7 @@ public class CallActivity extends AppCompatActivity {
|
||||
|
||||
case ENDED:
|
||||
statusText.setText(R.string.call_ended);
|
||||
if (!doNotAutoFinish) {
|
||||
finish();
|
||||
}
|
||||
finish();
|
||||
break;
|
||||
|
||||
case ERROR:
|
||||
@@ -677,7 +656,7 @@ public class CallActivity extends AppCompatActivity {
|
||||
new Handler(Looper.getMainLooper())
|
||||
.postDelayed(
|
||||
() -> {
|
||||
if (!isFinishing() && !doNotAutoFinish) {
|
||||
if (!isFinishing()) {
|
||||
finish();
|
||||
}
|
||||
},
|
||||
@@ -863,28 +842,16 @@ public class CallActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void handleMicPermissionDenied() {
|
||||
Toast.makeText(this, R.string.call_requires_mic_permission, Toast.LENGTH_LONG).show();
|
||||
|
||||
CallCoordinator coordinator = CallCoordinator.getInstance(getApplication());
|
||||
|
||||
if (coordinator.hasActiveCall() && !coordinator.hasOngoingCall()) {
|
||||
if (coordinator.isIncomingCall()) {
|
||||
coordinator.declineCall();
|
||||
} else {
|
||||
coordinator.hangUp();
|
||||
}
|
||||
if (coordinator.hasActiveCall()
|
||||
&& coordinator.isIncomingCall()
|
||||
&& !coordinator.hasOngoingCall()) {
|
||||
coordinator.declineCall();
|
||||
}
|
||||
|
||||
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();
|
||||
finish();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
private void proceedAfterPermissions() {
|
||||
@@ -911,22 +878,6 @@ public class CallActivity extends AppCompatActivity {
|
||||
|
||||
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) {
|
||||
boolean micGranted =
|
||||
grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
||||
@@ -942,17 +893,9 @@ public class CallActivity extends AppCompatActivity {
|
||||
|
||||
if (!cameraGranted) {
|
||||
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(
|
||||
this, "Starting audio-only call (camera permission denied)", Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
Toast.makeText(
|
||||
this, "Starting audio-only call (camera permission denied)", Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
coordinator.setStartsWithVideo(false);
|
||||
}
|
||||
}
|
||||
@@ -960,34 +903,6 @@ public class CallActivity extends AppCompatActivity {
|
||||
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
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.os.Looper;
|
||||
import android.telecom.DisconnectCause;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
@@ -33,7 +32,6 @@ import androidx.core.telecom.CallControlScope;
|
||||
import androidx.core.telecom.CallEndpointCompat;
|
||||
import androidx.core.telecom.CallException;
|
||||
import androidx.core.telecom.CallsManager;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.lifecycle.FlowLiveDataConversions;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MediatorLiveData;
|
||||
@@ -65,7 +63,7 @@ import org.webrtc.VideoTrack;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
private static final String TAG = "CallCoordinator";
|
||||
private static final String TAG = CallCoordinator.class.getSimpleName();
|
||||
|
||||
// Notification channels
|
||||
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> answeredElsewhere = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<Boolean> isFrontCamera = new MutableLiveData<>(true);
|
||||
private final MutableLiveData<Boolean> mediaCaptureReady = new MutableLiveData<>(false);
|
||||
|
||||
// Audio Routing Support
|
||||
private final MediatorLiveData<CallEndpointCompat> currentAudioEndpoint =
|
||||
@@ -122,7 +119,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
private String pendingOfferSdp;
|
||||
private boolean hasNotifiedBackend = false;
|
||||
private boolean hasAutoSelectedEarpiece = false;
|
||||
private boolean pendingMediaCapture = false;
|
||||
|
||||
private CallControlScope activeCallControlScope;
|
||||
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() {
|
||||
DcEventCenter eventCenter = DcHelper.getEventCenter(this.appContext);
|
||||
eventCenter.removeObservers(this);
|
||||
@@ -242,8 +215,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
calleeName = "Unknown";
|
||||
}
|
||||
|
||||
showOrUpdateOngoingNotification(
|
||||
appContext.getString(R.string.calling_person, calleeName));
|
||||
showOrUpdateOngoingNotification("Calling " + calleeName + "...");
|
||||
}
|
||||
|
||||
// Initialize call
|
||||
@@ -255,11 +227,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
mainHandler.removeCallbacks(outgoingRingtoneRunnable);
|
||||
mainHandler.postDelayed(outgoingRingtoneRunnable, 1500);
|
||||
}
|
||||
|
||||
if (pendingMediaCapture) {
|
||||
pendingMediaCapture = false;
|
||||
callService.startMediaCapture();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,10 +335,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
return isFrontCamera;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getMediaCaptureReady() {
|
||||
return mediaCaptureReady;
|
||||
}
|
||||
|
||||
// State Update Methods (CallService)
|
||||
|
||||
public void updateConnectionState(PeerConnection.PeerConnectionState state) {
|
||||
@@ -411,11 +374,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
isFrontCamera.postValue(front);
|
||||
}
|
||||
|
||||
public void updateMediaCaptureReady(boolean ready) {
|
||||
Log.d(TAG, "updateMediaCaptureReady: " + ready);
|
||||
mediaCaptureReady.postValue(ready);
|
||||
}
|
||||
|
||||
public void reportError(String error) {
|
||||
Log.e(TAG, "reportError: " + error);
|
||||
errorMessage.postValue(error);
|
||||
@@ -428,8 +386,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
if (callService != null) {
|
||||
callService.startMediaCapture();
|
||||
} else {
|
||||
Log.d(TAG, "Service not ready, deferring media capture");
|
||||
pendingMediaCapture = true;
|
||||
Log.w(TAG, "Cannot start media capture, service not ready");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,15 +402,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
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
|
||||
CallControlScope scope = activeCallControlScope;
|
||||
if (scope != null) {
|
||||
@@ -477,6 +425,8 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
notificationManager.cancel(NOTIFICATION_ID_CALL);
|
||||
}
|
||||
|
||||
public void answerWebRTC() {
|
||||
@@ -604,7 +554,28 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
|
||||
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
|
||||
if (callService != null) {
|
||||
@@ -625,7 +596,28 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
|
||||
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
|
||||
if (callService != null) {
|
||||
@@ -651,17 +643,11 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
public synchronized void setVideoEnabled(boolean 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);
|
||||
|
||||
if (callService != null) {
|
||||
callService.setVideoEnabled(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) {
|
||||
Log.d(TAG, "onIncomingCall: accId=" + accId + ", callId=" + callId);
|
||||
|
||||
Pair<DcChat, String> result = setupIncomingCallState(accId, callId, offerSdp, startsWithVideo);
|
||||
if (result == null) return;
|
||||
if (hasActiveCall()) {
|
||||
Log.w(TAG, "Already have an active call, ignoring incoming call");
|
||||
return;
|
||||
}
|
||||
|
||||
DcChat dcChat = result.first;
|
||||
String callerName = result.second;
|
||||
resetLiveDataForNewCall();
|
||||
|
||||
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);
|
||||
|
||||
displayName.postValue(callerName);
|
||||
displayIcon.postValue(callerIcon);
|
||||
|
||||
this.preferredStartingEndpoint = getPreferredStartingEndpoint(startsWithVideo);
|
||||
|
||||
// Add to CallsManager
|
||||
CallAttributesCompat callAttributes = createCallAttributes(callerName, callId, true);
|
||||
addCallToTelecom(callAttributes, callerName, callerIcon);
|
||||
@@ -900,37 +903,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
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) {
|
||||
Log.d(TAG, "onIncomingCallAccepted: callId=" + callId + ", fromThisDevice=" + fromThisDevice);
|
||||
|
||||
@@ -949,7 +921,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
callerName = "Unknown";
|
||||
}
|
||||
|
||||
showOrUpdateOngoingNotification(appContext.getString(R.string.call_with, callerName));
|
||||
showOrUpdateOngoingNotification("Call with " + callerName);
|
||||
}
|
||||
|
||||
private synchronized void onCallAnsweredOnOtherDevice() {
|
||||
@@ -972,7 +944,24 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
|
||||
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) {
|
||||
callService.endCall();
|
||||
@@ -1019,7 +1008,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
if (calleeName == null) {
|
||||
calleeName = "Unknown";
|
||||
}
|
||||
showOrUpdateOngoingNotification(appContext.getString(R.string.call_with, calleeName));
|
||||
showOrUpdateOngoingNotification("Call with " + calleeName);
|
||||
}
|
||||
|
||||
private synchronized void onCallEnded(int accId, int callId) {
|
||||
@@ -1050,7 +1039,25 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
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) {
|
||||
callService.endCall();
|
||||
@@ -1074,18 +1081,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
|
||||
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
|
||||
if (hasActiveCall()) {
|
||||
cleanupCall(activeAccId, activeCallId);
|
||||
@@ -1106,14 +1101,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
return;
|
||||
}
|
||||
|
||||
if (callService != null) {
|
||||
try {
|
||||
callService.stopForegroundAndDismiss();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "stopForegroundAndDismiss failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear state
|
||||
this.activeAccId = null;
|
||||
this.activeCallId = null;
|
||||
@@ -1126,7 +1113,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
this.pendingOfferSdp = null;
|
||||
this.hasNotifiedBackend = false;
|
||||
this.hasAutoSelectedEarpiece = false;
|
||||
this.pendingMediaCapture = false;
|
||||
|
||||
mainHandler.removeCallbacks(outgoingRingtoneRunnable);
|
||||
|
||||
@@ -1192,7 +1178,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
private void resetLiveDataForNewCall() {
|
||||
connectionState.postValue(PeerConnection.PeerConnectionState.NEW);
|
||||
answeredElsewhere.postValue(false); // clearLiveData() must not reset answeredElsewhere
|
||||
mediaCaptureReady.postValue(false);
|
||||
clearLiveData();
|
||||
}
|
||||
|
||||
@@ -1208,7 +1193,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
outgoingCallPlaced.postValue(false);
|
||||
currentAudioEndpoint.postValue(null);
|
||||
availableAudioEndpoints.postValue(null);
|
||||
mediaCaptureReady.postValue(false);
|
||||
}
|
||||
|
||||
public synchronized void initiateOutgoingCall(int accId, int chatId, boolean startsWithVideo) {
|
||||
@@ -1219,6 +1203,16 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
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();
|
||||
|
||||
this.activeCallId = -1; // Placeholder call ID for Intent
|
||||
@@ -1244,9 +1238,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
|
||||
this.preferredStartingEndpoint = getPreferredStartingEndpoint(startsWithVideo);
|
||||
|
||||
if (hasMicrophonePermission()) {
|
||||
startAndBindService();
|
||||
}
|
||||
startAndBindService();
|
||||
|
||||
launchCallActivity();
|
||||
}
|
||||
@@ -1278,43 +1270,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
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(
|
||||
CallAttributesCompat callAttributes, String displayName, Icon icon) {
|
||||
try {
|
||||
@@ -1422,11 +1377,12 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
// 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.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
PendingIntent declinePendingIntent =
|
||||
PendingIntent.getBroadcast(
|
||||
PendingIntent.getActivity(
|
||||
this.appContext,
|
||||
1,
|
||||
declineIntent,
|
||||
@@ -1495,11 +1451,12 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
Intent activityIntent = new Intent(this.appContext, CallActivity.class);
|
||||
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.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
PendingIntent hangupPendingIntent =
|
||||
PendingIntent.getBroadcast(
|
||||
PendingIntent.getActivity(
|
||||
this.appContext,
|
||||
3,
|
||||
hangupIntent,
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.app.Notification;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioFocusRequest;
|
||||
import android.media.AudioManager;
|
||||
@@ -14,9 +13,7 @@ import android.media.ToneGenerator;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
@@ -36,7 +33,7 @@ import org.webrtc.VideoTrack;
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
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();
|
||||
|
||||
@@ -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
|
||||
* ready.
|
||||
*/
|
||||
public void startMediaCapture() {
|
||||
Log.d(TAG, "startMediaCapture");
|
||||
Log.d(TAG, "startMediaCapture (Camera/Microphone)");
|
||||
|
||||
if (webRTCClient != null && webRTCClient.hasLocalMediaStream()) {
|
||||
Log.w(TAG, "Media already initialized, skipping");
|
||||
@@ -153,7 +150,7 @@ public class CallService extends Service implements WebRTCClient.Callbacks {
|
||||
|
||||
boolean startsWithVideo = callCoordinator.isStartsWithVideo();
|
||||
|
||||
Log.d(TAG, "Creating media stream");
|
||||
Log.d(TAG, "Creating media stream with video: " + startsWithVideo);
|
||||
|
||||
mediaStreamManager.createMediaStream(
|
||||
new MediaStreamManager.Callback() {
|
||||
@@ -163,14 +160,20 @@ public class CallService extends Service implements WebRTCClient.Callbacks {
|
||||
|
||||
webRTCClient.setLocalMediaStream(stream);
|
||||
|
||||
if (!stream.videoTracks.isEmpty()) {
|
||||
VideoTrack localTrack = stream.videoTracks.get(0);
|
||||
callCoordinator.updateLocalVideoTrack(localTrack);
|
||||
}
|
||||
callCoordinator.updateFrontCamera(mediaStreamManager.isFrontCamera());
|
||||
|
||||
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");
|
||||
}
|
||||
@@ -178,7 +181,10 @@ public class CallService extends Service implements WebRTCClient.Callbacks {
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
Log.e(TAG, "Failed to setup media: " + error);
|
||||
callCoordinator.reportError("Camera/microphone error: " + error);
|
||||
if (startsWithVideo) {
|
||||
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);
|
||||
|
||||
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) {
|
||||
webRTCClient.setVideoEnabled(true);
|
||||
}
|
||||
} else {
|
||||
if (webRTCClient != null) {
|
||||
webRTCClient.setVideoEnabled(false);
|
||||
}
|
||||
if (mediaStreamManager != null) {
|
||||
mediaStreamManager.stopVideoCapture();
|
||||
}
|
||||
if (webRTCClient != null) {
|
||||
webRTCClient.setVideoEnabled(enabled);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void sendMutedState(boolean audioEnabled, boolean videoEnabled) {
|
||||
@@ -444,12 +432,6 @@ public class CallService extends Service implements WebRTCClient.Callbacks {
|
||||
|
||||
disposeWebRTC();
|
||||
|
||||
try {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "stopForeground failed", e);
|
||||
}
|
||||
|
||||
stopService();
|
||||
}
|
||||
|
||||
@@ -512,44 +494,13 @@ public class CallService extends Service implements WebRTCClient.Callbacks {
|
||||
// Foreground Notification
|
||||
|
||||
public void startForegroundWithNotification(int id, Notification notification) {
|
||||
// Always run on main thread
|
||||
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);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "startForeground failed", e);
|
||||
if (callCoordinator != null) {
|
||||
callCoordinator.reportError("Failed to activate call FGS: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "Starting foreground with notification id: " + id);
|
||||
startForeground(id, notification);
|
||||
}
|
||||
|
||||
public void stopForegroundAndDismiss() {
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
new Handler(Looper.getMainLooper()).post(this::stopForegroundAndDismiss);
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "Stopping call FGS and dismissing notification");
|
||||
try {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "stopForeground failed", e);
|
||||
}
|
||||
Log.d(TAG, "Stopping foreground and dismissing notification");
|
||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
|
||||
@@ -5,18 +5,13 @@ import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.telecom.CallEndpointCompat;
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
@@ -26,7 +21,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
|
||||
public class CallUtil {
|
||||
private static final String TAG = "CallUtil";
|
||||
private static final String TAG = CallUtil.class.getSimpleName();
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void startAudioCall(Context context, int chatId) {
|
||||
@@ -57,22 +52,8 @@ public class CallUtil {
|
||||
return;
|
||||
}
|
||||
|
||||
Runnable proceedWithCall =
|
||||
() -> {
|
||||
int accId = DcHelper.getContext(context).getAccountId();
|
||||
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();
|
||||
int accId = DcHelper.getContext(context).getAccountId();
|
||||
coordinator.initiateOutgoingCall(accId, chatId, startsWithVideo);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -157,24 +138,4 @@ public class CallUtil {
|
||||
}
|
||||
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)
|
||||
public class CallViewModel extends AndroidViewModel {
|
||||
|
||||
private static final String TAG = "CallViewModel";
|
||||
private static final String TAG = CallViewModel.class.getSimpleName();
|
||||
|
||||
private final CallCoordinator callCoordinator;
|
||||
|
||||
@@ -46,8 +46,8 @@ public class CallViewModel extends AndroidViewModel {
|
||||
private final MediatorLiveData<CallState> callState;
|
||||
|
||||
// Observer References for one-time observe
|
||||
private Observer<Boolean> answerCallObserver;
|
||||
private Observer<Boolean> startOutgoingCallObserver;
|
||||
private Observer<VideoTrack> answerCallObserver;
|
||||
private Observer<VideoTrack> startOutgoingCallObserver;
|
||||
|
||||
private final AtomicBoolean hasCallEnded = new AtomicBoolean(false);
|
||||
|
||||
@@ -211,24 +211,25 @@ public class CallViewModel extends AndroidViewModel {
|
||||
callCoordinator.startMediaCapture();
|
||||
|
||||
// Create one-time observer
|
||||
LiveData<Boolean> mediaReady = callCoordinator.getMediaCaptureReady();
|
||||
LiveData<VideoTrack> localTrack = callCoordinator.getLocalVideoTrack();
|
||||
|
||||
answerCallObserver =
|
||||
new Observer<Boolean>() {
|
||||
new Observer<VideoTrack>() {
|
||||
@Override
|
||||
public void onChanged(Boolean ready) {
|
||||
if (Boolean.TRUE.equals(ready)) {
|
||||
mediaReady.removeObserver(this);
|
||||
public void onChanged(VideoTrack videoTrack) {
|
||||
if (videoTrack != null) {
|
||||
// Media is ready, remove observer
|
||||
localTrack.removeObserver(this);
|
||||
answerCallObserver = null;
|
||||
|
||||
Log.d(TAG, "Media capture ready, answering call (WebRTC)");
|
||||
Log.d(TAG, "Local video ready, answering call (WebRTC)");
|
||||
|
||||
callCoordinator.answerWebRTC();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mediaReady.observeForever(answerCallObserver);
|
||||
localTrack.observeForever(answerCallObserver);
|
||||
}
|
||||
|
||||
/** Start outgoing call with media capture Called by Activity for outgoing calls */
|
||||
@@ -243,28 +244,30 @@ public class CallViewModel extends AndroidViewModel {
|
||||
callCoordinator.startMediaCapture();
|
||||
|
||||
// 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");
|
||||
callCoordinator.startOutgoingCall();
|
||||
} else {
|
||||
startOutgoingCallObserver =
|
||||
new Observer<Boolean>() {
|
||||
new Observer<VideoTrack>() {
|
||||
@Override
|
||||
public void onChanged(Boolean ready) {
|
||||
if (Boolean.TRUE.equals(ready)) {
|
||||
mediaReady.removeObserver(this);
|
||||
public void onChanged(VideoTrack videoTrack) {
|
||||
if (videoTrack != null) {
|
||||
// Media is ready, remove observer
|
||||
localTrack.removeObserver(this);
|
||||
startOutgoingCallObserver = null;
|
||||
|
||||
Log.d(TAG, "Media capture ready, starting outgoing call");
|
||||
Log.d(TAG, "Local video ready, starting outgoing call");
|
||||
|
||||
callCoordinator.startOutgoingCall();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mediaReady.observeForever(startOutgoingCallObserver);
|
||||
localTrack.observeForever(startOutgoingCallObserver);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,12 +460,12 @@ public class CallViewModel extends AndroidViewModel {
|
||||
Log.d(TAG, "CallViewModel cleared");
|
||||
|
||||
if (answerCallObserver != null) {
|
||||
callCoordinator.getMediaCaptureReady().removeObserver(answerCallObserver);
|
||||
callCoordinator.getLocalVideoTrack().removeObserver(answerCallObserver);
|
||||
answerCallObserver = null;
|
||||
}
|
||||
|
||||
if (startOutgoingCallObserver != null) {
|
||||
callCoordinator.getMediaCaptureReady().removeObserver(startOutgoingCallObserver);
|
||||
callCoordinator.getLocalVideoTrack().removeObserver(startOutgoingCallObserver);
|
||||
startOutgoingCallObserver = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,15 +24,11 @@ import org.webrtc.VideoTrack;
|
||||
|
||||
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 AUDIO_TRACK_ID = "audio_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 PeerConnectionFactory peerConnectionFactory;
|
||||
|
||||
@@ -41,7 +37,6 @@ public class MediaStreamManager {
|
||||
private AudioSource audioSource;
|
||||
private SurfaceTextureHelper surfaceTextureHelper;
|
||||
private volatile boolean isFrontCamera = true;
|
||||
private volatile boolean isCapturing = false;
|
||||
|
||||
public interface Callback {
|
||||
void onMediaStreamReady(MediaStream stream);
|
||||
@@ -61,8 +56,9 @@ public class MediaStreamManager {
|
||||
this.peerConnectionFactory = peerConnectionFactory;
|
||||
}
|
||||
|
||||
/** Create a media stream with an audio track and a video track. */
|
||||
public synchronized void createMediaStream(Callback callback) {
|
||||
/** Create media stream with audio and optionally video */
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public void createMediaStream(Callback callback) {
|
||||
try {
|
||||
MediaStream mediaStream = peerConnectionFactory.createLocalMediaStream(STREAM_ID);
|
||||
|
||||
@@ -72,11 +68,24 @@ public class MediaStreamManager {
|
||||
AudioTrack audioTrack = peerConnectionFactory.createAudioTrack(AUDIO_TRACK_ID, audioSource);
|
||||
mediaStream.addTrack(audioTrack);
|
||||
|
||||
// Create video source and track
|
||||
videoSource = peerConnectionFactory.createVideoSource(false);
|
||||
// Create video track
|
||||
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);
|
||||
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);
|
||||
|
||||
} 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
|
||||
private VideoCapturer createVideoCapturer() {
|
||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
|
||||
@@ -176,14 +129,6 @@ public class MediaStreamManager {
|
||||
}
|
||||
|
||||
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)) {
|
||||
Log.e(TAG, "switchCamera called but videoCapturer is not a CameraVideoCapturer");
|
||||
return;
|
||||
@@ -241,12 +186,10 @@ public class MediaStreamManager {
|
||||
}
|
||||
|
||||
/** Cleanup resources */
|
||||
public synchronized void dispose() {
|
||||
public void dispose() {
|
||||
if (videoCapturer != null) {
|
||||
try {
|
||||
if (isCapturing) {
|
||||
videoCapturer.stopCapture();
|
||||
}
|
||||
videoCapturer.stopCapture();
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Error stopping capture", e);
|
||||
}
|
||||
@@ -254,8 +197,6 @@ public class MediaStreamManager {
|
||||
videoCapturer = null;
|
||||
}
|
||||
|
||||
isCapturing = false;
|
||||
|
||||
if (surfaceTextureHelper != null) {
|
||||
surfaceTextureHelper.dispose();
|
||||
surfaceTextureHelper = null;
|
||||
|
||||
@@ -92,10 +92,6 @@ public class AttachmentTypeSelector extends PopupWindow {
|
||||
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);
|
||||
|
||||
setContentView(layout);
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
|
||||
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 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,
|
||||
MediaKeyboard.MediaKeyboardListener {
|
||||
|
||||
private static final String TAG = "InputPanel";
|
||||
private static final String TAG = InputPanel.class.getSimpleName();
|
||||
|
||||
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 {
|
||||
void onRecorderStarted();
|
||||
|
||||
@@ -477,10 +452,10 @@ public class InputPanel extends ConstraintLayout
|
||||
}
|
||||
|
||||
public long hide() {
|
||||
long elapsedTime = getElapsedTime();
|
||||
long elapsedtime = System.currentTimeMillis() - startTime.get();
|
||||
this.startTime.set(0);
|
||||
ViewUtil.fadeOut(this.recordTimeView, FADE_TIME, View.INVISIBLE);
|
||||
return elapsedTime;
|
||||
return elapsedtime;
|
||||
}
|
||||
|
||||
@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) {
|
||||
return DateUtils.formatElapsedTime(TimeUnit.MILLISECONDS.toSeconds(ms))
|
||||
+ String.format(".%01d", ((ms / 100) % 10));
|
||||
|
||||
@@ -38,7 +38,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
* been opened and what its height would be.
|
||||
*/
|
||||
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;
|
||||
|
||||
|
||||
@@ -53,11 +53,10 @@ public class MediaView extends FrameLayout {
|
||||
@NonNull Window window,
|
||||
@NonNull Uri source,
|
||||
@Nullable String fileName,
|
||||
@Nullable String mediaType,
|
||||
@NonNull String mediaType,
|
||||
long size,
|
||||
boolean autoplay)
|
||||
throws IOException {
|
||||
mediaType = mediaType == null ? "null" : mediaType;
|
||||
if (mediaType.startsWith("image/")) {
|
||||
imageView.setVisibility(View.VISIBLE);
|
||||
if (videoView.resolved()) videoView.get().setVisibility(View.GONE);
|
||||
|
||||
@@ -287,15 +287,4 @@ public final class MicrophoneRecorderView extends FrameLayout implements View.On
|
||||
.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 {
|
||||
|
||||
private static final String TAG = "QuoteView";
|
||||
private static final String TAG = QuoteView.class.getSimpleName();
|
||||
|
||||
private static final int MESSAGE_TYPE_PREVIEW = 0;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ public class ScaleStableImageView extends AppCompatImageView
|
||||
implements KeyboardAwareLinearLayout.OnKeyboardShownListener,
|
||||
KeyboardAwareLinearLayout.OnKeyboardHiddenListener {
|
||||
|
||||
private static final String TAG = "ScaleStableImageView";
|
||||
private static final String TAG = ScaleStableImageView.class.getSimpleName();
|
||||
|
||||
private Drawable defaultDrawable;
|
||||
private Drawable currentDrawable;
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||
|
||||
public class SearchToolbar extends LinearLayout {
|
||||
|
||||
private static final String TAG = "SearchToolbar";
|
||||
private static final String TAG = SearchToolbar.class.getSimpleName();
|
||||
private float x, y;
|
||||
private MenuItem searchItem;
|
||||
private EditText searchText;
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
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 HEIGHT = 1;
|
||||
private static final int MIN_WIDTH = 0;
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.mms.VcardSlide;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
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 TextView name;
|
||||
|
||||
+43
-125
@@ -6,38 +6,34 @@ import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.PlaybackException;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.session.MediaController;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
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<Map<Integer, Long>> durations =
|
||||
new MutableLiveData<>(new HashMap<>());
|
||||
private final Map<Integer, Uri> durationUris = new HashMap<>();
|
||||
private final Set<Integer> extractionInProgress = new HashSet<>();
|
||||
private final ExecutorService extractionExecutor = Executors.newFixedThreadPool(2);
|
||||
|
||||
private @Nullable MediaController mediaController;
|
||||
private @Nullable ChatAudioQueueProvider queueProvider;
|
||||
private @Nullable Player.Listener playerListener;
|
||||
private final Handler handler;
|
||||
private boolean isUserSeeking = false;
|
||||
|
||||
@@ -51,11 +47,6 @@ public class AudioPlaybackViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
public void setMediaController(@Nullable MediaController controller) {
|
||||
if (this.mediaController != null && playerListener != null) {
|
||||
this.mediaController.removeListener(playerListener);
|
||||
}
|
||||
playerListener = null;
|
||||
|
||||
this.mediaController = controller;
|
||||
if (mediaController != null && mediaController.isPlaying()) {
|
||||
startUpdateProgress();
|
||||
@@ -68,41 +59,30 @@ public class AudioPlaybackViewModel extends ViewModel {
|
||||
public void loadAudioAndPlay(int msgId, Uri audioUri) {
|
||||
if (mediaController == null) return;
|
||||
|
||||
String mediaId = String.valueOf(msgId);
|
||||
// Set media item if we have a different audio.
|
||||
if (isDifferentAudio(msgId, audioUri)) {
|
||||
updateState(msgId, audioUri, AudioPlaybackState.PlaybackStatus.LOADING, 0, 0);
|
||||
|
||||
MediaItem current = mediaController.getCurrentMediaItem();
|
||||
if (current != null && mediaId.equals(current.mediaId)) {
|
||||
mediaController.play();
|
||||
return;
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder().setMediaId(String.valueOf(msgId)).setUri(audioUri).build();
|
||||
mediaController.setMediaItem(mediaItem);
|
||||
mediaController.prepare();
|
||||
}
|
||||
|
||||
updateState(msgId, audioUri, AudioPlaybackState.PlaybackStatus.LOADING, 0, 0);
|
||||
|
||||
List<MediaItem> items = null;
|
||||
int startIndex = -1;
|
||||
|
||||
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.play();
|
||||
play(msgId, audioUri);
|
||||
}
|
||||
|
||||
private static int indexOfMediaId(List<MediaItem> items, String mediaId) {
|
||||
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() {
|
||||
@@ -113,17 +93,7 @@ public class AudioPlaybackViewModel extends ViewModel {
|
||||
// Check cache
|
||||
Map<Integer, Long> currentDurations = durations.getValue();
|
||||
if (currentDurations != null && currentDurations.containsKey(msgId)) {
|
||||
Uri cachedUri = durationUris.get(msgId);
|
||||
if (audioUri.equals(cachedUri)) {
|
||||
return;
|
||||
}
|
||||
Map<Integer, Long> updated = new HashMap<>(currentDurations);
|
||||
updated.remove(msgId);
|
||||
durations.setValue(updated);
|
||||
durationUris.remove(msgId);
|
||||
synchronized (extractionInProgress) {
|
||||
extractionInProgress.remove(msgId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check extracting
|
||||
@@ -144,7 +114,6 @@ public class AudioPlaybackViewModel extends ViewModel {
|
||||
Map<Integer, Long> updatedDurations = new HashMap<>(durations.getValue());
|
||||
updatedDurations.put(msgId, duration);
|
||||
durations.setValue(updatedDurations);
|
||||
durationUris.put(msgId, audioUri);
|
||||
});
|
||||
|
||||
synchronized (extractionInProgress) {
|
||||
@@ -169,68 +138,46 @@ public class AudioPlaybackViewModel extends ViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
public void pause(int msgId) {
|
||||
if (isCurrentItem(msgId)) {
|
||||
public void pause(int msgId, Uri audioUri) {
|
||||
if (mediaController != null && isSameAudio(msgId, audioUri)) {
|
||||
mediaController.pause();
|
||||
}
|
||||
}
|
||||
|
||||
public void play(int msgId) {
|
||||
if (isCurrentItem(msgId)) {
|
||||
public void play(int msgId, Uri audioUri) {
|
||||
if (mediaController != null && isSameAudio(msgId, audioUri)) {
|
||||
mediaController.play();
|
||||
}
|
||||
}
|
||||
|
||||
public void seekTo(long position, int msgId) {
|
||||
if (isCurrentItem(msgId)) {
|
||||
public void seekTo(long position, int msgId, Uri audioUri) {
|
||||
if (mediaController != null && isSameAudio(msgId, audioUri)) {
|
||||
mediaController.seekTo(position);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop(int msgId) {
|
||||
if (isCurrentItem(msgId)) {
|
||||
public void stop(int msgId, Uri audioUri) {
|
||||
if (mediaController != null && isSameAudio(msgId, audioUri)) {
|
||||
mediaController.stop();
|
||||
mediaController.clearMediaItems();
|
||||
stopUpdateProgress();
|
||||
playbackState.setValue(AudioPlaybackState.idle());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCurrentItem(int msgId) {
|
||||
if (mediaController == null) return false;
|
||||
MediaItem current = mediaController.getCurrentMediaItem();
|
||||
return current != null && String.valueOf(msgId).equals(current.mediaId);
|
||||
public void stopNonMessageAudioPlayback() {
|
||||
stopByIds(NON_MESSAGE_AUDIO_MSG_ID);
|
||||
}
|
||||
|
||||
// A special method for deleting message, where we only use message Ids
|
||||
public void stopByIds(int... msgIds) {
|
||||
if (mediaController == null) return;
|
||||
|
||||
AudioPlaybackState currentState = playbackState.getValue();
|
||||
boolean stoppedCurrent = false;
|
||||
|
||||
if (currentState != null) {
|
||||
if (mediaController != null && currentState != null) {
|
||||
for (int msgId : msgIds) {
|
||||
if (msgId == currentState.getMsgId()) {
|
||||
mediaController.stop();
|
||||
mediaController.clearMediaItems();
|
||||
stopUpdateProgress();
|
||||
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() {
|
||||
if (mediaController == null) return;
|
||||
|
||||
playerListener =
|
||||
mediaController.addListener(
|
||||
new Player.Listener() {
|
||||
@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 (player.isPlaying()) {
|
||||
startUpdateProgress();
|
||||
@@ -256,43 +203,20 @@ public class AudioPlaybackViewModel extends ViewModel {
|
||||
}
|
||||
updateCurrentState(false);
|
||||
}
|
||||
if (events.containsAny(Player.EVENT_MEDIA_ITEM_TRANSITION)) {
|
||||
updateCurrentState(true);
|
||||
}
|
||||
if (events.containsAny(Player.EVENT_PLAYBACK_STATE_CHANGED)) {
|
||||
if (player.getPlaybackState() == Player.STATE_READY) {
|
||||
updateCurrentState(false);
|
||||
} else if (player.getPlaybackState() == Player.STATE_ENDED
|
||||
&& !player.hasNextMediaItem()) {
|
||||
mediaController.stop();
|
||||
mediaController.clearMediaItems();
|
||||
stopUpdateProgress();
|
||||
playbackState.setValue(AudioPlaybackState.idle());
|
||||
} else if (player.getPlaybackState() == Player.STATE_ENDED) {
|
||||
// This is to prevent automatically playing after the audio
|
||||
// has been play to the end once, then user dragged the seek bar again
|
||||
mediaController.setPlayWhenReady(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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 {
|
||||
if (events.containsAny(Player.EVENT_PLAYER_ERROR)) {
|
||||
updateCurrentAudioState(AudioPlaybackState.PlaybackStatus.ERROR, 0, 0);
|
||||
mediaController.clearMediaItems();
|
||||
}
|
||||
}
|
||||
};
|
||||
mediaController.addListener(playerListener);
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
private final Runnable progressRunnable =
|
||||
new Runnable() {
|
||||
@@ -394,7 +313,6 @@ public class AudioPlaybackViewModel extends ViewModel {
|
||||
protected void onCleared() {
|
||||
stopUpdateProgress();
|
||||
extractionExecutor.shutdown();
|
||||
durationUris.clear();
|
||||
super.onCleared();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.thoughtcrime.securesms.util.DateUtils;
|
||||
|
||||
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 AnimatedVectorDrawableCompat playToPauseDrawable;
|
||||
@@ -109,15 +109,12 @@ public class AudioView extends FrameLayout {
|
||||
|
||||
AudioPlaybackState state = viewModel.getPlaybackState().getValue();
|
||||
|
||||
if (state != null
|
||||
&& msgId == state.getMsgId()
|
||||
&& (state.getStatus() == AudioPlaybackState.PlaybackStatus.PLAYING
|
||||
|| state.getStatus() == AudioPlaybackState.PlaybackStatus.PAUSED)) {
|
||||
if (state != null && msgId == state.getMsgId() && audioUri.equals(state.getAudioUri())) {
|
||||
// Same audio
|
||||
if (state.getStatus() == AudioPlaybackState.PlaybackStatus.PLAYING) {
|
||||
viewModel.pause(msgId);
|
||||
viewModel.pause(msgId, audioUri);
|
||||
} else {
|
||||
viewModel.play(msgId);
|
||||
viewModel.play(msgId, audioUri);
|
||||
}
|
||||
} else {
|
||||
// Different audio
|
||||
@@ -148,7 +145,7 @@ public class AudioView extends FrameLayout {
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
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);
|
||||
|
||||
this.progress = 0;
|
||||
this.duration = 0;
|
||||
|
||||
viewModel.ensureDurationLoaded(getContext(), msgId, audioUri);
|
||||
|
||||
// Get duration
|
||||
Map<Integer, Long> durations = viewModel.getDurations().getValue();
|
||||
if (durations != null && durations.containsKey(msgId)) {
|
||||
this.duration = Math.toIntExact(durations.get(msgId));
|
||||
updateTimestampsAndSeekBar();
|
||||
} else {
|
||||
viewModel.ensureDurationLoaded(getContext(), msgId, audioUri);
|
||||
}
|
||||
updateTimestampsAndSeekBar();
|
||||
|
||||
if (audio.asAttachment().isVoiceNote() || !audio.getFileName().isPresent()) {
|
||||
title.setVisibility(View.GONE);
|
||||
@@ -311,7 +305,7 @@ public class AudioView extends FrameLayout {
|
||||
if (audioUri == null || state == null) return;
|
||||
|
||||
// Check if this state is about this message
|
||||
boolean isThisMessage = msgId == state.getMsgId();
|
||||
boolean isThisMessage = msgId == state.getMsgId() && audioUri.equals(state.getAudioUri());
|
||||
|
||||
if (isThisMessage) {
|
||||
updateUIForPlaybackState(state);
|
||||
@@ -346,18 +340,20 @@ public class AudioView extends FrameLayout {
|
||||
private void onDurationsChanged(Map<Integer, Long> durations) {
|
||||
AudioPlaybackState state = viewModel.getPlaybackState().getValue();
|
||||
|
||||
// When there is no playback happening, msgId can be -1 and audioUri is null
|
||||
if (state != null
|
||||
&& msgId >= 0
|
||||
&& msgId == state.getMsgId()
|
||||
&& (state.getStatus() == AudioPlaybackState.PlaybackStatus.PLAYING
|
||||
|| state.getStatus() == AudioPlaybackState.PlaybackStatus.PAUSED)) {
|
||||
return;
|
||||
&& audioUri != null
|
||||
&& audioUri.equals(state.getAudioUri())) {
|
||||
return; // Is playing this message
|
||||
}
|
||||
|
||||
Long duration = durations.get(msgId);
|
||||
if (duration != null) {
|
||||
if (duration != null && seekBar.getMax() <= 100) {
|
||||
this.duration = Math.toIntExact(duration);
|
||||
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