mirror of
https://github.com/ArcaneChat/android.git
synced 2026-07-03 14:05:24 +02:00
Compare commits
321 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0bc34daf35 | |||
| c47482851b | |||
| e2fb3968ca | |||
| ceffc5df1c | |||
| e6fd96bf35 | |||
| 60ab7f2826 | |||
| e511237e5e | |||
| f249fd6174 | |||
| 6db45c1911 | |||
| 06a1114744 | |||
| 3afff5625b | |||
| cef8ddeedb | |||
| 2f0ac973c0 | |||
| 9465776715 | |||
| 39685a575d | |||
| 8601736387 | |||
| 98d8572baf | |||
| 917e764b6f | |||
| 52263150c8 | |||
| c2aba94b34 | |||
| 1278dfd395 | |||
| 44a688470f | |||
| e9a09d9c72 | |||
| abc49a3b95 | |||
| cdd4e5eccd | |||
| fa8ed2b881 | |||
| 0731fe9447 | |||
| d3567d9f0c | |||
| 66ab1f8051 | |||
| 872fd17f5e | |||
| af49018911 | |||
| f870e9c8fd | |||
| d5982ba09f | |||
| 22d70d7bd4 | |||
| 09bdc32a9c | |||
| 7763ba17bf | |||
| 965204c46f | |||
| 815e4def6f | |||
| dc3ca8ff70 | |||
| 3bccb3fb84 | |||
| 402d93b5a4 | |||
| 927dc46431 | |||
| 87c32b1904 | |||
| bb97213460 | |||
| 9ca9bf1acd | |||
| 885c902b14 | |||
| dc4ee3f686 | |||
| 61bf5aaad4 | |||
| 18595cbee1 | |||
| 5d76586ac1 | |||
| dc78307df7 | |||
| 86a9cbfb45 | |||
| 623b20f713 | |||
| 5f4eae798f | |||
| 4a8609822d | |||
| 89ddc1e01f | |||
| 0ce42578fa | |||
| faa7ad0a35 | |||
| 38e8ceb253 | |||
| a32460f253 | |||
| 198268a4c3 | |||
| 71158970ae | |||
| 1383b06e86 | |||
| 24165e311b | |||
| 5ec892db34 | |||
| caef7eda29 | |||
| cf53af4778 | |||
| 415e0c2b5f | |||
| 9a22597473 | |||
| beb45af440 | |||
| 87a21eb0f2 | |||
| dea51cd356 | |||
| ed540e5584 | |||
| 9f80c9f35f | |||
| 5dec1b24cd | |||
| acb4eb2ae1 | |||
| 20c0354938 | |||
| eac112d602 | |||
| 9b4f659f67 | |||
| 0166d4e656 | |||
| 92b3761d2d | |||
| 484cee21c6 | |||
| 0e40318050 | |||
| b2cc76ff2e | |||
| 96acaaf000 | |||
| 400e5ea671 | |||
| 4fac460926 | |||
| 94a5631566 | |||
| ea91075107 | |||
| 0c7b82b9e4 | |||
| d765d3ddeb | |||
| 094fb1e2a4 | |||
| f7244c2152 | |||
| 2b5d1005e3 | |||
| b1eadf0716 | |||
| c001c13053 | |||
| ea4ec343bc | |||
| 5642e86f6a | |||
| 46db14fc3e | |||
| 0f6d9670ff | |||
| 4c3c24ae5a | |||
| eeb558d94d | |||
| 5e08f56dd3 | |||
| d99f150dd2 | |||
| 82b3100570 | |||
| 348b6fd3c1 | |||
| c2f492463f | |||
| 38239b2644 | |||
| 4b996c95de | |||
| 89d90efcef | |||
| d5b4bae502 | |||
| c3ec163e1a | |||
| a927a32909 | |||
| 9aab4517ef | |||
| 727e68edc7 | |||
| 9047de85c2 | |||
| 29c313ba58 | |||
| 487f601c09 | |||
| 7589b5ac37 | |||
| 8a9ce2ddd1 | |||
| be449f5afc | |||
| ac832a617e | |||
| 4fd2832370 | |||
| 52b072a7f3 | |||
| 55862757d6 | |||
| cbdf495c5f | |||
| a9d0d2e179 | |||
| d0cbf169dd | |||
| 833bc14405 | |||
| fb3620d0e3 | |||
| 02466e09fe | |||
| e39134faad | |||
| fa90b167ef | |||
| f5f20399ac | |||
| 659152dfcf | |||
| f34e87a593 | |||
| a172441155 | |||
| 90ca85ae9c | |||
| 40c0612412 | |||
| 5de79e3b0b | |||
| 39369aadd7 | |||
| b1e709d5bc | |||
| f760573fe4 | |||
| 4263ece65b | |||
| 217c45c102 | |||
| 3b2145ec79 | |||
| 566d1a1c13 | |||
| 09dab94807 | |||
| 6e3ddb8d96 | |||
| 8d7bb437eb | |||
| 392b43e8f7 | |||
| 32201a60c4 | |||
| bb114c570f | |||
| 42c52c10c7 | |||
| ade21a2cb8 | |||
| aced181d92 | |||
| 92cd1fc31c | |||
| 743138a100 | |||
| bebda06160 | |||
| f581930a40 | |||
| 70d4844dc6 | |||
| d3dd50dcac | |||
| ab8fdbad78 | |||
| 174bd9e986 | |||
| ab6f7ec97f | |||
| 6288463ddb | |||
| d700353ed8 | |||
| e979873bb3 | |||
| 331c77d317 | |||
| f9792615b7 | |||
| a94dc9336e | |||
| 6f1e11e860 | |||
| e720b4cef8 | |||
| 10f4534ee8 | |||
| 28c02a767d | |||
| 98f2ae5430 | |||
| 00c61133d5 | |||
| e457ab431d | |||
| b854fb6689 | |||
| 7c3a80296a | |||
| 4328bdd698 | |||
| e5f45df59a | |||
| 9dd270172b | |||
| 310f1fb921 | |||
| 5f14f0792f | |||
| fb3b43426a | |||
| 62b66d93ee | |||
| 6b12ff335d | |||
| ea522be8f4 | |||
| 44441f1ff9 | |||
| 3fca807356 | |||
| 4dc377233a | |||
| aabc60d36f | |||
| a653ac6382 | |||
| df7cc90da9 | |||
| eeb56a4630 | |||
| 713a87993e | |||
| 8ff55f3cf3 | |||
| 0fd72d2415 | |||
| 3c7f12c7b8 | |||
| 56abaa7c20 | |||
| 9335eedd04 | |||
| c13536031e | |||
| 57581634c4 | |||
| 4fa2535118 | |||
| 0ac0276dea | |||
| ed72a60f24 | |||
| 656aca7d1f | |||
| 3594847d8e | |||
| c17ac1d090 | |||
| 93ba86a779 | |||
| 738f5b2cc8 | |||
| 71a473d3e3 | |||
| d64d094de3 | |||
| 41df4eb03d | |||
| dde82ccb2f | |||
| c89f9ce875 | |||
| fc1adc4863 | |||
| d68c32ff26 | |||
| b67e9d795e | |||
| 87ac24124a | |||
| 138dcd7bdf | |||
| ae7be8a841 | |||
| 0d51e097d5 | |||
| abff1c0c55 | |||
| 1894425ad2 | |||
| 08643d3389 | |||
| 3460e7e405 | |||
| 36249777e0 | |||
| ca40ae7869 | |||
| cb05abb85f | |||
| b878ed4df3 | |||
| 16a02a5e4f | |||
| c614c5f004 | |||
| 8a76f99701 | |||
| fd5a3b9f8e | |||
| c7a542aac7 | |||
| 6cab1d1cf4 | |||
| be8009c950 | |||
| f6c1fa2f5c | |||
| e76f445985 | |||
| 6949be6fd8 | |||
| 69e18a009b | |||
| 64ef656544 | |||
| f95a6e8db7 | |||
| b81757d579 | |||
| 30c0026840 | |||
| efb4bf0536 | |||
| cd2fc0df0f | |||
| 51380cf906 | |||
| ded0c44ce3 | |||
| 61f5064929 | |||
| f316ebea20 | |||
| dc1a029e4a | |||
| 6bbacae54d | |||
| 972d372bd4 | |||
| fe7fb19adf | |||
| 5112389dae | |||
| c190a7b50a | |||
| 7ce8ccff78 | |||
| 7eb0a99fc7 | |||
| cbdd8f19ed | |||
| 5fdf319cda | |||
| 837e2313dd | |||
| e45fd2545e | |||
| 15537ea4b2 | |||
| df97f4069a | |||
| dea8b2bed9 | |||
| 0d130a4218 | |||
| b4ee89c1e8 | |||
| b2e7b8fdb6 | |||
| a494feb272 | |||
| 5efce4345a | |||
| b7703ce71f | |||
| c0ed32b645 | |||
| 7e4f408059 | |||
| a1993426de | |||
| bbb6ce8def | |||
| c601595477 | |||
| f909174119 | |||
| b0762a8b46 | |||
| 545f987f31 | |||
| b625c57d2d | |||
| 022d24bc5f | |||
| c5c09008a6 | |||
| 7679cc8bba | |||
| 4807d05861 | |||
| 34eedf5f07 | |||
| 941c186142 | |||
| b54ac50ea5 | |||
| b99426b7c6 | |||
| de1eedc63f | |||
| c3dac043cb | |||
| 00b52d4fe6 | |||
| b66bf595e2 | |||
| 7c24e836fe | |||
| 694f3bf013 | |||
| f6f42991e8 | |||
| 23f6803be9 | |||
| cdcb7fab4b | |||
| 5ee3607312 | |||
| 4b9642f51f | |||
| 4abbf3091f | |||
| ec4db3e58f | |||
| 0d2175b641 | |||
| a821ee363a | |||
| f43083acef | |||
| 7aed78a935 | |||
| 26c30fe3dc | |||
| 5550b8b1f5 | |||
| c94512fb24 | |||
| d1db6e5a8a | |||
| d352583237 | |||
| c8469be136 | |||
| 97620b8a3c | |||
| 24317b38bf | |||
| 6b28ed15e3 | |||
| b8b203e517 | |||
| e5c1e477f0 | |||
| b0699ab9be | |||
| 499f4aafc8 |
@@ -8,7 +8,7 @@ ArcaneChat is a Delta Chat Android client built on top of the official Delta Cha
|
||||
- **Language:** Java (Java 8 compatibility)
|
||||
- **Build System:** Gradle with Android Gradle Plugin 8.11.1
|
||||
- **Min SDK:** 21 (Android 5.0)
|
||||
- **Target SDK:** 35 (Android 15)
|
||||
- **Target SDK:** 36 (Android 16)
|
||||
- **NDK Version:** 27.0.12077973
|
||||
- **Native Components:** Rust (deltachat-core-rust submodule)
|
||||
- **UI Framework:** Android SDK, Material Design Components
|
||||
@@ -17,13 +17,23 @@ ArcaneChat is a Delta Chat Android client built on top of the official Delta Cha
|
||||
## Repository Structure
|
||||
|
||||
- `src/main/` - Main application source code
|
||||
- `src/main/java/org/thoughtcrime/securesms/` - Main UI components
|
||||
- `src/main/java/com/b44t/messenger/` - Delta Chat core integration
|
||||
- `src/main/java/chat/delta/rpc/` - JSON-RPC bindings (generated, don't edit manually)
|
||||
- `src/main/res/` - Android resources (layouts, strings, drawables)
|
||||
- `src/androidTest/` - Instrumented tests (UI tests, benchmarks)
|
||||
- `src/androidTest/java/com/b44t/messenger/uitests/` - UI tests
|
||||
- `src/androidTest/java/com/b44t/messenger/uibenchmarks/` - Performance benchmarks
|
||||
- `src/gplay/` - Google Play flavor-specific code
|
||||
- `src/foss/` - F-Droid/FOSS flavor-specific code
|
||||
- `jni/deltachat-core-rust/` - Native Rust core library (submodule)
|
||||
- `jni/deltachat-core-rust/` - Native Rust core library (submodule, **don't edit directly**)
|
||||
- `scripts/` - Build and helper scripts
|
||||
- `scripts/ndk-make.sh` - Build native libraries
|
||||
- `scripts/install-toolchains.sh` - Install Rust cross-compilation toolchains
|
||||
- `scripts/generate-rpc-bindings.sh` - Generate JSON-RPC bindings
|
||||
- `docs/` - Documentation
|
||||
- `fastlane/` - App store metadata and screenshots
|
||||
- `.github/workflows/` - CI/CD workflows (GitHub Actions)
|
||||
|
||||
## Build Instructions
|
||||
|
||||
@@ -33,17 +43,36 @@ ArcaneChat is a Delta Chat Android client built on top of the official Delta Cha
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
This MUST be done first before any build attempts.
|
||||
|
||||
2. **Build native libraries:**
|
||||
2. **Set up environment variables:**
|
||||
```bash
|
||||
export ANDROID_NDK_ROOT=/path/to/ndk/27.0.12077973
|
||||
export PATH=${PATH}:${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/:${ANDROID_NDK_ROOT}
|
||||
```
|
||||
Note: Path format varies by OS (linux-x86_64, darwin-x86_64, etc.)
|
||||
|
||||
3. **Install Rust toolchains:**
|
||||
```bash
|
||||
scripts/install-toolchains.sh
|
||||
```
|
||||
Required for building the native Rust components.
|
||||
|
||||
4. **Build native libraries:**
|
||||
```bash
|
||||
scripts/ndk-make.sh
|
||||
```
|
||||
Note: First run may take significant time as it builds for all architectures (armeabi-v7a, arm64-v8a, x86, x86_64)
|
||||
**IMPORTANT:** First run takes 30-60 minutes as it builds for all architectures (armeabi-v7a, arm64-v8a, x86, x86_64).
|
||||
For faster development builds, build for a single architecture:
|
||||
```bash
|
||||
scripts/ndk-make.sh armeabi-v7a
|
||||
```
|
||||
|
||||
3. **Build APK:**
|
||||
5. **Build APK:**
|
||||
```bash
|
||||
./gradlew assembleDebug
|
||||
```
|
||||
Build time: ~2-5 minutes after native libraries are built.
|
||||
|
||||
### Build Flavors
|
||||
|
||||
@@ -55,6 +84,24 @@ ArcaneChat is a Delta Chat Android client built on top of the official Delta Cha
|
||||
- Debug APKs: `build/outputs/apk/gplay/debug/` and `build/outputs/apk/fat/debug/`
|
||||
- Release APKs require signing configuration in `~/.gradle/gradle.properties`
|
||||
|
||||
### Common Build Issues
|
||||
|
||||
1. **Missing NDK or incorrect version:**
|
||||
- Error: `ANDROID_NDK_ROOT not set` or native library missing
|
||||
- Solution: Install NDK 27.0.12077973 and set ANDROID_NDK_ROOT environment variable
|
||||
|
||||
2. **Submodules not initialized:**
|
||||
- Error: Missing deltachat-core-rust files
|
||||
- Solution: Run `git submodule update --init --recursive`
|
||||
|
||||
3. **Gradle wrapper validation:**
|
||||
- Always validate gradle wrapper before building: `./gradlew wrapper --gradle-version=current`
|
||||
- Wrapper is validated in CI via `gradle/actions/wrapper-validation@v4`
|
||||
|
||||
4. **Clean build issues:**
|
||||
- If build fails, try: `./gradlew clean && scripts/ndk-make.sh && ./gradlew assembleDebug`
|
||||
- Remove `build/` directory if clean doesn't work
|
||||
|
||||
## Testing
|
||||
|
||||
### Running Unit Tests
|
||||
@@ -62,16 +109,19 @@ ArcaneChat is a Delta Chat Android client built on top of the official Delta Cha
|
||||
```bash
|
||||
./gradlew test
|
||||
```
|
||||
Expected duration: 1-3 minutes
|
||||
|
||||
### Running Instrumented Tests
|
||||
|
||||
1. **Disable animations** on your device/emulator:
|
||||
- Developer Options → Set "Window animation scale", "Transition animation scale", and "Animator duration scale" to 0x
|
||||
- **CRITICAL:** Tests will fail if animations are enabled
|
||||
|
||||
2. **Run tests:**
|
||||
```bash
|
||||
./gradlew connectedAndroidTest
|
||||
```
|
||||
Expected duration: 10-30 minutes depending on device/emulator
|
||||
|
||||
### Online Tests
|
||||
|
||||
@@ -129,6 +179,14 @@ TEST_MAIL_PW=yourpassword
|
||||
- Java bindings are in `src/main/java/com/b44t/messenger/Dc*.java`
|
||||
- JSON-RPC bindings in `chat.delta.rpc.*` package (generated via dcrpcgen)
|
||||
|
||||
### Generating JSON-RPC Bindings
|
||||
|
||||
To regenerate JSON-RPC bindings after core changes:
|
||||
```bash
|
||||
./scripts/generate-rpc-bindings.sh
|
||||
```
|
||||
**Note:** Requires Rust tooling and [dcrpcgen tool](https://github.com/chatmail/dcrpcgen) installed
|
||||
|
||||
### Working with Translations
|
||||
|
||||
- Translations managed via Transifex (not in repository)
|
||||
@@ -142,6 +200,43 @@ Decode crash symbols:
|
||||
$ANDROID_NDK_ROOT/ndk-stack --sym obj/local/armeabi-v7a --dump crash.txt > decoded.txt
|
||||
```
|
||||
|
||||
## Validation and Quality Checks
|
||||
|
||||
### Pre-commit Checks
|
||||
|
||||
Before committing changes, always run:
|
||||
|
||||
1. **Gradle wrapper validation:**
|
||||
```bash
|
||||
./gradlew wrapper --gradle-version=current
|
||||
```
|
||||
|
||||
2. **Build verification:**
|
||||
```bash
|
||||
./gradlew assembleDebug
|
||||
```
|
||||
|
||||
3. **Unit tests:**
|
||||
```bash
|
||||
./gradlew test
|
||||
```
|
||||
|
||||
4. **Code style:** Match existing code style in modified files (no automatic formatter configured)
|
||||
|
||||
### When to Rebuild Native Libraries
|
||||
|
||||
Rebuild native libraries (`scripts/ndk-make.sh`) when:
|
||||
- Updating deltachat-core-rust submodule
|
||||
- Modifying anything in `jni/` directory
|
||||
- Changing NDK version
|
||||
- After `git clean -fdx` or fresh clone
|
||||
|
||||
**DO NOT** rebuild native libraries for:
|
||||
- Pure Java/Kotlin code changes
|
||||
- Resource file changes
|
||||
- Gradle configuration changes (unless changing native library linking)
|
||||
- Documentation updates
|
||||
|
||||
## WebXDC Support
|
||||
|
||||
ArcaneChat has extended WebXDC support:
|
||||
@@ -152,12 +247,24 @@ ArcaneChat has extended WebXDC support:
|
||||
|
||||
## Important Files
|
||||
|
||||
- `build.gradle` - Main build configuration
|
||||
- `build.gradle` - Main build configuration (Android Gradle Plugin 8.11.1, Java 8 compatibility)
|
||||
- `CONTRIBUTING.md` - Contribution guidelines
|
||||
- `BUILDING.md` - Detailed build setup
|
||||
- `BUILDING.md` - Detailed build setup instructions
|
||||
- `RELEASE.md` - Release process
|
||||
- `proguard-rules.pro` - ProGuard configuration
|
||||
- `google-services.json` - Firebase configuration (gplay flavor)
|
||||
- `proguard-rules.pro` - ProGuard configuration (enabled for both debug and release)
|
||||
- `google-services.json` - Firebase configuration (gplay flavor only)
|
||||
- `settings.gradle` - Gradle settings
|
||||
- `.github/workflows/` - CI/CD configuration
|
||||
|
||||
## Dependencies and Constraints
|
||||
|
||||
- **Java Version:** Java 8 compatibility (do not use Java 9+ features)
|
||||
- **Gradle:** Use wrapper (`./gradlew`) to ensure correct Gradle version
|
||||
- **NDK:** Must use version 27.0.12077973 (specified in build.gradle)
|
||||
- **Min SDK:** 21 (Android 5.0) - code must be compatible
|
||||
- **Target SDK:** 36 (Android 16) - test on this API level when possible
|
||||
- **ProGuard:** Always enabled - ensure ProGuard rules are correct for new dependencies
|
||||
- **Multi-dex:** Enabled - app exceeds 65k method limit
|
||||
|
||||
## Package Structure
|
||||
|
||||
@@ -173,3 +280,35 @@ ArcaneChat has extended WebXDC support:
|
||||
- Native library must be rebuilt after core changes
|
||||
- ProGuard is enabled in both debug and release builds
|
||||
- Multi-dex is enabled due to app size
|
||||
|
||||
## CI/CD Workflows
|
||||
|
||||
### Preview APK Workflow (.github/workflows/preview-apk.yml)
|
||||
|
||||
Runs on every pull request to build and upload a preview APK:
|
||||
|
||||
1. **Setup steps:**
|
||||
- Checks out repository with submodules
|
||||
- Validates Fastlane metadata
|
||||
- Sets up Rust cache (working-directory: jni/deltachat-core-rust)
|
||||
- Sets up Java 17 (Temurin distribution)
|
||||
- Sets up Android SDK
|
||||
- Caches Gradle dependencies
|
||||
- Sets up NDK r27
|
||||
|
||||
2. **Build process:**
|
||||
```bash
|
||||
scripts/install-toolchains.sh && scripts/ndk-make.sh armeabi-v7a
|
||||
./gradlew --no-daemon -PABI_FILTER=armeabi-v7a assembleFossDebug
|
||||
```
|
||||
Note: Builds only armeabi-v7a for faster CI builds
|
||||
|
||||
3. **Output:** Uploads APK artifact to GitHub Actions
|
||||
|
||||
### Important CI Considerations
|
||||
|
||||
- Always validate Gradle wrapper before committing changes
|
||||
- Fastlane metadata must be valid (validated in CI)
|
||||
- Use `--no-daemon` flag for Gradle in CI environments
|
||||
- CI builds use FOSS flavor to avoid Google Services dependencies
|
||||
- Expected CI build time: 15-25 minutes for full workflow
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
name: Core Cache
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'jni/deltachat-core-rust'
|
||||
- 'jni/Android.mk'
|
||||
- 'jni/Application.mk'
|
||||
- 'jni/dc_wrapper.c'
|
||||
- 'scripts/ndk-make.sh'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: android-actions/setup-android@v3
|
||||
- uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: r27
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: jni/deltachat-core-rust
|
||||
|
||||
- name: Get deltachat-core-rust submodule hash
|
||||
id: core-hash
|
||||
run: echo "hash=$(git rev-parse HEAD:jni/deltachat-core-rust)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache compiled core
|
||||
id: cache-core
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
jni/arm64-v8a
|
||||
libs/arm64-v8a
|
||||
key: core-arm64-v8a-${{ steps.core-hash.outputs.hash }}-${{ hashFiles('jni/Android.mk', 'jni/Application.mk', 'jni/dc_wrapper.c', 'scripts/ndk-make.sh') }}-ndk-r27
|
||||
|
||||
- name: Compile core
|
||||
if: steps.cache-core.outputs.cache-hit != 'true'
|
||||
env:
|
||||
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
run: |
|
||||
export PATH="${PATH}:${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/"
|
||||
scripts/install-toolchains.sh && scripts/ndk-make.sh arm64-v8a
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Code Format
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
spotless:
|
||||
name: Check code format (Spotless)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/actions/wrapper-validation@v4
|
||||
- name: Check formatting
|
||||
run: ./gradlew spotlessCheck
|
||||
@@ -14,16 +14,15 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Validate Fastlane Metadata
|
||||
uses: ashutoshgngwr/validate-fastlane-supply-metadata@v2
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
working-directory: jni/deltachat-core-rust
|
||||
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
- uses: android-actions/setup-android@v3
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
@@ -32,23 +31,43 @@ jobs:
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/actions/wrapper-validation@v4
|
||||
|
||||
- uses: android-actions/setup-android@v3
|
||||
- uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: r27
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/actions/wrapper-validation@v4
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: jni/deltachat-core-rust
|
||||
|
||||
- name: Get deltachat-core-rust submodule hash
|
||||
id: core-hash
|
||||
run: echo "hash=$(git rev-parse HEAD:jni/deltachat-core-rust)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Restore compiled core
|
||||
id: core-cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
jni/arm64-v8a
|
||||
libs/arm64-v8a
|
||||
key: core-arm64-v8a-${{ steps.core-hash.outputs.hash }}-${{ hashFiles('jni/Android.mk', 'jni/Application.mk', 'jni/dc_wrapper.c', 'scripts/ndk-make.sh') }}-ndk-r27
|
||||
|
||||
- name: Compile core
|
||||
if: steps.core-cache.outputs.cache-hit != 'true'
|
||||
env:
|
||||
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
run: |
|
||||
export PATH="${PATH}:${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/"
|
||||
scripts/install-toolchains.sh && scripts/ndk-make.sh armeabi-v7a
|
||||
scripts/install-toolchains.sh && scripts/ndk-make.sh arm64-v8a
|
||||
|
||||
- name: Build APK
|
||||
run: ./gradlew --no-daemon -PABI_FILTER=armeabi-v7a assembleFossDebug
|
||||
run: ./gradlew --no-daemon -PABI_FILTER=arm64-v8a assembleFossDebug
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -72,14 +72,3 @@ jobs:
|
||||
files: |
|
||||
build/outputs/apk/foss/release/*.apk
|
||||
build/outputs/mapping/fossRelease/mapping-*.txt
|
||||
|
||||
- name: Release on ZapStore
|
||||
run: |
|
||||
export CHECKSUM=6e2c7cf6da53c3f1a78b523a6aacd6316dce3d74ace6f859c2676729ee439990
|
||||
curl -sL https://cdn.zapstore.dev/$CHECKSUM -o zapstore
|
||||
if echo "$CHECKSUM zapstore" | sha256sum -c --status; then
|
||||
chmod +x zapstore
|
||||
SIGN_WITH=${{ secrets.NOSTR_KEY }} ./zapstore publish --indexer-mode
|
||||
else
|
||||
echo "ERROR: checksum doesn't match!"
|
||||
fi
|
||||
|
||||
+13
-1
@@ -27,7 +27,7 @@ install Rust tooling (read sections below) and the [dcrpcgen tool](https://githu
|
||||
then generate the code running the script:
|
||||
|
||||
```
|
||||
./scripts/generate-rpc-bindings.sh
|
||||
./scripts/update-rpc-bindings.sh
|
||||
```
|
||||
|
||||
## Build Using Nix
|
||||
@@ -243,3 +243,15 @@ $ANDROID_NDK_ROOT/ndk-stack --sym obj/local/armeabi-v7a --dump crash.txt > decod
|
||||
`obj/local/armeabi-v7a` is the extracted path from `deltachat-gplay-release-X.X.X.apk-symbols.zip` file from https://download.delta.chat/android/symbols/
|
||||
|
||||
Replace `armeabi-v7a` by the correct architecture the logs come from (can be guessed by trial and error)
|
||||
|
||||
|
||||
### Deobfuscating Java 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:
|
||||
|
||||
```
|
||||
retrace mapping.txt crash.txt > decoded-crash.txt
|
||||
```
|
||||
|
||||
`mapping.txt` is extracted from the same `deltachat-gplay-release-X.X.X.apk-symbols.zip` file available at https://download.delta.chat/android/symbols/
|
||||
|
||||
@@ -1,5 +1,56 @@
|
||||
# Delta Chat Android Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
* Better incoming call system integration
|
||||
* Calls are not experimental anymore and don't need to be manually enabled
|
||||
* Display a permanent notification when doing location streaming and get rid of dangerous "Access Location in Background" permission
|
||||
* Allow to share location for 24 hours
|
||||
* Allow mini-apps to play audio without user interaction
|
||||
* Mark chats as unread (long tap a chat and select the corresponding option from the three-dot-menu)
|
||||
* Fix process of upgrading from a very old version of the app
|
||||
|
||||
## v2.49.0
|
||||
2026-04
|
||||
|
||||
* Fix file sharing to certain apps (e.g. Material Files, etc.)
|
||||
* Fix problem with calls when microphone permission is not granted
|
||||
* Fix taking pictures and videos in devices with SD cards
|
||||
* Fix flipped orientation for some images
|
||||
* Fix: avoid empty contact request chats when using invite links in a multi-device setup
|
||||
* Remove proxy toggle from profile editing to avoid confusion
|
||||
* Updated translations
|
||||
* Update to core 2.49.0
|
||||
|
||||
## v2.48.0
|
||||
2026-03
|
||||
|
||||
* Add a warning when editing relays
|
||||
* Fix message reordering problems in multi-relay setups
|
||||
* Some more bug fixes and updated translations
|
||||
* Update to core 2.48.0
|
||||
|
||||
## v2.47.0
|
||||
2026-03
|
||||
|
||||
* Allow to set chat description
|
||||
* Unified date display in call bubbles
|
||||
* Explain at "Settings / Chats / Outgoing Media Quality" how to send original quality
|
||||
* Add a basic sticker picker
|
||||
* Leave groups and channels before deletion
|
||||
* Further minimize metadata in messages and while getting in contact.
|
||||
* Increase resilience of multi-relay usage: if on relay goes down, messages are still received in the others.
|
||||
* Allow to hide a relay from contacts instead of removing, allowing smoother relay changes
|
||||
* HTML emails: allow to review and copy links before opening them
|
||||
* Mark call message as seen when accepting/declining a call
|
||||
* Fix: keep original sent timestamp for resent messages
|
||||
* Fix: make clicking on broadcast member-added messages work always
|
||||
* Fix: remove notification when a message is deleted by sender
|
||||
* Fix: avoid "reply privately" not quoting the selected message sometimes
|
||||
* Fix: properly hide the calls button
|
||||
* Some more bug fixes and updated translations
|
||||
* Update to core 2.47.0
|
||||
|
||||
## v2.43.0
|
||||
2026-02
|
||||
|
||||
|
||||
+7
-10
@@ -73,18 +73,15 @@ esp. before/after screenshots.
|
||||
|
||||
### Coding Conventions
|
||||
|
||||
Source files are partly derived from different other open source projects
|
||||
and may follow different coding styles and conventions.
|
||||
|
||||
If you do a PR fixing a bug or adding a feature,
|
||||
please embrace the coding convention you see in the corresponding files,
|
||||
so that the result fits well together.
|
||||
|
||||
Do not refactor or rename things in the same PR
|
||||
to make the diff small and the PR easy to review.
|
||||
|
||||
Project language is Java.
|
||||
|
||||
Code formatting is enforced via [Spotless](https://github.com/diffplug/spotless) Gradle plugin.
|
||||
Auto-format all files by running `./gradlew spotlessApply` before opening a PR.
|
||||
CI will fail if files are not formatted correctly so make sure to run the formatter before pushing.
|
||||
|
||||
If you do a PR fixing a bug or adding a feature, do not refactor or rename things in the same PR
|
||||
to make the diff small and the PR easy to review.
|
||||
|
||||
By using [Delta Chat Core](https://github.com/deltachat/deltachat-core-rust)
|
||||
there is already a strong separation between "UI" and "Model".
|
||||
Further separations and abstraction layers are often not helpful
|
||||
|
||||
+49
-6
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '8.11.1'
|
||||
id 'com.google.gms.google-services' version '4.4.1'
|
||||
id 'com.diffplug.spotless' version '8.3.0'
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -33,8 +34,8 @@ android {
|
||||
useLibrary 'org.apache.http.legacy'
|
||||
|
||||
defaultConfig {
|
||||
versionCode 30000737
|
||||
versionName "2.43.0"
|
||||
versionCode 30000742
|
||||
versionName "2.49.0"
|
||||
|
||||
applicationId "chat.delta.lite"
|
||||
multiDexEnabled true
|
||||
@@ -72,10 +73,20 @@ android {
|
||||
packagingOptions {
|
||||
jniLibs {
|
||||
doNotStrip '**/*.so'
|
||||
keepDebugSymbols += ['*/mips/*.so', '*/mips64/*.so']
|
||||
keepDebugSymbols += [
|
||||
'*/mips/*.so',
|
||||
'*/mips64/*.so'
|
||||
]
|
||||
}
|
||||
resources {
|
||||
excludes += ['LICENSE.txt', 'LICENSE', 'NOTICE', 'asm-license.txt', 'META-INF/LICENSE', 'META-INF/NOTICE']
|
||||
excludes += [
|
||||
'LICENSE.txt',
|
||||
'LICENSE',
|
||||
'NOTICE',
|
||||
'asm-license.txt',
|
||||
'META-INF/LICENSE',
|
||||
'META-INF/NOTICE'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +210,31 @@ android {
|
||||
renderScript true
|
||||
aidl true
|
||||
}
|
||||
}
|
||||
|
||||
spotless {
|
||||
java {
|
||||
target 'src/*/java/**/*.java'
|
||||
targetExclude 'src/main/java/chat/delta/**' // ignore auto-generated code
|
||||
googleJavaFormat() // Google style = 2-space indent, matches Android Studio defaults
|
||||
removeUnusedImports()
|
||||
trimTrailingWhitespace()
|
||||
endWithNewline()
|
||||
}
|
||||
format 'xml', {
|
||||
target 'src/*/res/**/*.xml', 'src/*/AndroidManifest.xml'
|
||||
targetExclude 'src/*/res/values*/strings*.xml' // line-break changes invalidate translations
|
||||
eclipseWtp('xml').configFile('spotless/eclipse-wtp-xml.prefs')
|
||||
trimTrailingWhitespace()
|
||||
endWithNewline()
|
||||
}
|
||||
groovyGradle {
|
||||
target '*.gradle'
|
||||
greclipse()
|
||||
leadingTabsToSpaces(4)
|
||||
trimTrailingWhitespace()
|
||||
endWithNewline()
|
||||
}
|
||||
}
|
||||
|
||||
final def markwon_version = '4.6.2'
|
||||
@@ -214,6 +249,11 @@ dependencies {
|
||||
def media3_version = "1.8.0" // 1.9.0 need minSdkVersion 23
|
||||
|
||||
implementation 'androidx.concurrent:concurrent-futures:1.3.0'
|
||||
implementation 'androidx.core:core:1.17.0'
|
||||
implementation 'androidx.core:core-telecom:1.0.1'
|
||||
implementation 'im.conversations.webrtc:webrtc-android:129.0.0'
|
||||
// For Kotlin->Java interpolation
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.9.4'
|
||||
implementation 'androidx.sharetarget:sharetarget:1.2.0'
|
||||
implementation 'androidx.webkit:webkit:1.14.0'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
@@ -251,7 +291,8 @@ dependencies {
|
||||
implementation 'com.github.amulyakhare:TextDrawable:558677ea31' // number of unread messages,
|
||||
// the one-letter circle for the contacts (when there is not avatar) and a white background.
|
||||
implementation 'com.googlecode.mp4parser:isoparser:1.0.6' // MP4 recoding; upgrading eg. to 1.1.22 breaks recoding, however, i have not investigated further, just reset to 1.0.6
|
||||
implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.10.0') { // for the zooming on photos / media
|
||||
implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.10.0') {
|
||||
// for the zooming on photos / media
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
}
|
||||
|
||||
@@ -260,11 +301,13 @@ dependencies {
|
||||
// <https://github.com/cketti/SafeContentResolver>
|
||||
implementation 'de.cketti.safecontentresolver:safe-content-resolver-v21:1.0.0'
|
||||
|
||||
gplayImplementation('com.google.firebase:firebase-messaging:24.1.2') { // for PUSH notifications, don't upgrade: v25.0.0 requires minSdk>=23
|
||||
gplayImplementation('com.google.firebase:firebase-messaging:24.1.2') {
|
||||
// for PUSH notifications, don't upgrade: v25.0.0 requires minSdk>=23
|
||||
exclude group: 'com.google.firebase', module: 'firebase-core'
|
||||
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
||||
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
||||
}
|
||||
gplayImplementation 'com.google.android.gms:play-services-location:21.3.0'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.assertj:assertj-core:3.27.3'
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
ArcaneChat is a decentralized and secure instant messenger that is easy to use for friends and family.
|
||||
|
||||
• Anonymous. Instant onboarding without a phone number, e-mail or other private data.
|
||||
• Private. Instant on-boarding without a phone number or other private data.
|
||||
|
||||
• Friends & Family: Only chat with people you know. Unknown users cannot message or follow you without mutual consent.
|
||||
|
||||
• Flexible. Supports multiple chat profiles and is easy to setup on multiple devices.
|
||||
|
||||
@@ -23,7 +25,7 @@ ArcaneChat is a Delta Chat client and was created with a focus on usability, goo
|
||||
<li>Multiple color themes/skins</li>
|
||||
<li>It is possible to disable profiles to completely disconnect them saving data/bandwidth</li>
|
||||
<li>You can easily see the connection status of all your profiles in the profile switcher</li>
|
||||
<li>Extra option to share location for 12 hours</li>
|
||||
<li>Extra option to share location for 24 hours</li>
|
||||
<li>Clicking on a message with a POI location, will open the POI on the map</li>
|
||||
<li>Last-seen status of contacts is shown in your contact list, like in WhatsApp, Telegram, etc.</li>
|
||||
<li>Videos are played in loop, useful for short GIF videos</li>
|
||||
|
||||
Generated
+15
-15
@@ -7,11 +7,11 @@
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1756239746,
|
||||
"narHash": "sha256-0ibN685tT+u/Nbmbrrq9G3mRUzct2Votyv/a7Wwv26s=",
|
||||
"lastModified": 1775939535,
|
||||
"narHash": "sha256-Zq1U7Vhw5ctvhJQy+3ShqIv7Mplf3XkgJY+QJnhfUGQ=",
|
||||
"owner": "tadfisher",
|
||||
"repo": "android-nixpkgs",
|
||||
"rev": "256631d162ec883b2341ee59621516e1f65f0f6b",
|
||||
"rev": "d2e16192309bf3101e4998ffa62e914819dee077",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -28,11 +28,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1741473158,
|
||||
"narHash": "sha256-kWNaq6wQUbUMlPgw8Y+9/9wP0F8SHkjy24/mN3UAppg=",
|
||||
"lastModified": 1768818222,
|
||||
"narHash": "sha256-460jc0+CZfyaO8+w8JNtlClB2n4ui1RbHfPTLkpwhU8=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "7c9e793ebe66bcba8292989a68c0419b737a22a0",
|
||||
"rev": "255a2b1725a20d060f566e4755dbf571bbbb5f76",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -79,11 +79,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1756125398,
|
||||
"narHash": "sha256-XexyKZpf46cMiO5Vbj+dWSAXOnr285GHsMch8FBoHbc=",
|
||||
"lastModified": 1775710090,
|
||||
"narHash": "sha256-ar3rofg+awPB8QXDaFJhJ2jJhu+KqN/PRCXeyuXR76E=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3b9f00d7a7bf68acd4c4abb9d43695afb04e03a5",
|
||||
"rev": "4c1018dae018162ec878d42fec712642d214fdfa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -95,11 +95,11 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1756159630,
|
||||
"narHash": "sha256-ohMvsjtSVdT/bruXf5ClBh8ZYXRmD4krmjKrXhEvwMg=",
|
||||
"lastModified": 1775823930,
|
||||
"narHash": "sha256-ALT447J7FcxP/97J01A/gp/hgdO5lXRsm+zLMt+gIjc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "84c256e42600cb0fdf25763b48d28df2f25a0c8b",
|
||||
"rev": "8c11f88bb9573a10a7d6bf87161ef08455ac70b9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -138,11 +138,11 @@
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763347184,
|
||||
"narHash": "sha256-6QH8hpCYJxifvyHEYg+Da0BotUn03BwLIvYo3JAxuqQ=",
|
||||
"lastModified": 1775877051,
|
||||
"narHash": "sha256-wpSQm2PD/w4uRo2wb8utk0b5hOBkkg/CZ1xICY+qB7M=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "08895cce80433978d5bfd668efa41c5e24578cbd",
|
||||
"rev": "08b4f3633471874c8894632ade1b78d75dbda002",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
+1
-21
@@ -946,18 +946,6 @@ JNIEXPORT jstring Java_com_b44t_messenger_DcContext_getContactEncrInfo(JNIEnv *e
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jstring Java_com_b44t_messenger_DcContext_initiateKeyTransfer(JNIEnv *env, jobject obj)
|
||||
{
|
||||
jstring setup_code = NULL;
|
||||
char* temp = dc_initiate_key_transfer(get_dc_context(env, obj));
|
||||
if (temp) {
|
||||
setup_code = JSTRING_NEW(temp);
|
||||
dc_str_unref(temp);
|
||||
}
|
||||
return setup_code;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT void Java_com_b44t_messenger_DcContext_imex(JNIEnv *env, jobject obj, jint what, jstring dir)
|
||||
{
|
||||
CHAR_REF(dir);
|
||||
@@ -1405,6 +1393,7 @@ JNIEXPORT jint Java_com_b44t_messenger_DcMsg_getId(JNIEnv *env, jobject obj)
|
||||
return dc_msg_get_id(get_dc_msg(env, obj));
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jstring Java_com_b44t_messenger_DcMsg_getText(JNIEnv *env, jobject obj)
|
||||
{
|
||||
char* temp = dc_msg_get_text(get_dc_msg(env, obj));
|
||||
@@ -1630,15 +1619,6 @@ JNIEXPORT jboolean Java_com_b44t_messenger_DcMsg_hasHtml(JNIEnv *env, jobject ob
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jstring Java_com_b44t_messenger_DcMsg_getSetupCodeBegin(JNIEnv *env, jobject obj)
|
||||
{
|
||||
char* temp = dc_msg_get_setupcodebegin(get_dc_msg(env, obj));
|
||||
jstring ret = JSTRING_NEW(temp);
|
||||
dc_str_unref(temp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT void Java_com_b44t_messenger_DcMsg_setSubject(JNIEnv *env, jobject obj, jstring text)
|
||||
{
|
||||
CHAR_REF(text);
|
||||
|
||||
+1
-1
Submodule jni/deltachat-core-rust updated: 7ad67cfa56...570119830f
Vendored
+5
@@ -13,3 +13,8 @@
|
||||
-keep class org.thoughtcrime.securesms.crypto.KeyStoreHelper* { *; }
|
||||
|
||||
-dontwarn com.google.firebase.analytics.connector.AnalyticsConnector
|
||||
|
||||
# Keep WebRTC classes
|
||||
-keep class org.webrtc.** { *; }
|
||||
-keepclassmembers class org.webrtc.** { *; }
|
||||
-keepattributes InnerClasses
|
||||
|
||||
@@ -9,3 +9,4 @@ cd "$ROOT_DIR"
|
||||
|
||||
# generate code
|
||||
dcrpcgen java --schema schema.json -o ./src/main/java/
|
||||
rm schema.json
|
||||
|
||||
@@ -34,6 +34,7 @@ cd ../..
|
||||
SYMBOLS_ZIP="$APK-symbols.zip"
|
||||
rm $SYMBOLS_ZIP
|
||||
zip -r $SYMBOLS_ZIP obj
|
||||
zip $SYMBOLS_ZIP build/outputs/mapping/gplayRelease/mapping.txt
|
||||
ls -l $SYMBOLS_ZIP
|
||||
rsync --progress $SYMBOLS_ZIP jekyll@download.delta.chat:/var/www/html/download/android/symbols/
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
eclipse.preferences.version=1
|
||||
lineWidth=100
|
||||
splitMultiAttrs=true
|
||||
indentMultipleAttributes=true
|
||||
indentationChar=space
|
||||
indentationSize=4
|
||||
spaceBeforeEmptyCloseTag=true
|
||||
@@ -11,7 +11,6 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.espresso.NoMatchingViewException;
|
||||
import androidx.test.espresso.UiController;
|
||||
@@ -19,7 +18,6 @@ import androidx.test.espresso.ViewAction;
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.espresso.util.TreeIterables;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.thoughtcrime.securesms.ConversationListActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
@@ -60,10 +58,12 @@ public class TestUtils {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ActivityScenarioRule<ConversationListActivity> getOfflineActivityRule(boolean useExistingChats) {
|
||||
public static ActivityScenarioRule<ConversationListActivity> getOfflineActivityRule(
|
||||
boolean useExistingChats) {
|
||||
Intent intent =
|
||||
Intent.makeMainActivity(
|
||||
new ComponentName(getInstrumentation().getTargetContext(), ConversationListActivity.class));
|
||||
new ComponentName(
|
||||
getInstrumentation().getTargetContext(), ConversationListActivity.class));
|
||||
if (!useExistingChats) {
|
||||
createOfflineAccount();
|
||||
}
|
||||
@@ -72,18 +72,22 @@ public class TestUtils {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static <T extends Activity> ActivityScenarioRule<T> getOnlineActivityRule(Class<T> activityClass) {
|
||||
public static <T extends Activity> ActivityScenarioRule<T> getOnlineActivityRule(
|
||||
Class<T> activityClass) {
|
||||
Context context = getInstrumentation().getTargetContext();
|
||||
AccountManager.getInstance().beginAccountCreation(context);
|
||||
prepare();
|
||||
return new ActivityScenarioRule<>(new Intent(getInstrumentation().getTargetContext(), activityClass));
|
||||
return new ActivityScenarioRule<>(
|
||||
new Intent(getInstrumentation().getTargetContext(), activityClass));
|
||||
}
|
||||
|
||||
private static void prepare() {
|
||||
Prefs.setBooleanPreference(getInstrumentation().getTargetContext(), Prefs.DOZE_ASKED_DIRECTLY, true);
|
||||
Prefs.setBooleanPreference(
|
||||
getInstrumentation().getTargetContext(), Prefs.DOZE_ASKED_DIRECTLY, true);
|
||||
if (!AccessibilityUtil.areAnimationsDisabled(getInstrumentation().getTargetContext())) {
|
||||
throw new RuntimeException("To run the tests, disable animations at Developer options' " +
|
||||
"-> 'Window/Transition/Animator animation scale' -> Set all 3 to 'off'");
|
||||
throw new RuntimeException(
|
||||
"To run the tests, disable animations at Developer options' "
|
||||
+ "-> 'Window/Transition/Animator animation scale' -> Set all 3 to 'off'");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,18 +128,14 @@ public class TestUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform action of implicitly waiting for a certain view.
|
||||
* This differs from EspressoExtensions.searchFor in that,
|
||||
* upon failure to locate an element, it will fetch a new root view
|
||||
* in which to traverse searching for our @param match
|
||||
* Perform action of implicitly waiting for a certain view. This differs from
|
||||
* EspressoExtensions.searchFor in that, upon failure to locate an element, it will fetch a new
|
||||
* root view in which to traverse searching for our @param match
|
||||
*
|
||||
* @param viewMatcher ViewMatcher used to find our view
|
||||
*/
|
||||
public static ViewInteraction waitForView(
|
||||
Matcher<View> viewMatcher,
|
||||
int waitMillis,
|
||||
int waitMillisPerTry
|
||||
) {
|
||||
Matcher<View> viewMatcher, int waitMillis, int waitMillisPerTry) {
|
||||
|
||||
// Derive the max tries
|
||||
int maxTries = (int) (waitMillis / waitMillisPerTry);
|
||||
@@ -164,12 +164,11 @@ public class TestUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Normally, you would do
|
||||
* onView(withId(R.id.send_button)).perform(click());
|
||||
* to send the draft message. However, in order to change the send button to the attach button
|
||||
* while there is no draft, the send button is made invisible and the attach button is made
|
||||
* visible instead. This confuses the test framework.<br/><br/>
|
||||
*
|
||||
* Normally, you would do onView(withId(R.id.send_button)).perform(click()); to send the draft
|
||||
* message. However, in order to change the send button to the attach button while there is no
|
||||
* draft, the send button is made invisible and the attach button is made visible instead. This
|
||||
* confuses the test framework.<br>
|
||||
* <br>
|
||||
* So, this is a workaround for pressing the send button.
|
||||
*/
|
||||
public static void pressSend() {
|
||||
|
||||
@@ -10,14 +10,11 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
|
||||
import com.b44t.messenger.TestUtils;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
@@ -34,18 +31,19 @@ public class EnterChatsBenchmark {
|
||||
// ==============================================================================================
|
||||
// Set this to true if you already have at least 10 chats on your existing DeltaChat installation
|
||||
// and want to traverse through them instead of 10 newly created chats
|
||||
private final static boolean USE_EXISTING_CHATS = false;
|
||||
private static final boolean USE_EXISTING_CHATS = false;
|
||||
// ==============================================================================================
|
||||
private final static int GO_THROUGH_ALL_CHATS_N_TIMES = 8;
|
||||
private static final int GO_THROUGH_ALL_CHATS_N_TIMES = 8;
|
||||
|
||||
// ==============================================================================================
|
||||
// PLEASE BACKUP YOUR ACCOUNT BEFORE RUNNING THIS!
|
||||
// ==============================================================================================
|
||||
|
||||
private final static String TAG = EnterChatsBenchmark.class.getSimpleName();
|
||||
private static final String TAG = "EnterChatsBenchmark";
|
||||
|
||||
@Rule
|
||||
public ActivityScenarioRule<ConversationListActivity> activityRule = TestUtils.getOfflineActivityRule(USE_EXISTING_CHATS);
|
||||
public ActivityScenarioRule<ConversationListActivity> activityRule =
|
||||
TestUtils.getOfflineActivityRule(USE_EXISTING_CHATS);
|
||||
|
||||
@Test
|
||||
public void createAndEnter10FilledChats() {
|
||||
@@ -55,7 +53,9 @@ public class EnterChatsBenchmark {
|
||||
for (int i = 0; i < GO_THROUGH_ALL_CHATS_N_TIMES; i++) {
|
||||
times[i] = "" + timeGoToNChats(10); // 10 group chats were created
|
||||
}
|
||||
Log.i(TAG, "MEASURED RESULTS (Benchmark) - Going thorough all 10 chats: " + String.join(",", times));
|
||||
Log.i(
|
||||
TAG,
|
||||
"MEASURED RESULTS (Benchmark) - Going thorough all 10 chats: " + String.join(",", times));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -66,13 +66,17 @@ public class EnterChatsBenchmark {
|
||||
for (int i = 0; i < GO_THROUGH_ALL_CHATS_N_TIMES; i++) {
|
||||
times[i] = "" + timeGoToNChats(1);
|
||||
}
|
||||
Log.i(TAG, "MEASURED RESULTS (Benchmark) - Entering and leaving 1 empty chat: " + String.join(",", times));
|
||||
Log.i(
|
||||
TAG,
|
||||
"MEASURED RESULTS (Benchmark) - Entering and leaving 1 empty chat: "
|
||||
+ String.join(",", times));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enterFilledChat() {
|
||||
if (!USE_EXISTING_CHATS) {
|
||||
createChatAndGoBack("Group #1", true, "Hello!", "Some links: https://testrun.org", "And a command: /help");
|
||||
createChatAndGoBack(
|
||||
"Group #1", true, "Hello!", "Some links: https://testrun.org", "And a command: /help");
|
||||
}
|
||||
|
||||
String[] times = new String[50];
|
||||
@@ -82,7 +86,18 @@ public class EnterChatsBenchmark {
|
||||
long end = System.currentTimeMillis();
|
||||
long diff = end - start;
|
||||
pressBack();
|
||||
Log.i(TAG, "Measured (Benchmark) " + (i+1) + "/" + times.length + ": Entering 1 filled chat took " + diff + "ms " + "(going back took " + (System.currentTimeMillis() - end) + "ms)");
|
||||
Log.i(
|
||||
TAG,
|
||||
"Measured (Benchmark) "
|
||||
+ (i + 1)
|
||||
+ "/"
|
||||
+ times.length
|
||||
+ ": Entering 1 filled chat took "
|
||||
+ diff
|
||||
+ "ms "
|
||||
+ "(going back took "
|
||||
+ (System.currentTimeMillis() - end)
|
||||
+ "ms)");
|
||||
|
||||
times[i] = "" + diff;
|
||||
}
|
||||
@@ -91,16 +106,37 @@ public class EnterChatsBenchmark {
|
||||
|
||||
private void create10Chats(boolean fillWithMsgs) {
|
||||
if (!USE_EXISTING_CHATS) {
|
||||
createChatAndGoBack("Group #1", fillWithMsgs, "Hello!", "Some links: https://testrun.org", "And a command: /help");
|
||||
createChatAndGoBack("Group #2", fillWithMsgs, "example.org, alice@example.org", "aaaaaaa", "bbbbbb");
|
||||
createChatAndGoBack("Group #3", fillWithMsgs, repeat("Some string ", 600), repeat("Another string", 200), "Hi!!!");
|
||||
createChatAndGoBack(
|
||||
"Group #1",
|
||||
fillWithMsgs,
|
||||
"Hello!",
|
||||
"Some links: https://testrun.org",
|
||||
"And a command: /help");
|
||||
createChatAndGoBack(
|
||||
"Group #2", fillWithMsgs, "example.org, alice@example.org", "aaaaaaa", "bbbbbb");
|
||||
createChatAndGoBack(
|
||||
"Group #3",
|
||||
fillWithMsgs,
|
||||
repeat("Some string ", 600),
|
||||
repeat("Another string", 200),
|
||||
"Hi!!!");
|
||||
createChatAndGoBack("Group #4", fillWithMsgs, "xyzabc", "Hi!!!!", "Let's meet!");
|
||||
createChatAndGoBack("Group #5", fillWithMsgs, repeat("aaaa", 40), "bbbbbbbbbbbbbbbbbb", "ccccccccccccccc");
|
||||
createChatAndGoBack("Group #6", fillWithMsgs, "aaaaaaaaaaa", repeat("Hi! ", 1000), "bbbbbbbbbb");
|
||||
createChatAndGoBack("Group #7", fillWithMsgs, repeat("abcdefg ", 500), repeat("xxxxx", 100), "yrrrrrrrrrrrrr");
|
||||
createChatAndGoBack("Group #8", fillWithMsgs, "and a number: 037362/384756", "ccccc", "Nice!");
|
||||
createChatAndGoBack("Group #9", fillWithMsgs, "ddddddddddddddddd", "zuuuuuuuuuuuuuuuu", "ccccc");
|
||||
createChatAndGoBack("Group #10", fillWithMsgs, repeat("xxxxxxyyyyy", 100), repeat("String!!", 10), "abcd");
|
||||
createChatAndGoBack(
|
||||
"Group #5", fillWithMsgs, repeat("aaaa", 40), "bbbbbbbbbbbbbbbbbb", "ccccccccccccccc");
|
||||
createChatAndGoBack(
|
||||
"Group #6", fillWithMsgs, "aaaaaaaaaaa", repeat("Hi! ", 1000), "bbbbbbbbbb");
|
||||
createChatAndGoBack(
|
||||
"Group #7",
|
||||
fillWithMsgs,
|
||||
repeat("abcdefg ", 500),
|
||||
repeat("xxxxx", 100),
|
||||
"yrrrrrrrrrrrrr");
|
||||
createChatAndGoBack(
|
||||
"Group #8", fillWithMsgs, "and a number: 037362/384756", "ccccc", "Nice!");
|
||||
createChatAndGoBack(
|
||||
"Group #9", fillWithMsgs, "ddddddddddddddddd", "zuuuuuuuuuuuuuuuu", "ccccc");
|
||||
createChatAndGoBack(
|
||||
"Group #10", fillWithMsgs, repeat("xxxxxxyyyyy", 100), repeat("String!!", 10), "abcd");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,10 +166,10 @@ public class EnterChatsBenchmark {
|
||||
onView(withContentDescription(R.string.group_create_button)).perform(click());
|
||||
|
||||
if (fillWithMsgs) {
|
||||
for (String t: texts) {
|
||||
for (String t : texts) {
|
||||
sendText(t);
|
||||
}
|
||||
for (String t: texts) {
|
||||
for (String t : texts) {
|
||||
sendText(t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,10 @@ import androidx.test.espresso.contrib.RecyclerViewActions;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
|
||||
import com.b44t.messenger.DcContact;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.TestUtils;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
@@ -29,8 +28,6 @@ import org.thoughtcrime.securesms.ConversationListActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class ForwardingTest {
|
||||
@@ -43,7 +40,8 @@ public class ForwardingTest {
|
||||
}
|
||||
|
||||
@Rule
|
||||
public final ActivityScenarioRule<ConversationListActivity> activityRule = TestUtils.getOfflineActivityRule(false);
|
||||
public final ActivityScenarioRule<ConversationListActivity> activityRule =
|
||||
TestUtils.getOfflineActivityRule(false);
|
||||
|
||||
@Before
|
||||
public void createChats() {
|
||||
@@ -51,10 +49,13 @@ public class ForwardingTest {
|
||||
dcContext.createChatByContactId(DcContact.DC_CONTACT_ID_SELF);
|
||||
// Disable bcc_self so that DC doesn't try to send messages to the server.
|
||||
// If we didn't do this, messages would stay in DC_STATE_OUT_PENDING forever.
|
||||
// The thing is, DC_STATE_OUT_PENDING show a rotating circle animation, and Espresso doesn't work
|
||||
// The thing is, DC_STATE_OUT_PENDING show a rotating circle animation, and Espresso doesn't
|
||||
// work
|
||||
// with animations, and the tests would hang and never finish.
|
||||
dcContext.setConfig("bcc_self", "0");
|
||||
activityRule.getScenario().onActivity(a -> createdGroupId = DcHelper.getContext(a).createGroupChat( "group"));
|
||||
activityRule
|
||||
.getScenario()
|
||||
.onActivity(a -> createdGroupId = DcHelper.getContext(a).createGroupChat("group"));
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -68,7 +69,8 @@ public class ForwardingTest {
|
||||
// The group is at position 0, self chat is at position 1, device talk is at position 2
|
||||
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(2, click()));
|
||||
onView(withId(R.id.title)).check(matches(withText(R.string.device_talk)));
|
||||
onView(withId(android.R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(0, longClick()));
|
||||
onView(withId(android.R.id.list))
|
||||
.perform(RecyclerViewActions.actionOnItemAtPosition(0, longClick()));
|
||||
onView(withId(R.id.menu_context_forward)).perform(click());
|
||||
// Send it to self chat (which is sorted to the top because we're forwarding)
|
||||
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
|
||||
@@ -77,11 +79,13 @@ public class ForwardingTest {
|
||||
|
||||
pressBack();
|
||||
|
||||
onView(withId(R.id.toolbar_title)).check(matches(withText(R.string.connectivity_not_connected)));
|
||||
onView(withId(R.id.toolbar_title))
|
||||
.check(matches(withText(R.string.connectivity_not_connected)));
|
||||
// Self chat moved up because we sent a message there
|
||||
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
|
||||
onView(withId(R.id.title)).check(matches(withText(R.string.saved_messages)));
|
||||
onView(withId(android.R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(0, longClick()));
|
||||
onView(withId(android.R.id.list))
|
||||
.perform(RecyclerViewActions.actionOnItemAtPosition(0, longClick()));
|
||||
onView(withId(R.id.menu_context_forward)).perform(click());
|
||||
// Send it to the group
|
||||
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(1, click()));
|
||||
@@ -89,6 +93,7 @@ public class ForwardingTest {
|
||||
onView(withId(R.id.title)).check(matches(withText("group")));
|
||||
|
||||
pressBack();
|
||||
onView(withId(R.id.toolbar_title)).check(matches(withText(R.string.connectivity_not_connected)));
|
||||
onView(withId(R.id.toolbar_title))
|
||||
.check(matches(withText(R.string.connectivity_not_connected)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,15 +14,13 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.TestUtils;
|
||||
|
||||
import java.io.File;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -34,9 +32,6 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.ShareActivity;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class SharingTest {
|
||||
@@ -48,16 +43,22 @@ public class SharingTest {
|
||||
private static int createdSingleChatId;
|
||||
|
||||
@Rule
|
||||
public final ActivityScenarioRule<ConversationListActivity> activityRule = TestUtils.getOfflineActivityRule(false);
|
||||
public final ActivityScenarioRule<ConversationListActivity> activityRule =
|
||||
TestUtils.getOfflineActivityRule(false);
|
||||
|
||||
@Before
|
||||
public void createGroup() {
|
||||
activityRule.getScenario().onActivity(a -> createdGroupId = DcHelper.getContext(a).createGroupChat( "group"));
|
||||
activityRule
|
||||
.getScenario()
|
||||
.onActivity(a -> createdGroupId = DcHelper.getContext(a).createGroupChat("group"));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void createSingleChat() {
|
||||
activityRule.getScenario().onActivity(a -> {
|
||||
activityRule
|
||||
.getScenario()
|
||||
.onActivity(
|
||||
a -> {
|
||||
int contactId = DcHelper.getContext(a).createContact("", "abc@example.org");
|
||||
createdSingleChatId = DcHelper.getContext(a).createChatByContactId(contactId);
|
||||
});
|
||||
@@ -67,7 +68,9 @@ public class SharingTest {
|
||||
public void testNormalSharing() {
|
||||
Intent i = new Intent(Intent.ACTION_SEND);
|
||||
i.putExtra(Intent.EXTRA_TEXT, "Hello!");
|
||||
i.setComponent(new ComponentName(getInstrumentation().getTargetContext().getApplicationContext(), ShareActivity.class));
|
||||
i.setComponent(
|
||||
new ComponentName(
|
||||
getInstrumentation().getTargetContext().getApplicationContext(), ShareActivity.class));
|
||||
activityRule.getScenario().onActivity(a -> a.startActivity(i));
|
||||
|
||||
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
|
||||
@@ -77,9 +80,9 @@ public class SharingTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test direct sharing from a screenshot.
|
||||
* Also, this is the regression test for https://github.com/deltachat/deltachat-android/issues/2040
|
||||
* where network changes during sharing lead to a bug
|
||||
* Test direct sharing from a screenshot. Also, this is the regression test for
|
||||
* https://github.com/deltachat/deltachat-android/issues/2040 where network changes during sharing
|
||||
* lead to a bug
|
||||
*/
|
||||
@Test
|
||||
public void testShareFromScreenshot() {
|
||||
@@ -92,20 +95,25 @@ public class SharingTest {
|
||||
pngImage = file;
|
||||
}
|
||||
}
|
||||
Uri uri = Uri.parse("content://" + BuildConfig.APPLICATION_ID + ".attachments/" + Uri.encode(pngImage));
|
||||
Uri uri =
|
||||
Uri.parse(
|
||||
"content://" + BuildConfig.APPLICATION_ID + ".attachments/" + Uri.encode(pngImage));
|
||||
DcHelper.sharedFiles.put(pngImage, "image/png");
|
||||
|
||||
Intent i = new Intent(Intent.ACTION_SEND);
|
||||
i.setType("image/png");
|
||||
i.putExtra(Intent.EXTRA_SUBJECT, "Screenshot (Sep 27, 2021 00:00:00");
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
|
||||
i.setFlags(
|
||||
Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
|
||||
| Intent.FLAG_ACTIVITY_FORWARD_RESULT
|
||||
| Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
|
||||
| Intent.FLAG_RECEIVER_FOREGROUND
|
||||
| Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
i.putExtra(Intent.EXTRA_STREAM, uri);
|
||||
i.putExtra(ShareActivity.EXTRA_CHAT_ID, createdGroupId);
|
||||
i.setComponent(new ComponentName(getInstrumentation().getTargetContext().getApplicationContext(), ShareActivity.class));
|
||||
i.setComponent(
|
||||
new ComponentName(
|
||||
getInstrumentation().getTargetContext().getApplicationContext(), ShareActivity.class));
|
||||
activityRule.getScenario().onActivity(a -> a.startActivity(i));
|
||||
|
||||
TestUtils.waitForView(withId(R.id.send_button), 10000, 50);
|
||||
@@ -121,16 +129,32 @@ public class SharingTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests https://github.com/deltachat/interface/blob/master/user-testing/mailto-links.md#mailto-links:
|
||||
* Tests
|
||||
* https://github.com/deltachat/interface/blob/master/user-testing/mailto-links.md#mailto-links:
|
||||
*
|
||||
* <ul dir="auto">
|
||||
* <li><a href="mailto:abc@example.org">Just an email address</a> - should open a chat with <code>abc@example.org</code> (and maybe ask whether a chat should be created if it does not exist already)</li>
|
||||
* <li><a href="mailto:abc@example.org?subject=testing%20mailto%20uris">email address with subject</a> - should open a chat with <code>abc@example.org</code> and fill <code>testing mailto uris</code>; as we created the chat in the previous step, it should not ask <code>Chat with …</code> but directly open the chat</li>
|
||||
* <li><a href="mailto:abc@example.org?body=this%20is%20a%20test">email address with body</a> - should open a chat with <code>abc@example.org</code>, draft <code>this is a test</code></li>
|
||||
* <li><a href="mailto:abc@example.org?subject=testing%20mailto%20uris&body=this%20is%20a%20test">email address with subject and body</a> - should open a chat with <code>abc@example.org</code>, draft <code>testing mailto uris</code> <newline> <code>this is a test</code></li>
|
||||
* <li><a href="mailto:%20info@example.org">HTML encoding</a> - should open a chat with <code>info@example.org</code></li>
|
||||
* <li><a href="mailto:simplebot@example.org?body=!web%20https%3A%2F%2Fduckduckgo.com%2Flite%3Fq%3Dduck%2520it">more HTML encoding</a> - should open a chat with <code>simplebot@example.org</code>, draft <code>!web https://duckduckgo.com/lite?q=duck%20it</code></li>
|
||||
* <li><a href="mailto:?subject=bla&body=blub">no email, just subject&body</a> - this should let you choose a chat and create a draft <code>bla</code> <newline> <code>blub</code> there</li>
|
||||
* <li><a href="mailto:abc@example.org">Just an email address</a> - should open a chat with
|
||||
* <code>abc@example.org</code> (and maybe ask whether a chat should be created if it does
|
||||
* not exist already)
|
||||
* <li><a href="mailto:abc@example.org?subject=testing%20mailto%20uris">email address with
|
||||
* subject</a> - should open a chat with <code>abc@example.org</code> and fill <code>
|
||||
* testing mailto uris</code>; as we created the chat in the previous step, it should not
|
||||
* ask <code>Chat with …</code> but directly open the chat
|
||||
* <li><a href="mailto:abc@example.org?body=this%20is%20a%20test">email address with body</a> -
|
||||
* should open a chat with <code>abc@example.org</code>, draft <code>this is a test</code>
|
||||
* <li><a
|
||||
* href="mailto:abc@example.org?subject=testing%20mailto%20uris&body=this%20is%20a%20test">email
|
||||
* address with subject and body</a> - should open a chat with <code>abc@example.org</code>,
|
||||
* draft <code>testing mailto uris</code> <newline> <code>this is a test</code>
|
||||
* <li><a href="mailto:%20info@example.org">HTML encoding</a> - should open a chat with <code>
|
||||
* info@example.org</code>
|
||||
* <li><a
|
||||
* href="mailto:simplebot@example.org?body=!web%20https%3A%2F%2Fduckduckgo.com%2Flite%3Fq%3Dduck%2520it">more
|
||||
* HTML encoding</a> - should open a chat with <code>simplebot@example.org</code>, draft
|
||||
* <code>!web https://duckduckgo.com/lite?q=duck%20it</code>
|
||||
* <li><a href="mailto:?subject=bla&body=blub">no email, just subject&body</a> - this
|
||||
* should let you choose a chat and create a draft <code>bla</code> <newline> <code>
|
||||
* blub</code> there
|
||||
* </ul>
|
||||
*/
|
||||
@Test
|
||||
@@ -140,7 +164,8 @@ public class SharingTest {
|
||||
|
||||
openLink("mailto:abc@example.org?subject=testing%20mailto%20uris");
|
||||
onView(withId(R.id.subtitle)).check(matches(withText("abc@example.org")));
|
||||
onView(withHint(R.string.chat_input_placeholder)).check(matches(withText("testing mailto uris")));
|
||||
onView(withHint(R.string.chat_input_placeholder))
|
||||
.check(matches(withText("testing mailto uris")));
|
||||
|
||||
openLink("mailto:abc@example.org?body=this%20is%20a%20test");
|
||||
onView(withId(R.id.subtitle)).check(matches(withText("abc@example.org")));
|
||||
@@ -148,17 +173,22 @@ public class SharingTest {
|
||||
|
||||
openLink("mailto:abc@example.org?subject=testing%20mailto%20uris&body=this%20is%20a%20test");
|
||||
onView(withId(R.id.subtitle)).check(matches(withText("abc@example.org")));
|
||||
onView(withHint(R.string.chat_input_placeholder)).check(matches(withText("testing mailto uris\nthis is a test")));
|
||||
onView(withHint(R.string.chat_input_placeholder))
|
||||
.check(matches(withText("testing mailto uris\nthis is a test")));
|
||||
|
||||
openLink("mailto:%20abc@example.org");
|
||||
onView(withId(R.id.subtitle)).check(matches(withText("abc@example.org")));
|
||||
|
||||
openLink("mailto:abc@example.org?body=!web%20https%3A%2F%2Fduckduckgo.com%2Flite%3Fq%3Dduck%2520it");
|
||||
openLink(
|
||||
"mailto:abc@example.org?body=!web%20https%3A%2F%2Fduckduckgo.com%2Flite%3Fq%3Dduck%2520it");
|
||||
onView(withId(R.id.subtitle)).check(matches(withText("abc@example.org")));
|
||||
onView(withHint(R.string.chat_input_placeholder)).check(matches(withText("!web https://duckduckgo.com/lite?q=duck%20it")));
|
||||
onView(withHint(R.string.chat_input_placeholder))
|
||||
.check(matches(withText("!web https://duckduckgo.com/lite?q=duck%20it")));
|
||||
|
||||
openLink("mailto:?subject=bla&body=blub");
|
||||
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
|
||||
onView(withId(R.id.list))
|
||||
.perform(
|
||||
RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
|
||||
onView(withId(R.id.subtitle)).check(matches(withText("abc@example.org")));
|
||||
onView(withHint(R.string.chat_input_placeholder)).check(matches(withText("bla\nblub")));
|
||||
}
|
||||
@@ -170,49 +200,61 @@ public class SharingTest {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* <ul dir="auto">
|
||||
* <li>Open Saved Messages chat (could be any other chat too)</li>
|
||||
* <li>Go to another app and share some text to DC</li>
|
||||
* <li>In DC select Saved Messages. Edit the shared text if you like. <em>Don't</em> hit the Send button.</li>
|
||||
* <li>Leave DC</li>
|
||||
* <li>Open DC again from the "Recent apps"</li>
|
||||
* <li>Check that your draft is still there</li>
|
||||
* <li>Open Saved Messages chat (could be any other chat too)
|
||||
* <li>Go to another app and share some text to DC
|
||||
* <li>In DC select Saved Messages. Edit the shared text if you like. <em>Don't</em> hit the
|
||||
* Send button.
|
||||
* <li>Leave DC
|
||||
* <li>Open DC again from the "Recent apps"
|
||||
* <li>Check that your draft is still there
|
||||
* </ul>
|
||||
*/
|
||||
@Test
|
||||
public void testOpenAgainFromRecents() {
|
||||
// Open a chat
|
||||
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
|
||||
onView(withId(R.id.list))
|
||||
.perform(
|
||||
RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
|
||||
|
||||
// Share some text to DC
|
||||
Intent i = new Intent(Intent.ACTION_SEND);
|
||||
i.putExtra(Intent.EXTRA_TEXT, "Veeery important draft");
|
||||
i.setComponent(new ComponentName(getInstrumentation().getTargetContext().getApplicationContext(), ShareActivity.class));
|
||||
i.setComponent(
|
||||
new ComponentName(
|
||||
getInstrumentation().getTargetContext().getApplicationContext(), ShareActivity.class));
|
||||
activityRule.getScenario().onActivity(a -> a.startActivity(i));
|
||||
|
||||
// In DC, select the same chat you opened before
|
||||
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
|
||||
onView(withId(R.id.list))
|
||||
.perform(
|
||||
RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
|
||||
|
||||
// Leave DC and go back to the previous activity
|
||||
pressBack();
|
||||
|
||||
// Here, we can't exactly replicate the "steps to reproduce". Previously, the other activity
|
||||
// stayed open in the background, but since it doesn't anymore, we need to open it again:
|
||||
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
|
||||
onView(withId(R.id.list))
|
||||
.perform(
|
||||
RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
|
||||
|
||||
// Check that the draft is still there
|
||||
// Util.sleep(2000); // Uncomment for debugging
|
||||
onView(withHint(R.string.chat_input_placeholder)).check(matches(withText("Veeery important draft")));
|
||||
onView(withHint(R.string.chat_input_placeholder))
|
||||
.check(matches(withText("Veeery important draft")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression test:
|
||||
*
|
||||
* If you save your contacts's emails in the contacts app of the phone, there are buttons to call
|
||||
* them and also to write an email to them.
|
||||
* <p>If you save your contacts's emails in the contacts app of the phone, there are buttons to
|
||||
* call them and also to write an email to them.
|
||||
*
|
||||
* If you click the email button, ArcaneChat opened but instead of opening a chat with that contact,
|
||||
* the chat list was show and "share with" was displayed at the top
|
||||
* <p>If you click the email button, Delta Chat opened but instead of opening a chat with that
|
||||
* contact, the chat list was show and "share with" was displayed at the top
|
||||
*/
|
||||
@Test
|
||||
public void testOpenChatFromContacts() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.b44t.messenger.uitests.online;
|
||||
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.replaceText;
|
||||
@@ -11,13 +10,10 @@ import static androidx.test.espresso.matcher.ViewMatchers.withHint;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
|
||||
import com.b44t.messenger.TestUtils;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
@@ -30,14 +26,16 @@ import org.thoughtcrime.securesms.WelcomeActivity;
|
||||
@LargeTest
|
||||
public class OnboardingTest {
|
||||
@Rule
|
||||
public ActivityScenarioRule<WelcomeActivity> activityRule = TestUtils.getOnlineActivityRule(WelcomeActivity.class);
|
||||
public ActivityScenarioRule<WelcomeActivity> activityRule =
|
||||
TestUtils.getOnlineActivityRule(WelcomeActivity.class);
|
||||
|
||||
@Test
|
||||
public void testAccountCreation() {
|
||||
if (TextUtils.isEmpty(BuildConfig.TEST_ADDR) || TextUtils.isEmpty(BuildConfig.TEST_MAIL_PW)) {
|
||||
throw new RuntimeException("You need to set TEST_ADDR and TEST_MAIL_PW; " +
|
||||
"either in gradle.properties or via an environment variable. " +
|
||||
"See README.md for more details.");
|
||||
throw new RuntimeException(
|
||||
"You need to set TEST_ADDR and TEST_MAIL_PW; "
|
||||
+ "either in gradle.properties or via an environment variable. "
|
||||
+ "See README.md for more details.");
|
||||
}
|
||||
onView(withText(R.string.scan_invitation_code)).check(matches(isClickable()));
|
||||
onView(withText(R.string.import_backup_title)).check(matches(isClickable()));
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="228"
|
||||
android:viewportHeight="280">
|
||||
<group android:scaleX="0.35014287"
|
||||
<group
|
||||
android:scaleX="0.35014287"
|
||||
android:scaleY="0.43"
|
||||
android:translateX="74.08372"
|
||||
android:translateY="79.8">
|
||||
<path
|
||||
android:pathData="m10.03,234.14c0.3,-0.01 0.6,-0.02 0.9,-0.04 -0.07,-0.49 -0.14,-0.97 -0.2,-1.46 -0.15,-1.34 -0.26,-2.69 -0.25,-4.04 -0.02,-0.86 -0.05,-1.71 -0.07,-2.57 -0.09,-0.06 -0.18,-0.13 -0.27,-0.19 -0.02,-0.02 -0.04,-0.03 -0.07,-0.05zM44.87,232.95c11.71,-8.35 26.86,-14.79 46.21,-15.9 0,0 39.93,-0.27 47.91,-3.53 7.98,-3.26 68.68,-14.69 82.94,-98.43 14.26,-83.74 -1.06,-115.09 -1.06,-115.09 0,0 -21.14,55.68 -81.02,59.81 0,0 -14.5,1.03 -38.82,1.42 -24.32,0.39 -75.77,20.65 -90.55,85.62l-0.22,43.44c2.5,4.22 5.49,8.12 8.91,11.66 3.99,4.11 8.11,8.12 12.79,11.45 2.26,1.65 4.65,3.2 6.51,5.33 1.94,2.34 3.33,5 4.2,7.93 0.71,2.1 1.45,4.2 2.2,6.28z"
|
||||
android:fillColor="@color/ic_launcher_background"/>
|
||||
android:fillColor="@color/ic_launcher_background" />
|
||||
<path
|
||||
android:pathData="m217.97,45.86c-0.3,0.01 -0.6,0.02 -0.9,0.04 0.07,0.49 0.14,0.97 0.2,1.46 0.15,1.34 0.26,2.69 0.25,4.04 0.02,0.86 0.05,1.71 0.07,2.57 0.09,0.06 0.18,0.13 0.27,0.19 0.02,0.02 0.04,0.03 0.07,0.05zM183.13,47.05c-11.71,8.35 -26.86,14.79 -46.21,15.9 0,0 -39.93,0.27 -47.91,3.53 -7.98,3.26 -68.68,14.69 -82.94,98.43 -14.26,83.74 1.06,115.09 1.06,115.09 0,0 21.14,-55.68 81.02,-59.81 0,0 14.5,-1.03 38.82,-1.42 24.32,-0.39 75.77,-20.65 90.55,-85.62l0.22,-43.44c-2.5,-4.22 -5.49,-8.12 -8.91,-11.66 -3.99,-4.11 -8.11,-8.12 -12.79,-11.45 -2.26,-1.65 -4.65,-3.2 -6.51,-5.33 -1.94,-2.34 -3.33,-5 -4.2,-7.93 -0.71,-2.1 -1.45,-4.2 -2.2,-6.28z"
|
||||
android:fillColor="@color/ic_launcher_background"/>
|
||||
android:fillColor="@color/ic_launcher_background" />
|
||||
</group>
|
||||
</vector>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/white"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/white" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/white"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/white" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
|
||||
<application>
|
||||
<service
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.thoughtcrime.securesms.geolocation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
/** Non-GMS, always uses the platform LocationManager. */
|
||||
public final class LocationSourceFactory {
|
||||
|
||||
private static final String TAG = "LocationSourceFactory";
|
||||
|
||||
private LocationSourceFactory() {}
|
||||
|
||||
public static LocationSource create(Context context) {
|
||||
Log.i(TAG, "Non-GMS build, Using platform LocationManager");
|
||||
return new PlatformLocationSource();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.thoughtcrime.securesms.notifications;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/*
|
||||
@@ -10,6 +9,11 @@ import androidx.annotation.Nullable;
|
||||
*/
|
||||
public class FcmReceiveService {
|
||||
public static void register(Context context) {}
|
||||
|
||||
public static void waitForRegisterFinished() {}
|
||||
@Nullable public static String getToken() { return null; }
|
||||
|
||||
@Nullable
|
||||
public static String getToken() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- force compiling emojipicker on sdk<21 and firebase on sdk<19; runtime checks are required then -->
|
||||
<uses-sdk tools:overrideLibrary="androidx.emoji2.emojipicker, com.google.firebase.messaging, com.google.android.gms.cloudmessaging"/>
|
||||
|
||||
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
|
||||
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
|
||||
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
|
||||
<meta-data
|
||||
android:name="firebase_analytics_collection_deactivated"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="google_analytics_adid_collection_enabled"
|
||||
android:value="false" />
|
||||
<meta-data
|
||||
android:name="firebase_messaging_auto_init_enabled"
|
||||
android:value="false" />
|
||||
|
||||
<application>
|
||||
<service
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.thoughtcrime.securesms.geolocation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.google.android.gms.location.FusedLocationProviderClient;
|
||||
import com.google.android.gms.location.LocationCallback;
|
||||
import com.google.android.gms.location.LocationRequest;
|
||||
import com.google.android.gms.location.LocationResult;
|
||||
import com.google.android.gms.location.LocationServices;
|
||||
import com.google.android.gms.location.Priority;
|
||||
|
||||
public class GmsLocationSource implements LocationSource {
|
||||
|
||||
private static final String TAG = "GmsLocationSource";
|
||||
private static final long UPDATE_INTERVAL_MS = 3_000;
|
||||
private static final long FASTEST_INTERVAL_MS = 1_000;
|
||||
|
||||
private FusedLocationProviderClient client;
|
||||
private LocationCallback locationCallback;
|
||||
|
||||
@Override
|
||||
public void startUpdates(@NonNull Context context, @NonNull Callback callback) {
|
||||
client = LocationServices.getFusedLocationProviderClient(context);
|
||||
|
||||
LocationRequest request =
|
||||
new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, UPDATE_INTERVAL_MS)
|
||||
.setMinUpdateIntervalMillis(FASTEST_INTERVAL_MS)
|
||||
.setMinUpdateDistanceMeters(0)
|
||||
.setWaitForAccurateLocation(false)
|
||||
.build();
|
||||
|
||||
locationCallback =
|
||||
new LocationCallback() {
|
||||
@Override
|
||||
public void onLocationResult(@NonNull LocationResult result) {
|
||||
Location loc = result.getLastLocation();
|
||||
if (loc != null) {
|
||||
callback.onLocationUpdate(loc);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
client.requestLocationUpdates(request, locationCallback, Looper.getMainLooper());
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Missing location permission", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopUpdates() {
|
||||
if (client != null && locationCallback != null) {
|
||||
client.removeLocationUpdates(locationCallback);
|
||||
client = null;
|
||||
locationCallback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.thoughtcrime.securesms.geolocation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GoogleApiAvailability;
|
||||
|
||||
/**
|
||||
* Prefers FusedLocationProviderClient, falls back to platform LocationManager if Play Services are
|
||||
* somehow unavailable.
|
||||
*/
|
||||
public final class LocationSourceFactory {
|
||||
|
||||
private static final String TAG = "LocationSourceFactory";
|
||||
|
||||
private LocationSourceFactory() {}
|
||||
|
||||
public static LocationSource create(Context context) {
|
||||
if (isGmsAvailable(context)) {
|
||||
Log.i(TAG, "Using FusedLocationProviderClient");
|
||||
return new GmsLocationSource();
|
||||
}
|
||||
Log.i(TAG, "GMS unavailable, falling back to LocationManager");
|
||||
return new PlatformLocationSource();
|
||||
}
|
||||
|
||||
private static boolean isGmsAvailable(Context context) {
|
||||
try {
|
||||
return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
|
||||
== ConnectionResult.SUCCESS;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,23 @@
|
||||
package org.thoughtcrime.securesms.notifications;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.google.android.gms.tasks.Tasks;
|
||||
import com.google.firebase.FirebaseApp;
|
||||
import com.google.firebase.messaging.FirebaseMessaging;
|
||||
import com.google.firebase.messaging.FirebaseMessagingService;
|
||||
import com.google.firebase.messaging.RemoteMessage;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.service.FetchForegroundService;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class FcmReceiveService extends FirebaseMessagingService {
|
||||
private static final String TAG = FcmReceiveService.class.getSimpleName();
|
||||
private static final String TAG = "FcmReceiveService";
|
||||
private static final Object INIT_LOCK = new Object();
|
||||
private static boolean initialized;
|
||||
private static volatile boolean triedRegistering;
|
||||
@@ -35,7 +31,8 @@ public class FcmReceiveService extends FirebaseMessagingService {
|
||||
return;
|
||||
}
|
||||
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
Util.runOnAnyBackgroundThread(
|
||||
() -> {
|
||||
final String rawToken;
|
||||
|
||||
try {
|
||||
@@ -49,7 +46,8 @@ public class FcmReceiveService extends FirebaseMessagingService {
|
||||
}
|
||||
rawToken = Tasks.await(FirebaseMessaging.getInstance().getToken());
|
||||
} catch (Exception e) {
|
||||
// we're here usually when FCM is not available and initializeApp() or getToken() failed.
|
||||
// we're here usually when FCM is not available and initializeApp() or getToken()
|
||||
// failed.
|
||||
Log.w(TAG, "cannot get FCM token for " + BuildConfig.APPLICATION_ID + ": " + e);
|
||||
triedRegistering = true;
|
||||
return;
|
||||
@@ -98,7 +96,8 @@ public class FcmReceiveService extends FirebaseMessagingService {
|
||||
// ForegroundServiceStartNotAllowedException.
|
||||
// So, it's recommended to check the result of RemoteMessage.getPriority() and
|
||||
// confirm it's PRIORITY_HIGH() before attempting to start a foreground service.
|
||||
// source: https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start
|
||||
// source:
|
||||
// https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start
|
||||
if (remoteMessage.getPriority() == RemoteMessage.PRIORITY_HIGH) {
|
||||
FetchForegroundService.start(this);
|
||||
} else {
|
||||
|
||||
+270
-175
@@ -1,27 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.bluetooth" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.location" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.location.network" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.location.gps" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.microphone" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.wifi" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.portrait" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.location"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.network"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.gps"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.microphone"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.wifi"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.portrait"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<!-- dangerous permissions - we need to as the user with a PermissionsRequest -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||
|
||||
<!-- normal/instant permissions - adding them here is enough -->
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
@@ -34,42 +62,47 @@
|
||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||
<uses-permission
|
||||
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" />
|
||||
|
||||
<!-- force compiling emojipicker on sdk<21; runtime checks are required then -->
|
||||
<uses-sdk tools:overrideLibrary="androidx.emoji2.emojipicker"/>
|
||||
<!-- force compiling libs on older sdk than supported; runtime checks are required -->
|
||||
<uses-sdk tools:overrideLibrary="androidx.core.telecom,im.conversations.webrtc" />
|
||||
|
||||
<application android:name=".ApplicationContext"
|
||||
<application
|
||||
android:name=".ApplicationContext"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
tools:replace="android:allowBackup"
|
||||
android:allowBackup="false"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:largeHeap="true"
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
tools:targetApi="TIRAMISU"
|
||||
android:hasFragileUserData="true"
|
||||
>
|
||||
android:hasFragileUserData="true">
|
||||
|
||||
<!-- android car support, see https://developer.android.com/training/auto/start/,
|
||||
as this potentially blocks releases on gplay due to extra-checks,
|
||||
we disable this during the first gplay releases -->
|
||||
<!--
|
||||
<meta-data android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/automotive_app_desc" />
|
||||
-->
|
||||
<!-- android car support, see https://developer.android.com/training/auto/start/, as this potentially
|
||||
blocks releases on gplay due to extra-checks, we disable this during the first gplay releases -->
|
||||
<!-- <meta-data android:name="com.google.android.gms.car.application" android:resource="@xml/automotive_app_desc"
|
||||
/> -->
|
||||
|
||||
<!-- Opt out of metrics collection: https://developer.android.com/develop/ui/views/layout/webapps/managing-webview#metrics -->
|
||||
<meta-data android:name="android.webkit.WebView.MetricsOptOut"
|
||||
<meta-data
|
||||
android:name="android.webkit.WebView.MetricsOptOut"
|
||||
android:value="true" />
|
||||
|
||||
<activity android:name=".ShareActivity"
|
||||
<activity
|
||||
android:name=".ShareActivity"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
@@ -80,22 +113,22 @@
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="audio/*" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="text/plain" />
|
||||
<data android:mimeType="video/*" />
|
||||
<data android:mimeType="application/*"/>
|
||||
<data android:mimeType="text/*"/>
|
||||
<data android:mimeType="*/*"/>
|
||||
<data android:mimeType="application/*" />
|
||||
<data android:mimeType="text/*" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<data android:scheme="mailto"/>
|
||||
<action android:name="android.intent.action.SENDTO"/>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="mailto" />
|
||||
<action android:name="android.intent.action.SENDTO" />
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
@@ -105,9 +138,9 @@
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="text/plain" />
|
||||
<data android:mimeType="video/*" />
|
||||
<data android:mimeType="application/*"/>
|
||||
<data android:mimeType="text/*"/>
|
||||
<data android:mimeType="*/*"/>
|
||||
<data android:mimeType="application/*" />
|
||||
<data android:mimeType="text/*" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.service.chooser.chooser_target_service"
|
||||
@@ -115,14 +148,16 @@
|
||||
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ConversationListActivity"
|
||||
<activity
|
||||
android:name=".ConversationListActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/TextSecure.Splash"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="true" />
|
||||
|
||||
<activity-alias android:name=".RoutingActivity"
|
||||
<activity-alias
|
||||
android:name=".RoutingActivity"
|
||||
android:targetActivity=".ConversationListActivity"
|
||||
android:exported="true">
|
||||
|
||||
@@ -133,18 +168,22 @@
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<!-- Android's scheme matcher is case-sensitive, so include most likely variations -->
|
||||
<data android:scheme="openpgp4fpr" />
|
||||
<data android:scheme="OPENPGP4FPR"
|
||||
<data
|
||||
android:scheme="OPENPGP4FPR"
|
||||
tools:ignore="AppLinkUrlError" />
|
||||
<data android:scheme="OpenPGP4FPR"
|
||||
<data
|
||||
android:scheme="OpenPGP4FPR"
|
||||
tools:ignore="AppLinkUrlError" />
|
||||
<data android:scheme="OpenPGP4Fpr"
|
||||
<data
|
||||
android:scheme="OpenPGP4Fpr"
|
||||
tools:ignore="AppLinkUrlError" />
|
||||
<data android:scheme="OpenPGP4fpr"
|
||||
<data
|
||||
android:scheme="OpenPGP4fpr"
|
||||
tools:ignore="AppLinkUrlError" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -152,13 +191,16 @@
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https"
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="i.delta.chat" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="com.sec.minimode.icon.portrait.normal"
|
||||
<meta-data
|
||||
android:name="com.sec.minimode.icon.portrait.normal"
|
||||
android:resource="@mipmap/ic_launcher" />
|
||||
<meta-data android:name="com.sec.minimode.icon.landscape.normal"
|
||||
<meta-data
|
||||
android:name="com.sec.minimode.icon.landscape.normal"
|
||||
android:resource="@mipmap/ic_launcher" />
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
@@ -166,7 +208,8 @@
|
||||
|
||||
</activity-alias>
|
||||
|
||||
<activity android:name=".ConversationListArchiveActivity"
|
||||
<activity
|
||||
android:name=".ConversationListArchiveActivity"
|
||||
android:label="@string/chat_archived_label"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
@@ -176,105 +219,122 @@
|
||||
android:value="org.thoughtcrime.securesms.ConversationListActivity" />
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ConversationListRelayingActivity"
|
||||
<activity
|
||||
android:name=".ConversationListRelayingActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/TextSecure.LightNoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".ConversationActivity"
|
||||
<activity
|
||||
android:name=".ConversationActivity"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:launchMode="singleTask"
|
||||
android:relinquishTaskIdentity="true"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:parentActivityName=".ConversationListActivity">
|
||||
<!--
|
||||
Without relinquishTaskIdentity="true", we would have the following bug:
|
||||
- Directly open a chat via shortcut (by long-pressing on the DeltaChat icon in the launcher)
|
||||
- Press back twice
|
||||
- Reopen DeltaChat from recent apps
|
||||
- Expected: ConversationList shown. Actual: The chat from the beginning is shown again.
|
||||
-->
|
||||
<!-- Without relinquishTaskIdentity="true", we would have the following bug: - Directly open
|
||||
a chat via shortcut (by long-pressing on the DeltaChat icon in the launcher) - Press back twice - Reopen
|
||||
DeltaChat from recent apps - Expected: ConversationList shown. Actual: The chat from the beginning is
|
||||
shown again. -->
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.thoughtcrime.securesms.ConversationListActivity" />
|
||||
</activity>
|
||||
|
||||
<activity android:name=".GroupCreateActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
<activity
|
||||
android:name=".GroupCreateActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".contacts.NewContactActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
<activity
|
||||
android:name=".contacts.NewContactActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".NewConversationActivity"
|
||||
<activity
|
||||
android:name=".NewConversationActivity"
|
||||
android:theme="@style/TextSecure.LightNoActionBar"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ContactMultiSelectionActivity"
|
||||
<activity
|
||||
android:name=".ContactMultiSelectionActivity"
|
||||
android:label="@string/contacts_title"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".ApplicationPreferencesActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
<activity
|
||||
android:name=".ApplicationPreferencesActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".WelcomeActivity"
|
||||
<activity
|
||||
android:name=".WelcomeActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/TextSecure.LightNoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".relay.EditRelayActivity"
|
||||
<activity
|
||||
android:name=".relay.EditRelayActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".relay.RelayListActivity"
|
||||
<activity
|
||||
android:name=".relay.RelayListActivity"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".proxy.ProxySettingsActivity"
|
||||
<activity
|
||||
android:name=".proxy.ProxySettingsActivity"
|
||||
android:label="@string/proxy_settings"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:exported="true"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<!-- Android's scheme matcher is case-sensitive, so include most likely variations -->
|
||||
<data android:scheme="ss" />
|
||||
<data android:scheme="socks5" />
|
||||
<data android:scheme="SOCKS5" tools:ignore="AppLinkUrlError" />
|
||||
<data android:scheme="SS" tools:ignore="AppLinkUrlError" />
|
||||
<data
|
||||
android:scheme="SOCKS5"
|
||||
tools:ignore="AppLinkUrlError" />
|
||||
<data
|
||||
android:scheme="SS"
|
||||
tools:ignore="AppLinkUrlError" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
|
||||
<activity android:name=".LogViewActivity"
|
||||
<activity
|
||||
android:name=".LogViewActivity"
|
||||
android:label="@string/pref_log_header"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".MediaPreviewActivity"
|
||||
<activity
|
||||
android:name=".MediaPreviewActivity"
|
||||
android:label="@string/media_preview"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".ProfileActivity"
|
||||
<activity
|
||||
android:name=".ProfileActivity"
|
||||
android:theme="@style/TextSecure.LightNoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".AllMediaActivity"
|
||||
<activity
|
||||
android:name=".AllMediaActivity"
|
||||
android:theme="@style/TextSecure.LightNoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".DummyActivity"
|
||||
<activity
|
||||
android:name=".DummyActivity"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
android:enabled="true"
|
||||
android:allowTaskReparenting="true"
|
||||
@@ -285,105 +345,126 @@
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:finishOnTaskLaunch="true" />
|
||||
|
||||
<activity android:name=".AttachContactActivity"
|
||||
<activity
|
||||
android:name=".AttachContactActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".BlockedContactsActivity"
|
||||
<activity
|
||||
android:name=".BlockedContactsActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".scribbles.ScribbleActivity"
|
||||
<activity
|
||||
android:name=".scribbles.ScribbleActivity"
|
||||
android:theme="@style/TextSecure.ScribbleTheme"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".scribbles.StickerSelectActivity"
|
||||
<activity
|
||||
android:name=".scribbles.StickerSelectActivity"
|
||||
android:theme="@style/TextSecure.ScribbleTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".InstantOnboardingActivity"
|
||||
<activity
|
||||
android:name=".InstantOnboardingActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:windowSoftInputMode="stateHidden|adjustResize"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<!-- Android's scheme matcher is case-sensitive, so include most likely variations -->
|
||||
<data android:scheme="DCACCOUNT"
|
||||
<data
|
||||
android:scheme="DCACCOUNT"
|
||||
tools:ignore="AppLinkUrlError" />
|
||||
<data android:scheme="dcaccount"
|
||||
<data
|
||||
android:scheme="dcaccount"
|
||||
tools:ignore="AppLinkUrlError" />
|
||||
<data android:scheme="DCLOGIN"
|
||||
<data
|
||||
android:scheme="DCLOGIN"
|
||||
tools:ignore="AppLinkUrlError" />
|
||||
<data android:scheme="dclogin"
|
||||
<data
|
||||
android:scheme="dclogin"
|
||||
tools:ignore="AppLinkUrlError" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".CreateProfileActivity"
|
||||
<activity
|
||||
android:name=".CreateProfileActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:windowSoftInputMode="stateHidden|adjustResize"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".qr.QrActivity"
|
||||
<activity
|
||||
android:name=".qr.QrActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".qr.QrShowActivity"
|
||||
<activity
|
||||
android:name=".qr.QrShowActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".qr.RegistrationQrActivity"
|
||||
<activity
|
||||
android:name=".qr.RegistrationQrActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".qr.BackupTransferActivity"
|
||||
<activity
|
||||
android:name=".qr.BackupTransferActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".preferences.ChatBackgroundActivity"
|
||||
<activity
|
||||
android:name=".preferences.ChatBackgroundActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".LocalHelpActivity"
|
||||
<activity
|
||||
android:name=".LocalHelpActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".calls.CallActivity"
|
||||
<activity
|
||||
android:name=".calls.CallActivity"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity=".calls.PipCallTask"
|
||||
android:theme="@style/Theme.App.NoActionBar.Fullscreen"
|
||||
android:exported="false"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:excludeFromRecents="true">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".WebxdcActivity"
|
||||
android:label=""
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".WebxdcActivity"
|
||||
android:label=""
|
||||
<activity
|
||||
android:name=".WebxdcStoreActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".WebxdcStoreActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".FullMsgActivity"
|
||||
<activity
|
||||
android:name=".FullMsgActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ConnectivityActivity"
|
||||
<activity
|
||||
android:name=".ConnectivityActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".geolocation.LocationBackgroundService"
|
||||
android:foregroundServiceType="location" />
|
||||
|
||||
<service
|
||||
android:name=".service.GenericForegroundService"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
@@ -392,54 +473,58 @@
|
||||
android:name=".service.FetchForegroundService"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
|
||||
<service
|
||||
android:name=".geolocation.LocationStreamingService"
|
||||
android:foregroundServiceType="location"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".service.AudioPlaybackService"
|
||||
android:foregroundServiceType="mediaPlayback"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.media3.session.MediaSessionService"/>
|
||||
<action android:name="androidx.media3.session.MediaSessionService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver android:name=".notifications.MarkReadReceiver"
|
||||
<service
|
||||
android:name=".calls.CallService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="camera|microphone|phoneCall" />
|
||||
|
||||
<receiver
|
||||
android:name=".notifications.MarkReadReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="org.thoughtcrime.securesms.notifications.MARK_NOTICED"/>
|
||||
<action android:name="org.thoughtcrime.securesms.notifications.CANCEL"/>
|
||||
<action android:name="org.thoughtcrime.securesms.notifications.MARK_NOTICED" />
|
||||
<action android:name="org.thoughtcrime.securesms.notifications.CANCEL" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".notifications.DeclineCallReceiver"
|
||||
<receiver
|
||||
android:name=".notifications.RemoteReplyReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="org.thoughtcrime.securesms.notifications.DECLINE_CALL_NOTICED"/>
|
||||
<action android:name="org.thoughtcrime.securesms.notifications.WEAR_REPLY" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".notifications.RemoteReplyReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="org.thoughtcrime.securesms.notifications.WEAR_REPLY"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<provider android:name="androidx.core.content.FileProvider"
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
|
||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_provider_paths" />
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_provider_paths" />
|
||||
|
||||
<!--
|
||||
tell SafeContentResolver to allow incoming share-file Intent pointing to files from
|
||||
this content resolver, it could be the user sharing the app's log file to a chat.
|
||||
See:
|
||||
https://github.com/deltachat/deltachat-android/pull/3052
|
||||
https://github.com/deltachat/deltachat-android/pull/3031
|
||||
-->
|
||||
<!-- tell SafeContentResolver to allow incoming share-file Intent pointing to files from
|
||||
this content resolver, it could be the user sharing the app's log file to a chat. See: https://github.com/deltachat/deltachat-android/pull/3052
|
||||
https://github.com/deltachat/deltachat-android/pull/3031 -->
|
||||
<meta-data
|
||||
android:name="de.cketti.safecontentresolver.ALLOW_INTERNAL_ACCESS"
|
||||
android:value="true" />
|
||||
@@ -452,22 +537,20 @@
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
|
||||
<!--
|
||||
tell SafeContentResolver to allow incoming share-file Intent pointing to files from
|
||||
this content resolver, it could be the user sharing a file from one chat to another.
|
||||
See: https://github.com/deltachat/deltachat-android/pull/3031
|
||||
-->
|
||||
<!-- tell SafeContentResolver to allow incoming share-file Intent pointing to files from
|
||||
this content resolver, it could be the user sharing a file from one chat to another. See: https://github.com/deltachat/deltachat-android/pull/3031 -->
|
||||
<meta-data
|
||||
android:name="de.cketti.safecontentresolver.ALLOW_INTERNAL_ACCESS"
|
||||
android:value="true" />
|
||||
|
||||
</provider>
|
||||
|
||||
<receiver android:name=".service.BootReceiver"
|
||||
<receiver
|
||||
android:name=".service.BootReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
<action android:name="org.thoughtcrime.securesms.RESTART"/>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="org.thoughtcrime.securesms.RESTART" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
@@ -479,20 +562,32 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false"/>
|
||||
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
||||
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W" android:value="632.0dip" />
|
||||
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H" android:value="598.0dip" />
|
||||
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W" android:value="632.0dip" />
|
||||
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H" android:value="598.0dip" />
|
||||
</application>
|
||||
<uses-library
|
||||
android:name="com.sec.android.app.multiwindow"
|
||||
android:required="false" />
|
||||
<meta-data
|
||||
android:name="com.sec.android.support.multiwindow"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W"
|
||||
android:value="632.0dip" />
|
||||
<meta-data
|
||||
android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H"
|
||||
android:value="598.0dip" />
|
||||
<meta-data
|
||||
android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W"
|
||||
android:value="632.0dip" />
|
||||
<meta-data
|
||||
android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H"
|
||||
android:value="598.0dip" />
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.media.action.IMAGE_CAPTURE" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.TTS_SERVICE" />
|
||||
</intent>
|
||||
</queries>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<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="#podporuje-delta-chat-obrázky-videa-a-jiné-přílohy">Podporuje Delta Chat obrázky, videa a jiné přílohy?</a></li>
|
||||
<li><a href="#multiple-accounts">What are profiles? How can I switch between them?</a></li>
|
||||
<li><a href="#kdo-uvidí-můj-profilový-obrázek">Kdo uvidí můj profilový obrázek?</a></li>
|
||||
<li><a href="#signature">Can I set a Bio/Status with Delta Chat?</a></li>
|
||||
@@ -14,6 +13,7 @@
|
||||
<li><a href="#what-does-the-green-dot-mean">What does the green dot mean?</a></li>
|
||||
<li><a href="#what-do-the-ticks-shown-beside-outgoing-messages-mean">What do the ticks shown beside outgoing messages mean?</a></li>
|
||||
<li><a href="#edit">Correct typos and delete messages after sending</a></li>
|
||||
<li><a href="#mediaquality">How is media quality handled?</a></li>
|
||||
<li><a href="#ephemeralmsgs">How do disappearing messages work?</a></li>
|
||||
<li><a href="#delold">What happens if I turn on “Delete Messages from Device”?</a></li>
|
||||
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
|
||||
@@ -218,19 +218,6 @@ You can also add a little introduction message.</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="podporuje-delta-chat-obrázky-videa-a-jiné-přílohy">
|
||||
|
||||
|
||||
Podporuje Delta Chat obrázky, videa a jiné přílohy? <a href="#podporuje-delta-chat-obrázky-videa-a-jiné-přílohy" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Yes. 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>Attachment-</strong>
|
||||
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
|
||||
|
||||
<p>For performance, images are optimized and sent at a smaller size by default, but you can send it as a “file” to preserve the original.</p>
|
||||
|
||||
<h3 id="multiple-accounts">
|
||||
|
||||
|
||||
@@ -412,6 +399,32 @@ Notifications are not sent and there is no time limit.</p>
|
||||
<p>Note, that the original message may still be received by chat members
|
||||
who could have already replied, forwarded, saved, screenshotted or otherwise copied the message.</p>
|
||||
|
||||
<h3 id="mediaquality">
|
||||
|
||||
|
||||
How is media quality handled? <a href="#mediaquality" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<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>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>In regions with worse connectivity,
|
||||
you can choose higher compression at <strong>Settings → Chats → Outgoing Media Quality</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<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>
|
||||
|
||||
<h3 id="ephemeralmsgs">
|
||||
|
||||
|
||||
@@ -1030,22 +1043,26 @@ Relays are operated by different groups and people.</p>
|
||||
<p>By default, after installation, a relay is <strong>automatically set up</strong>,
|
||||
so you do not need to care about that.
|
||||
However, if you want to,
|
||||
you can configure relays at At <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
you can configure relays at <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>You can <strong>add</strong> a relay by scanning its QR code;
|
||||
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.</p>
|
||||
<a href="https://chatmail.at/relays">chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.
|
||||
Contacts learn your current relays automatically when you message them.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
|
||||
<p>Tap on a relay to set it as <strong>used for sending</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If you <strong>remove</strong> a relay,
|
||||
make sure another default relay was used for a sufficient amount of time.
|
||||
Otherwise, messages from your chat partners won’t reach you.
|
||||
If in doubt, remove later.</p>
|
||||
contacts who only know this relay may not reach you until you message them again.
|
||||
To stay reachable in the meantime, choose <strong>Hide from Contacts</strong> in the confirmation dialog
|
||||
instead of removing it right away.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>To <strong>show</strong> a hidden relay again, tap on it.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1561,7 +1578,7 @@ See <a href="https://delta.chat/en/2023-05-22-webxdc-security">here for the full
|
||||
<li>
|
||||
<p>2023 March, <a href="https://cure53.de">Cure53</a> analyzed both the transport encryption of
|
||||
Delta Chat’s network connections and a reproducible mail server setup as
|
||||
<a href="https://delta.chat/cs/serverguide">recommended on this site</a>.
|
||||
<a href="https://delta.chat/serverguide">recommended on this site</a>.
|
||||
You can read more about the audit <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">on our blog</a>
|
||||
or read the <a href="https://delta.chat/assets/blog/MER-01-report.pdf">full report here</a>.</p>
|
||||
</li>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<li><a href="#howtoe2ee">Wie finde ich Leute, mit denen ich chatten kann?</a></li>
|
||||
<li><a href="#warum-ist-ein-chat-als-anfrage-markiert">Warum ist ein Chat als “Anfrage” markiert?</a></li>
|
||||
<li><a href="#wie-kann-ich-zwei-meiner-freunde-miteinander-in-kontakt-bringen">Wie kann ich zwei meiner Freunde miteinander in Kontakt bringen?</a></li>
|
||||
<li><a href="#unterstützt-delta-chat-bilder-videos-und-dateianhänge">Unterstützt Delta Chat Bilder, Videos und Dateianhänge?</a></li>
|
||||
<li><a href="#multiple-accounts">Was sind Profile? Wie kann ich zwischen ihnen wechseln?</a></li>
|
||||
<li><a href="#wer-sieht-mein-profilbild">Wer sieht mein Profilbild?</a></li>
|
||||
<li><a href="#signature">Kann ich einen Status festlegen?</a></li>
|
||||
@@ -14,6 +13,7 @@
|
||||
<li><a href="#was-bedeutet-der-grüne-punkt">Was bedeutet der grüne Punkt?</a></li>
|
||||
<li><a href="#was-bedeuten-die-häkchen-neben-den-ausgehenden-nachrichten">Was bedeuten die Häkchen neben den ausgehenden Nachrichten?</a></li>
|
||||
<li><a href="#edit">Schreibfehler korrigieren und Nachrichten nach dem Senden löschen</a></li>
|
||||
<li><a href="#mediaquality">Medienqualität und Datenverbrauch</a></li>
|
||||
<li><a href="#ephemeralmsgs">Wie funktionieren “Verschwindende Nachrichten”?</a></li>
|
||||
<li><a href="#delold">Was passiert, wenn ich “Nachrichten vom Gerät löschen” aktiviere?</a></li>
|
||||
<li><a href="#remove-account">Wie kann ich mein Chat-Profil löschen?</a></li>
|
||||
@@ -109,7 +109,7 @@
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Einfache Erstellung von <strong>privaten Chat-Profile</strong> mit sicheren, schnellen und interoperablen <a href="https://chatmail.at/relays">Chatmail-Servern</a>,
|
||||
<p>Einfache Erstellung von <strong>privaten Chat-Profilen</strong> mit sicheren, schnellen und interoperablen <a href="https://chatmail.at/relays">Chatmail-Servern</a>,
|
||||
die sofortige Push-Benachrichtigungen für iOS- und Android-Geräte bieten.</p>
|
||||
</li>
|
||||
<li>
|
||||
@@ -137,17 +137,17 @@ basierend auf <a href="https://github.com/chatmail/core/blob/main/standards.md#s
|
||||
</h3>
|
||||
|
||||
<p>Beachte zunächst, dass Delta Chat ein privater Messenger ist.
|
||||
Es gibt keine öffentliches Verzeichnis, du entscheiden selbst über deine Kontakte.</p>
|
||||
Es gibt kein öffentliches Verzeichnis, du entscheidest selbst über deine Kontakte.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Wenn du <strong>persönlich</strong> mit deinen Freunden oder Familie zusammen bist,
|
||||
tippe auf das <strong>QR-Code</strong>-Symbol <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" />
|
||||
auf dem Hauptbildschirm.<br />
|
||||
Bitte deinen Chatpartner den QR-Code mit Delta Chat zu <strong>scannen</strong>.</p>
|
||||
Bitte dann deinen Chatpartner den QR-Code mit Delta Chat zu <strong>scannen</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Für eine Kontaktaufnahme <strong>aus der Ferne</strong>, klicke im selben Bildschirm auf “Kopieren” oder “Teilen” und sende den <strong>Einladungslink</strong> über einen anderen privaten Chat.</p>
|
||||
<p>Für eine Kontaktaufnahme <strong>aus der Ferne</strong> klicke im selben Bildschirm auf “Kopieren” oder “Teilen” und sende den <strong>Einladungslink</strong> über einen anderen privaten Chat.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -177,7 +177,7 @@ wird eine Ende-zu-Ende-Verschlüsselung zwischen allen Mitgliedern eingerichtet.
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Da Delta Chat ein privater Messenger ist, können dir zunächst nur Freunde und Familienmitglieder, denen du deinen <a href="#howtoe2ee">QR-Code oder Einladungslink</a> schickst, schreiben.</p>
|
||||
<p>Da Delta Chat ein privater Messenger ist, können dir zunächst nur Freunde und Familienmitglieder schreiben, denen du deinen <a href="#howtoe2ee">QR-Code oder Einladungslink</a> schickst.</p>
|
||||
|
||||
<p>Deine Freunde können deine Kontaktdaten dann mit anderen Freunden teilen. Dies wird als <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Anfrage</b> angezeigt.</p>
|
||||
|
||||
@@ -186,12 +186,12 @@ wird eine Ende-zu-Ende-Verschlüsselung zwischen allen Mitgliedern eingerichtet.
|
||||
<p>Du musst die Anfrage <strong>akzeptieren</strong>, bevor du antworten kannst.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Du kannst sie auch “löschen”, wenn du vorerst nicht mit ihm chatten möchten.</p>
|
||||
<p>Du kannst sie auch “löschen”, wenn du vorerst nicht mit dieser Person chatten möchtest.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If you delete a request, future messages from that contact will still appear
|
||||
as message request, so you can change your mind. If you really don’t want to
|
||||
receive messages from this person, consider <strong>blocking</strong> them.</p>
|
||||
<p>Wenn du eine Anfrage löschst, werden zukünftige Nachrichten von diesem Kontakt weiterhin
|
||||
als Nachrichtenanfrage angezeigt, sodass du deine Meinung ändern kannst. Wenn du wirklich keine
|
||||
Nachrichten von dieser Person erhalten möchtest, solltest du sie <strong>blockieren</strong>.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -209,19 +209,6 @@ Du kannst auch eine kurze Nachricht hinzufügen.</p>
|
||||
<p>Der zweite Kontakt erhält dann die <strong>Kontaktdaten</strong> und
|
||||
kann darauf tippen, um mit dem ersten Kontakt zu chatten.</p>
|
||||
|
||||
<h3 id="unterstützt-delta-chat-bilder-videos-und-dateianhänge">
|
||||
|
||||
|
||||
Unterstützt Delta Chat Bilder, Videos und Dateianhänge? <a href="#unterstützt-delta-chat-bilder-videos-und-dateianhänge" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Ja. Bilder, Videos, Dateien, Sprachnachrichten und mehr können über die <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Anhang-</strong>
|
||||
bzw. <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Sprachnachricht</strong>-Buttons hinzugefügt werden</p>
|
||||
|
||||
<p>Um die Leistung zu verbessern, werden die Bilder standardmäßig optimiert und in einer kleineren Größe gesendet, aber du kannst sie auch als “Datei” senden, um das Original zu erhalten.</p>
|
||||
|
||||
<h3 id="multiple-accounts">
|
||||
|
||||
|
||||
@@ -241,7 +228,7 @@ oder <strong>Profile zu wechseln</strong>.</p>
|
||||
|
||||
<p>Du kannst separate Profile für politische, familiäre oder berufliche Aktivitäten verwenden.</p>
|
||||
|
||||
<p>Vielleicht möchtest due auch erfahren, wie du <a href="#multiclient">Profile auf mehreren Geräten verwenden kannst</a>.</p>
|
||||
<p>Vielleicht möchtest du auch erfahren, wie du <a href="#multiclient">Profile auf mehreren Geräten verwenden kannst</a>.</p>
|
||||
|
||||
<h3 id="wer-sieht-mein-profilbild">
|
||||
|
||||
@@ -279,7 +266,7 @@ Sobald du eine Nachricht an einen Kontakt sendest, kann dieser deine Signatur in
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p><strong>Angeheftete Chats</strong> bleiben immer ganz oben in der Chatliste. So kannst du schnell auf deine Lieblingschats zugreifen oder du verwendest vorübergehend angeheftete Chats um Dinge nicht zu vergessen.</p>
|
||||
<p><strong>Angeheftete Chats</strong> bleiben immer ganz oben in der Chatliste. So kannst du schnell auf deine Lieblingschats zugreifen oder du verwendest vorübergehend angeheftete Chats, um Dinge nicht zu vergessen.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Stummgeschaltete Chats</strong> erhalten keine Benachrichtigungen, bleiben ansonsten aber an ihrem Platz. Du kannst auch stummgeschaltete Chats anheften.</p>
|
||||
@@ -292,7 +279,7 @@ Sobald du eine Nachricht an einen Kontakt sendest, kann dieser deine Signatur in
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Um die Funktionen zu nutzen, lang auf einen Chat in der Chatliste tippen oder den Chat mit der rechten Maustaste anklicken.</p>
|
||||
<p>Um die Funktionen zu nutzen, tippe lang auf einen Chat in der Chatliste oder klicke den Chat mit der rechten Maustaste an.</p>
|
||||
|
||||
<h3 id="save">
|
||||
|
||||
@@ -302,11 +289,11 @@ Sobald du eine Nachricht an einen Kontakt sendest, kann dieser deine Signatur in
|
||||
|
||||
</h3>
|
||||
|
||||
<p><strong>Gespeicherte Nachrichten</strong> ist ein Chat, den du verwenden kannst, um dir Nachrichten zu merken und wiederzufinden.</p>
|
||||
<p><strong>Gespeicherte Nachrichten</strong> ist ein Chat, den du verwenden kannst, um dir Nachrichten zu merken und sie wiederzufinden.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Tippen in einem beliebigen Chat lange auf eine Nachricht oder klicken mit der rechten Maustaste darauf und wähle <strong>Speichern</strong>.</p>
|
||||
<p>Tippe im Chat lange auf eine Nachricht oder klicke mit der rechten Maustaste darauf und wähle <strong>Speichern</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Gespeicherte Nachrichten werden mit dem Symbol
|
||||
@@ -319,7 +306,7 @@ Durch Tippen auf <img style="vertical-align:middle; width:1.2em; margin:1px" src
|
||||
kannst du zu der ursprünglichen Nachricht im ursprünglichen Chat zurückkehren</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Schließlich kannst du auch „Gespeicherte Nachrichten“ verwenden, um <strong>persönliche Notizen</strong> zu machen - öffnen den Chat, gib etwas ein, fügen ein Foto oder eine Sprachnachricht hinzu usw.</p>
|
||||
<p>Schließlich kannst du auch „Gespeicherte Nachrichten“ verwenden, um <strong>persönliche Notizen</strong> zu machen. Öffne den Chat, gib etwas ein, fügen ein Foto oder eine Sprachnachricht hinzu usw.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Da „Gespeicherte Nachrichten“ synchronisiert werden, können sie sehr praktisch für die Übertragung von Daten zwischen Geräten sein</p>
|
||||
@@ -360,7 +347,7 @@ sei es durch den <a href="#edit">Absender</a>, durch <a href="#delold">Automatis
|
||||
|
||||
<p>In <a href="#groups">Gruppen</a> bedeutet das zweite Häkchen, dass die Nachricht von mindestens einem Mitglied gelesen wurde.</p>
|
||||
|
||||
<p>Du erhälst nur dann das zweite Häkchen, wenn sowohl du als auch einer der Empfänger, die die Nachricht gelesen haben, <strong>Einstellungen → Chats → Lesebestätigungen</strong> aktiviert haben.</p>
|
||||
<p>Du erhältst nur dann das zweite Häkchen, wenn sowohl du als auch einer der Empfänger, die die Nachricht gelesen haben, <strong>Einstellungen → Chats → Lesebestätigungen</strong> aktiviert haben.</p>
|
||||
|
||||
<h3 id="edit">
|
||||
|
||||
@@ -388,6 +375,31 @@ Es werden keine Benachrichtigungen verschickt und es gibt kein Zeitlimit.</p>
|
||||
<p>Beachten, dass die ursprüngliche Nachricht dennoch von Chatteilnehmern empfangen worden sein könnte,
|
||||
die die Nachricht bereits beantwortet, weitergeleitet, gespeichert, mit einem Screenshot versehen oder anderweitig kopiert haben könnten.</p>
|
||||
|
||||
<h3 id="mediaquality">
|
||||
|
||||
|
||||
Medienqualität und Datenverbrauch <a href="#mediaquality" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Bilder, Videos, Dateien, Sprachnachrichten und mehr können über die <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Anhang-</strong>
|
||||
bzw. <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Sprachnachricht</strong>-Buttons hinzugefügt werden.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Standardmäßig sorgt Komprimierung für eine <strong>schnelle, effiziente Übertragung</strong>, die die Datenlimits und Speicherplatzkapazitäten aller Beteiligten berücksichtigt.
|
||||
Dies ist ideal für die tägliche Kommunikation.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In Regionen mit schlechter Verbindung können Sie unter <strong>Einstellungen → Chats → Medienqualität beim Senden</strong> eine höhere Komprimierung wählen.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Wenn Sie Medien ausdrücklich in ihrer <strong>Originalqualität</strong> senden müssen, verwende im Chat die Option <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Anhängen → Datei</strong>.
|
||||
Verwende diese Methode nur sparsam, da das Senden von Originaldateien den Datenverbrauch für dich und alle Empfänger im Chat erheblich erhöht.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="ephemeralmsgs">
|
||||
|
||||
|
||||
@@ -503,7 +515,7 @@ und seine <a href="#edit">eigenen Nachrichten von Geräten der Mitglieder lösch
|
||||
<p>Wenn das Mitglied noch nicht in deiner Kontaktliste ist, sie sich aber <strong>persönlich</strong> treffen, wählen Sie dort <strong>QR-Einladungscode</strong> an. Dein Chat-Partner kann nun den QR-Code mit seiner Delta Chat-App <strong>scannen</strong> indem er auf <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> auf dem Hauptbildschirm tippt.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Für eine Kontaktaufnahme <strong>aus der Ferne</strong>, tippe dort “Kopieren” oder “Teilen” und sende den Einladungslink über einen anderen privaten Chat zum neuen Mitglied.</p>
|
||||
<p>Für eine Kontaktaufnahme <strong>aus der Ferne</strong> tippe dort “Kopieren” oder “Teilen” und sende den Einladungslink über einen anderen privaten Chat zum neuen Mitglied.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -533,7 +545,7 @@ Kein Problem, bitte einfach ein anderes Gruppenmitglied in einem normalen Chat,
|
||||
Wenn du der Gruppe später erneut beitreten möchtest, bitten ein anderes Gruppenmitglied, dich hinzuzufügen.</li>
|
||||
</ul>
|
||||
|
||||
<p>Alternativ kannst du eine Gruppe auch “stummschalten” - dies bedeutet, dass du weiterhin alle Nachrichten erhälst und neue schreiben kannst, aber nicht mehr über neue Nachrichten informiert wirst.</p>
|
||||
<p>Alternativ kannst du eine Gruppe auch “stummschalten” - dies bedeutet, dass du weiterhin alle Nachrichten erhältst und neue schreiben kannst, aber nicht mehr über neue Nachrichten informiert wirst.</p>
|
||||
|
||||
<h3 id="eine-gruppe-klonen">
|
||||
|
||||
@@ -958,18 +970,16 @@ kannst du jedoch unter <strong>Einstellungen → Erweitert → Relays</strong>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Du kannst ein Relay <strong>hinzufügen</strong>, indem du einen QR-Code scannst,
|
||||
z.B. von <a href="https://chatmail.at/relays">https://chatmail.at/relays</a>.
|
||||
Bei mehreren Relays, empfängst du die Nachrichten von allen Relays.</p>
|
||||
<p>Du kannst ein Relay <strong>hinzufügen</strong>, indem du einen QR-Code scannst, z.B. von <a href="https://chatmail.at/relays">chatmail.at/relays</a>. Bei mehreren Relays, empfängst du die Nachrichten von allen Relays. Deine Kontakte lernen deine Relays automatisch, sobald du ihnen schreibst.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Standard</strong> legt das Relay fest, an das deine Chatpartner zukünftig Nachrichten senden.</p>
|
||||
<p>Tippe ein Relay an, um es <strong>Zum Senden zu verwenden</strong></p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Wenn du ein Relay <strong>entfernst</strong>,
|
||||
stelle sicher, dass ein anderes Standard-Relay ausreichend lange verwendet wurde.
|
||||
Andernfalls erreichen dich keine Nachrichten von deinen Kontakten.
|
||||
Im Zweifelsfall entferne das Relay später.</p>
|
||||
<p>Wenn du ein Relay <strong>entfernst</strong>, können Kontakte, die nur dieses Relay kennen, dich nicht erreichen, bis du ihnen wieder schreibst. Um erreichbar zu bleiben, wähle <strong>Vor Kontakten verstecken</strong> im Bestätigungsdialog anstelle das Relay direkt zu löschen.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Um ein verstecktes Relay wieder <strong>anzuzeigen</strong> tippe es an.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1458,7 +1468,7 @@ Weitere Informationen findest du in unserem Blogbeitrag über <a href="https://d
|
||||
<p>Im April 2023 haben wir Sicherheits- und Datenschutzprobleme mit dem “In Chats geteilten Apps”-Feature behoben, die mit Fehlern beim Sandboxing, insbesondere mit Chromium zusammenhängen. Wir haben daraufhin eine unabhängige Sicherheitsprüfung von Cure53 durchführen lassen, und alle gefundenen Probleme wurden mit den im April 2023 veröffentlichten 1.36 Releases behoben. Siehe <a href="https://delta.chat/en/2023-05-22-webxdc-security">hier für die vollständige Hintergrundgeschichte</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Im März 2023 analysierte <a href="https://cure53.de">Cure53</a> sowohl die Transportverschlüsselung von Delta Chats Netzwerkverbindungen als auch das reproduzierbare Mailserver-Setup wie <a href="https://delta.chat/de/serverguide">auf dieser Seite empfohlen</a>. Du kannst mehr über das Audit <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">in unserem Blog</a> lesen oder du liest den <a href="https://delta.chat/assets/blog/MER-01-report.pdf">vollständigen Bericht hier</a>.</p>
|
||||
<p>Im März 2023 analysierte <a href="https://cure53.de">Cure53</a> sowohl die Transportverschlüsselung von Delta Chats Netzwerkverbindungen als auch das reproduzierbare Mailserver-Setup wie <a href="https://delta.chat/serverguide">auf dieser Seite empfohlen</a>. Du kannst mehr über das Audit <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">in unserem Blog</a> lesen oder du liest den <a href="https://delta.chat/assets/blog/MER-01-report.pdf">vollständigen Bericht hier</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Im Jahr 2020 analysierte <a href="https://includesecurity.com">Include Security</a> Delta Chats Rust <a href="https://github.com/deltachat/deltachat-core-rust/">core</a>, <a href="https://github.com/async-email/async-imap">IMAP</a>,<a href="https://github.com/async-email/async-smtp">SMTP</a>, und <a href="https://github.com/async-email/async-native-tls">TLS</a> Bibliotheken.
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<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="#does-delta-chat-support-images-videos-and-other-attachments">Does Delta Chat support images, videos and other attachments?</a></li>
|
||||
<li><a href="#multiple-accounts">What are profiles? How can I switch between them?</a></li>
|
||||
<li><a href="#who-sees-my-profile-picture">Who sees my profile picture?</a></li>
|
||||
<li><a href="#signature">Can I set a Bio/Status with Delta Chat?</a></li>
|
||||
@@ -14,6 +13,7 @@
|
||||
<li><a href="#what-does-the-green-dot-mean">What does the green dot mean?</a></li>
|
||||
<li><a href="#what-do-the-ticks-shown-beside-outgoing-messages-mean">What do the ticks shown beside outgoing messages mean?</a></li>
|
||||
<li><a href="#edit">Correct typos and delete messages after sending</a></li>
|
||||
<li><a href="#mediaquality">How is media quality handled?</a></li>
|
||||
<li><a href="#ephemeralmsgs">How do disappearing messages work?</a></li>
|
||||
<li><a href="#delold">What happens if I turn on “Delete Messages from Device”?</a></li>
|
||||
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
|
||||
@@ -218,19 +218,6 @@ You can also add a little introduction message.</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="does-delta-chat-support-images-videos-and-other-attachments">
|
||||
|
||||
|
||||
Does Delta Chat support images, videos and other attachments? <a href="#does-delta-chat-support-images-videos-and-other-attachments" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Yes. 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>Attachment-</strong>
|
||||
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
|
||||
|
||||
<p>For performance, images are optimized and sent at a smaller size by default, but you can send it as a “file” to preserve the original.</p>
|
||||
|
||||
<h3 id="multiple-accounts">
|
||||
|
||||
|
||||
@@ -411,6 +398,32 @@ Notifications are not sent and there is no time limit.</p>
|
||||
<p>Note, that the original message may still be received by chat members
|
||||
who could have already replied, forwarded, saved, screenshotted or otherwise copied the message.</p>
|
||||
|
||||
<h3 id="mediaquality">
|
||||
|
||||
|
||||
How is media quality handled? <a href="#mediaquality" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<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>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>In regions with worse connectivity,
|
||||
you can choose higher compression at <strong>Settings → Chats → Outgoing Media Quality</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<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>
|
||||
|
||||
<h3 id="ephemeralmsgs">
|
||||
|
||||
|
||||
@@ -1030,22 +1043,26 @@ Relays are operated by different groups and people.</p>
|
||||
<p>By default, after installation, a relay is <strong>automatically set up</strong>,
|
||||
so you do not need to care about that.
|
||||
However, if you want to,
|
||||
you can configure relays at At <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
you can configure relays at <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>You can <strong>add</strong> a relay by scanning its QR code;
|
||||
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.</p>
|
||||
<a href="https://chatmail.at/relays">chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.
|
||||
Contacts learn your current relays automatically when you message them.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
|
||||
<p>Tap on a relay to set it as <strong>used for sending</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If you <strong>remove</strong> a relay,
|
||||
make sure another default relay was used for a sufficient amount of time.
|
||||
Otherwise, messages from your chat partners won’t reach you.
|
||||
If in doubt, remove later.</p>
|
||||
contacts who only know this relay may not reach you until you message them again.
|
||||
To stay reachable in the meantime, choose <strong>Hide from Contacts</strong> in the confirmation dialog
|
||||
instead of removing it right away.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>To <strong>show</strong> a hidden relay again, tap on it.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1561,7 +1578,7 @@ See <a href="https://delta.chat/en/2023-05-22-webxdc-security">here for the full
|
||||
<li>
|
||||
<p>2023 March, <a href="https://cure53.de">Cure53</a> analyzed both the transport encryption of
|
||||
Delta Chat’s network connections and a reproducible mail server setup as
|
||||
<a href="https://delta.chat/en/serverguide">recommended on this site</a>.
|
||||
<a href="https://delta.chat/serverguide">recommended on this site</a>.
|
||||
You can read more about the audit <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">on our blog</a>
|
||||
or read the <a href="https://delta.chat/assets/blog/MER-01-report.pdf">full report here</a>.</p>
|
||||
</li>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<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="#delta-chat-soporta-envío-de-imágenes-videos-documentos-y-otros-archivos">¿Delta Chat soporta envío de imágenes, videos, documentos y otros archivos?</a></li>
|
||||
<li><a href="#multiple-accounts">¿Qué son los perfiles? ¿Cómo puedo cambiar entre ellos?</a></li>
|
||||
<li><a href="#quién-ve-mi-foto-de-perfil">¿Quién ve mi foto de perfil?</a></li>
|
||||
<li><a href="#signature">Can I set a Bio/Status with Delta Chat?</a></li>
|
||||
@@ -14,6 +13,7 @@
|
||||
<li><a href="#qué-significa-el-punto-verde">¿Qué significa el punto verde?</a></li>
|
||||
<li><a href="#qué-significan-las-marcas-que-se-muestran-junto-a-los-mensajes-salientes">¿Qué significan las marcas que se muestran junto a los mensajes salientes?</a></li>
|
||||
<li><a href="#edit">Corregir errores y borrar mensajes después de enviar</a></li>
|
||||
<li><a href="#mediaquality">How is media quality handled?</a></li>
|
||||
<li><a href="#ephemeralmsgs">¿Cómo funciona la desaparición de mensajes?</a></li>
|
||||
<li><a href="#delold">¿Qué pasa si activo “Borrar mensajes del dispositivo”?</a></li>
|
||||
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
|
||||
@@ -105,8 +105,7 @@
|
||||
|
||||
</h2>
|
||||
|
||||
<p>Delta Chat is a reliable, decentralized and secure instant messaging app,
|
||||
available for mobile and desktop platforms.</p>
|
||||
<p>Delta Chat es una aplicación de mensajería instantánea confiable, descentralizada y segura, disponible para dispositivos móviles y computadoras de escritorio.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
@@ -216,19 +215,6 @@ You can also add a little introduction message.</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="delta-chat-soporta-envío-de-imágenes-videos-documentos-y-otros-archivos">
|
||||
|
||||
|
||||
¿Delta Chat soporta envío de imágenes, videos, documentos y otros archivos? <a href="#delta-chat-soporta-envío-de-imágenes-videos-documentos-y-otros-archivos" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Yes. 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>Attachment-</strong>
|
||||
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
|
||||
|
||||
<p>Para mejorar el rendimiento, las imágenes se optimizan y se envían en un tamaño más pequeño de forma predeterminada, pero puedes enviarla como un “archivo” para conservar la original.</p>
|
||||
|
||||
<h3 id="multiple-accounts">
|
||||
|
||||
|
||||
@@ -407,6 +393,32 @@ No se envían notificaciones y no hay límite de tiempo.</p>
|
||||
<p>Note, that the original message may still be received by chat members
|
||||
who could have already replied, forwarded, saved, screenshotted or otherwise copied the message.</p>
|
||||
|
||||
<h3 id="mediaquality">
|
||||
|
||||
|
||||
How is media quality handled? <a href="#mediaquality" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<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>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>In regions with worse connectivity,
|
||||
you can choose higher compression at <strong>Settings → Chats → Outgoing Media Quality</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<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>
|
||||
|
||||
<h3 id="ephemeralmsgs">
|
||||
|
||||
|
||||
@@ -1026,22 +1038,26 @@ Relays are operated by different groups and people.</p>
|
||||
<p>By default, after installation, a relay is <strong>automatically set up</strong>,
|
||||
so you do not need to care about that.
|
||||
However, if you want to,
|
||||
you can configure relays at At <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
you can configure relays at <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>You can <strong>add</strong> a relay by scanning its QR code;
|
||||
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.</p>
|
||||
<a href="https://chatmail.at/relays">chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.
|
||||
Contacts learn your current relays automatically when you message them.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
|
||||
<p>Tap on a relay to set it as <strong>used for sending</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If you <strong>remove</strong> a relay,
|
||||
make sure another default relay was used for a sufficient amount of time.
|
||||
Otherwise, messages from your chat partners won’t reach you.
|
||||
If in doubt, remove later.</p>
|
||||
contacts who only know this relay may not reach you until you message them again.
|
||||
To stay reachable in the meantime, choose <strong>Hide from Contacts</strong> in the confirmation dialog
|
||||
instead of removing it right away.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>To <strong>show</strong> a hidden relay again, tap on it.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1550,7 +1566,7 @@ See <a href="https://delta.chat/en/2023-05-22-webxdc-security">here for the full
|
||||
<li>
|
||||
<p>2023 March, <a href="https://cure53.de">Cure53</a> analyzed both the transport encryption of
|
||||
Delta Chat’s network connections and a reproducible mail server setup as
|
||||
<a href="https://delta.chat/es/serverguide">recommended on this site</a>.
|
||||
<a href="https://delta.chat/serverguide">recommended on this site</a>.
|
||||
You can read more about the audit <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">on our blog</a>
|
||||
or read the <a href="https://delta.chat/assets/blog/MER-01-report.pdf">full report here</a>.</p>
|
||||
</li>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+116
-171
@@ -2,27 +2,27 @@
|
||||
<html lang="fr"><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="#quest-ce-que-delta-chat-">Qu’est-ce que Delta Chat ?</a>
|
||||
<ul>
|
||||
<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="#delta-chat-prend-il-en-charge-les-images-vidéos-et-autres-pièces-jointes-">Delta Chat prend-il en charge les images, vidéos et autres pièces jointes ?</a></li>
|
||||
<li><a href="#multiple-accounts">What are profiles? How can I switch between them?</a></li>
|
||||
<li><a href="#howtoe2ee">Comment trouver des personnes avec qui discuter ?</a></li>
|
||||
<li><a href="#pourquoi-une-discussion-est-marquée-comme-invitation-">Pourquoi une discussion est marquée comme “invitation” ?</a></li>
|
||||
<li><a href="#comment-mettre-deux-personnes-en-relation-entre-elles-">Comment mettre deux personnes en relation entre elles ?</a></li>
|
||||
<li><a href="#multiple-accounts">À quoi correspondent les profils ? Comment est-ce que je peux passer de l’un à l’autre ?</a></li>
|
||||
<li><a href="#qui-peut-voir-ma-photo-de-profil-">Qui peut voir ma photo de profil ?</a></li>
|
||||
<li><a href="#signature">Can I set a Bio/Status with Delta Chat?</a></li>
|
||||
<li><a href="#signature">Est-il possible de définir une Bio/un Statut avec Delta Chat ?</a></li>
|
||||
<li><a href="#que-signifient-épingler-sourdine-et-archiver-">Que signifient “épingler”, “sourdine” et “archiver” ?</a></li>
|
||||
<li><a href="#save">How do “Saved Messages” work?</a></li>
|
||||
<li><a href="#save">Comment fonctionnent les “Messages enregistrés” ?</a></li>
|
||||
<li><a href="#que-signifie-le-point-vert-">Que signifie le point vert ?</a></li>
|
||||
<li><a href="#que-signifient-les-coches-affichées-à-côté-des-messages-sortants-">Que signifient les coches affichées à côté des messages sortants ?</a></li>
|
||||
<li><a href="#edit">Correct typos and delete messages after sending</a></li>
|
||||
<li><a href="#ephemeralmsgs">How do disappearing messages work?</a></li>
|
||||
<li><a href="#edit">Corriger des fautes et supprimer des messages après les avoir envoyés</a></li>
|
||||
<li><a href="#mediaquality">Comment est gérée la qualité des médias envoyés ?</a></li>
|
||||
<li><a href="#ephemeralmsgs">Comment fonctionnent les messages éphémères ?</a></li>
|
||||
<li><a href="#delold">Que se passe-t-il si j’active l’option “Supprimer les anciens messages de l’appareil” ?</a></li>
|
||||
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
|
||||
<li><a href="#remove-account">Comment puis-je supprimer un profil de discussion ?</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#groups">Groups</a>
|
||||
<li><a href="#groups">Groupes</a>
|
||||
<ul>
|
||||
<li><a href="#création-dun-groupe">Création d’un groupe</a></li>
|
||||
<li><a href="#addmembers">Add and remove members</a></li>
|
||||
<li><a href="#addmembers">Ajouter et supprimer des membres</a></li>
|
||||
<li><a href="#jai-quitté-un-groupe-par-accident">J’ai quitté un groupe par accident.</a></li>
|
||||
<li><a href="#je-ne-souhaite-plus-recevoir-les-messages-dun-groupe">Je ne souhaite plus recevoir les messages d’un groupe.</a></li>
|
||||
<li><a href="#cloning-a-group">Cloning a group</a></li>
|
||||
@@ -105,90 +105,71 @@
|
||||
|
||||
</h2>
|
||||
|
||||
<p>Delta Chat is a reliable, decentralized and secure instant messaging app,
|
||||
available for mobile and desktop platforms.</p>
|
||||
<p>Delta Chat est une application de messagerie fiable, décentralisée et sécurisée, disponible pour smartphones et ordinateurs de bureau.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<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>
|
||||
<p>Création à la volée de <strong>profils de discussion privés</strong> grâce à des <a href="https://chatmail.at/relays">relais chatmail</a> sécurisés et interopérables qui permettent des échanges instantanés et des notifications Push pour les appareils iOS et Android.</p>
|
||||
</li>
|
||||
<li>
|
||||
<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>
|
||||
<p>Fonctionne en <a href="#multiple-accounts">multi-profils</a> et <a href="#multiclient">multi-appareils</a> sur toutes les plateformes et quelque soit <a href="https://chatmail.at/clients">l’application chatmail</a> utilisée.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Interactive <a href="#webxdc">in-chat apps</a> for gaming and collaboration</p>
|
||||
<p><a href="#webxdc">Applications</a> interactives permettant de jouer et collaborer au sein des discussions.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="#security-audits">Audited end-to-end encryption</a>
|
||||
safe against network and server attacks.</p>
|
||||
<p><a href="#security-audits">Chiffrement de bout-en-bout audité</a> protégé contre les attaques réseaux et de serveurs.</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>
|
||||
<p>Logiciel libre et gratuit, autant côté serveur que côté application, développé autour des <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">standards d’internet</a>.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="howtoe2ee">
|
||||
|
||||
|
||||
How can I find people to chat with? <a href="#howtoe2ee" class="anchor"></a>
|
||||
Comment trouver des personnes avec qui discuter ? <a href="#howtoe2ee" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>First, note that Delta Chat is a private messenger.
|
||||
There is no public discovery, <em>you</em> decide about your contacts.</p>
|
||||
<p>Delta Chat est une application de messagerie privée. Il n’y a pas de mécanisme de découverte publique des utilisateurs et utilisatrices. <em>Vous</em> décidez avec qui vous échangez dans Delta Chat.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<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>
|
||||
<p>En <strong>présence physique</strong> de vos amis ou de membres de votre famille, appuyez sur l’icône <strong>QR code</strong> <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> sur l’écran principal. Demandez à votre contact de <strong>scanner</strong> le QR code avec leur application Delta Chat.</p>
|
||||
</li>
|
||||
<li>
|
||||
<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>
|
||||
<p>Pour la mise en place d’un échange <strong>à distance</strong>, cliquez sur l’icône <strong>QR code</strong> sur l’écran principal, puis sur le bouton “Lien”, puis sur “Copier” ou “Partager” et envoyez le lien d’invitation via une autre messagerie privée.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Now wait while connection gets established.</p>
|
||||
<p>Il suffit ensuite d’attendre que la connexion soit établie.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>If both sides are online, they will soon see a chat
|
||||
and can start messaging securely.</p>
|
||||
<p>Si les deux contacts sont en ligne, ils verront rapidement apparaître une nouvelle discussion et pourront tout de suite commencer une discussion sécurisée.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If one side is offline or in bad network,
|
||||
the ability to chat is delayed until connectivity is restored.</p>
|
||||
<p>Si l’un des contacts n’est pas en ligne ou a des problèmes de réseau, la possibilité de démarrer la discussion aura lieu dès que la connectivité sera rétablie.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<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>
|
||||
<p>Félicitations !
|
||||
Vous utiliserez désormais le <a href="#e2ee">chiffrement de bout-en-bout</a> avec ce contact.
|
||||
Si vous vous ajoutez à des discussions de <a href="#groups">groupe</a> le chiffrement de bout-en-bout sera établi entre tous les membres de la discussion.</p>
|
||||
|
||||
<h3 id="why-is-a-chat-marked-as-request">
|
||||
<h3 id="pourquoi-une-discussion-est-marquée-comme-invitation-">
|
||||
|
||||
|
||||
Why is a chat marked as “Request”? <a href="#why-is-a-chat-marked-as-request" class="anchor"></a>
|
||||
Pourquoi une discussion est marquée comme “invitation” ? <a href="#pourquoi-une-discussion-est-marquée-comme-invitation-" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<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>Delta Chat étant une messagerie privée, seules les personnes avec qui vous <a href="#howtoe2ee">partagez votre QR code ou lien d’invitation</a> peuvent vous écrire.</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>
|
||||
<p>Vos contacts peuvent cependant partager votre profil avec d’autres personnes, ce qui apparaît alors comme une <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">invitation</b>.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
@@ -204,53 +185,37 @@ recevoir de messages de cette personne, nous vous conseillons de la <strong>bloq
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="how-can-i-put-two-of-my-friends-in-contact-with-each-other">
|
||||
<h3 id="comment-mettre-deux-personnes-en-relation-entre-elles-">
|
||||
|
||||
|
||||
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>
|
||||
Comment mettre deux personnes en relation entre elles ? <a href="#comment-mettre-deux-personnes-en-relation-entre-elles-" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<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>Joignez le premier contact à votre discussion avec le second en utilisant <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Bouton pièce jointe → Contact</strong>.
|
||||
Profitez-en pour ajouter un message de présentation.</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="delta-chat-prend-il-en-charge-les-images-vidéos-et-autres-pièces-jointes-">
|
||||
|
||||
|
||||
Delta Chat prend-il en charge les images, vidéos et autres pièces jointes ? <a href="#delta-chat-prend-il-en-charge-les-images-vidéos-et-autres-pièces-jointes-" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Oui. 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>Attachment-</strong>
|
||||
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
|
||||
|
||||
<p>Pour améliorer les performances, les images sont redimensionnées et envoyées en taille réduite par défaut ; mais vous pouvez les envoyer en tant que “fichier” pour en conserver la taille originale.</p>
|
||||
<p>Le deuxième contact recevra une <strong>carte</strong> qu’il pourra cliquer pour commencer une discussion avec le premier.</p>
|
||||
|
||||
<h3 id="multiple-accounts">
|
||||
|
||||
|
||||
What are profiles? How can I switch between them? <a href="#multiple-accounts" class="anchor"></a>
|
||||
À quoi correspondent les profils ? Comment est-ce que je peux passer de l’un à l’autre ? <a href="#multiple-accounts" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<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>Un profil est <strong>un nom, une photo</strong> et quelques informations supplémentaires pour chiffrer des messages.
|
||||
Un profil n’est présent que sur votre appareil/vos appareils et utilise le serveur seulement pour relayer les messages vers vos contacts.</p>
|
||||
|
||||
<p>On first installation of Delta Chat a first profile is created.</p>
|
||||
<p>Lors de la première installation de Delta Chat un premier profil est créé.</p>
|
||||
|
||||
<p>Later, you can tap your profile image in the upper left corner to <strong>Add Profiles</strong>
|
||||
or to <strong>Switch Profiles</strong>.</p>
|
||||
<p>Par la suite vous pourrez cliquer sur votre image de profil en haut à gauche de l’écran principal pour <strong>Ajouter un profil</strong> ou <strong>Changer de profil</strong>.</p>
|
||||
|
||||
<p>You may want to use separate profiles for political, family or work related activities.</p>
|
||||
<p>Vous souhaiterez peut être utiliser des profils différents pour vos activités familiales, professionnelles ou politiques.</p>
|
||||
|
||||
<p>You may also wish to learn <a href="#multiclient">how to use the same profile on multiple devices</a>.</p>
|
||||
<p>Vous souhaiterez peut être aussi apprendre <a href="#multiclient">comment utiliser le même profil sur plusieurs appareils</a>.</p>
|
||||
|
||||
<h3 id="qui-peut-voir-ma-photo-de-profil-">
|
||||
|
||||
@@ -267,15 +232,13 @@ or to <strong>Switch Profiles</strong>.</p>
|
||||
<h3 id="signature">
|
||||
|
||||
|
||||
Can I set a Bio/Status with Delta Chat? <a href="#signature" class="anchor"></a>
|
||||
Est-il possible de définir une Bio/un Statut avec Delta Chat ? <a href="#signature" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<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>
|
||||
<p>Oui, vous pouvez le faire dans <strong>Paramètres → Profil → Signature</strong>.
|
||||
À partir du moment ou vous échangez avec quelqu’un, cette personne pourra voir votre Signature en regardant les détails de votre profil.</p>
|
||||
|
||||
<h3 id="que-signifient-épingler-sourdine-et-archiver-">
|
||||
|
||||
@@ -309,37 +272,33 @@ pour mettre une discussion en sourdine, ouvrez le menu de la conversation (Andro
|
||||
<h3 id="save">
|
||||
|
||||
|
||||
How do “Saved Messages” work? <a href="#save" class="anchor"></a>
|
||||
Comment fonctionnent les “Messages enregistrés” ? <a href="#save" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p><strong>Saved Messages</strong> is a chat that you can use to easily remember and find messages.</p>
|
||||
<p><strong>Messages enregistrés</strong> est une discussion que vous pouvez utiliser pour sauvegarder et retrouver facilement des messages.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>In any chat, long tap or right click a message and select <strong>Save</strong></p>
|
||||
<p>Dans n’importe quelle discussion, touchez <img style="vertical-align:middle; width:1.2em; margin:1px;" src="../saved-icon.png" alt="Saved icon" /> ou faites un clic droit sur un message et sélectionnez <strong>Enregistrer</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Saved messages are marked by the symbol
|
||||
<img style="vertical-align:middle; width:1.2em; margin:1px" src="../saved-icon.png" alt="Saved icon" />
|
||||
next to the timestamp</p>
|
||||
<p>Les messages enregistrés sont identifiés par le symbole <img style="vertical-align:middle; width:1.2em; margin:1px" src="../saved-icon.png" alt="Saved icon" /> à côté de l’horodatage du message.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Later, open the “Saved Messages” chat - and you will see the saved messages there.
|
||||
By tapping <img style="vertical-align:middle; width:1.2em; margin:1px" src="../go-to-original.png" alt="Arrow-right icon" />,
|
||||
you can go back to the original message in the original chat</p>
|
||||
<p>Par la suite vous pourrez retrouver tous ces messages dans la discussion “Messages enregistrés”.
|
||||
En touchant <img style="vertical-align:middle; width:1.2em; margin:1px" src="../go-to-original.png" alt="Arrow-right icon" /> vous retrouverez le message dans sa discussion d’origine.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Finally, you can also use “Saved Messages” to take <strong>personal notes</strong> - open the chat, type something, add a photo or a voice message etc.</p>
|
||||
<p>Enfin vous pouvez aussi utiliser la discussion “Messages enregistrés” pour prendre des <strong>notes privées</strong> - ouvrez la discussion, écrivez votre message, ajoutez une photo, un message vocal, etc.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>As “Saved Message” are synced, they can become very handy for transferring data between devices</p>
|
||||
<p>La discussion “Messages enregistrés” étant synchronisée elle peut être très pratique pour transférer des informations d’un appareil à un autre.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Messages stay saved even if they are edited or deleted -
|
||||
may it be by <a href="#edit">sender</a>, by <a href="#delold">device cleanup</a> or by <a href="#ephemeralmsgs">disappearing messages of other chats</a>.</p>
|
||||
<p>Les messages restent enregistrés même lorsqu’ils sont édités ou supprimés, que ça soit par <a href="#edit">l’expéditeur⋅rice</a>, par <a href="#delold">suppression des anciens messages de l’appareil</a> ou par <a href="#ephemeralmsgs">disparition des messages éphémères au sein d’une discussion</a>.</p>
|
||||
|
||||
<h3 id="que-signifie-le-point-vert-">
|
||||
|
||||
@@ -349,13 +308,10 @@ may it be by <a href="#edit">sender</a>, by <a href="#delold">device cleanup</a>
|
||||
|
||||
</h3>
|
||||
|
||||
<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>Vous pourrez parfois voir un <strong>point vert</strong> <img style="vertical-align:middle; width:1.2em; margin:1px" src="../green-dot.png" alt="" /> sur le profil d’un contact.
|
||||
Cela veut dire que vous avez <strong>récemment vu</strong> le contact dans les 10 dernières minutes, par exemple parce que vous avez échangé avec ce contact, ou que vous avez reçu un accusé de lecture.</p>
|
||||
|
||||
<p>So this is not a real time online status
|
||||
and others will as well not always see that you are “online”.</p>
|
||||
<p>Ceci n’est donc pas un indicateur temps réel de présence en ligne et vos contacts ne pourront donc pas toujours voir si vous êtes en ligne ou non.</p>
|
||||
|
||||
<h3 id="que-signifient-les-coches-affichées-à-côté-des-messages-sortants-">
|
||||
|
||||
@@ -367,79 +323,75 @@ and others will as well not always see that you are “online”.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<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>
|
||||
<p><strong>Une marque</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick1.png" alt="" /> veut dire que le message a été envoyé correctement au <a href="#relays">relai</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<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>
|
||||
<p><strong>Deux marques</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick2.png" alt="" /> indiquent que votre contact a lu le message.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<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>Au sein des <a href="#groups">groupes</a> la seconde marque veut dire qu’au moins un membre du groupe a pu lire le message.</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>
|
||||
<p>La seconde marque ne peut s’afficher que si vous et l’un de vos destinataires du message avez activé <strong>Paramètres → Discussions → Accusés de lecture</strong>.</p>
|
||||
|
||||
<h3 id="edit">
|
||||
|
||||
|
||||
Correct typos and delete messages after sending <a href="#edit" class="anchor"></a>
|
||||
Corriger des fautes et supprimer des messages après les avoir envoyés <a href="#edit" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>You can edit the text of your messages after sending.
|
||||
For that, long tap or right click the message and select <strong>Edit</strong>
|
||||
or <img style="vertical-align:middle; width:1.2em; margin:1px" src="../edit-icon.png" alt="Edit icon" />.</p>
|
||||
<p>Vous pouvez éditer le texte de votre message après l’avoir envoyé. Pour cela, appuyer longtemps ou faire clic droit sur le message et choisir <strong>Modifier</strong> ou <img style="vertical-align:middle; width:1.2em; margin:1px" src="../edit-icon.png" alt="Edit icon" />.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If you have sent a message accidentally,
|
||||
from the same menu, select <strong>Delete</strong> and then <strong>Delete for Everyone</strong>.</p>
|
||||
<p>Si vous avez envoyé un message par erreur vous pouvez le supprimer depuis le même menu en choisissant <strong>Supprimer le message</strong> puis <strong>Supprimer pour moi</strong> ou <strong>Supprimer pour tout le monde</strong>.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>While edited messages will have the word “Edited” next to the timestamp,
|
||||
deleted messages will be removed without a marker in the chat.
|
||||
Notifications are not sent and there is no time limit.</p>
|
||||
<p>Les messages modifiés seront indiqués par le mot “Modifié” à côté de l’horodatage du message, tandis que les messages supprimés le sont sans notification, ni dans la discussion, ni sur l’appareil, et peuvent l’être sans limite dans le temps.</p>
|
||||
|
||||
<p>Note, that the original message may still be received by chat members
|
||||
who could have already replied, forwarded, saved, screenshotted or otherwise copied the message.</p>
|
||||
<p>Remarque : le message original peut toutefois encore exister s’il a déjà été transféré, enregistré, ou si l’un des membres de la discussion en a fait une copie ou une capture d’écran.</p>
|
||||
|
||||
<h3 id="ephemeralmsgs">
|
||||
<h3 id="mediaquality">
|
||||
|
||||
|
||||
How do disappearing messages work? <a href="#ephemeralmsgs" class="anchor"></a>
|
||||
Comment est gérée la qualité des médias envoyés ? <a href="#mediaquality" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>You can turn on “disappearing messages”
|
||||
in the settings of a chat,
|
||||
at the top right of the chat window,
|
||||
by selecting a time span
|
||||
between 5 minutes and 1 year.</p>
|
||||
<p>Images, vidéos, fichiers, messages vocaux etc. peuvent être envoyé avec les boutons <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Pièce jointe</strong> ou <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Message vocal</strong>.</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>
|
||||
<ul>
|
||||
<li>
|
||||
<p>Par défaut le niveau de compression choisi permet un <strong>envoi rapide et efficace</strong> des messages en respectant les limites de données et de stockage de tout le monde. C’est un réglage idéal pour des échanges standards.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Dans les régions avec une connectivité limitée il est possible de choisir une compression plus forte dans <strong>Paramètres → Discussions → Qualité du média en sortie</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Si vous souhaitez absolument transmettre un fichier en <strong>qualité originale</strong>, utilisez <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Pièce jointe → Fichier</strong> dans la discussion. Pensez à utiliser cette fonctionnalité avec parcimonie pour limiter l’utilisation des données par toutes les personnes dans la discussion.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Note that you can rely on disappearing messages
|
||||
only as long as you trust your chat partners;
|
||||
malicious chat partners can take photos,
|
||||
or otherwise save, copy or forward messages before deletion.</p>
|
||||
<h3 id="ephemeralmsgs">
|
||||
|
||||
<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>
|
||||
|
||||
Comment fonctionnent les messages éphémères ? <a href="#ephemeralmsgs" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Vous pouvez activer les “Messages éphémères” dans les paramètres d’une discussion, dans le menu en haut à droite, en choisissant une durée prédéfinie entre 5 min et 1 an.</p>
|
||||
|
||||
<p>Tant que le paramètre reste activé, les applications de chacun des membres se chargent de supprimer les messages qui dépassent la durée définie. La durée est comptabilisée à partir du moment où le message est vu pour la première fois, et le message est supprimé au sein de l’application et sur le serveur.</p>
|
||||
|
||||
<p>Remarque : ne faites confiance au réglage “Messages éphémères” que si vous faites confiance aux membres de la discussion. Des membres mal intentionné⋅es peuvent très bien prendre des photos, copier, sauvegarder, transférer un message avant qu’il ne soit supprimé.</p>
|
||||
|
||||
<p>Enfin, si l’un⋅e des membres d’une discussion désinstalle l’application Delta Chat, les messages (chiffrés, pour rappel) peuvent rester plus longtemps sur le serveur avant d’être supprimés.</p>
|
||||
|
||||
<h3 id="delold">
|
||||
|
||||
@@ -456,39 +408,28 @@ the (anyway encrypted) messages may take longer to get deleted from their server
|
||||
<h3 id="remove-account">
|
||||
|
||||
|
||||
How can I delete my chat profile? <a href="#remove-account" class="anchor"></a>
|
||||
Comment puis-je supprimer un profil de discussion ? <a href="#remove-account" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<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>Si vous utilisez plus d’un profil dans l’application, vous pouvez supprimer un profil spécifique via le menu (en haut sur Android et iOS), ou dans la barre latérale (sur l’application de bureau) permettant de passer d’un profil à un autre. Les profils ne sont toujours supprimés que sur l’appareil en question. Les profils continueront donc d’exister sur les autres appareils le cas échéant.</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>
|
||||
<p>Si vous n’utilisez qu’un seul profil vous pouvez tout simplement désinstaller l’application. Cela déclenchera la suppression des données liées au profil en question sur le serveur chatmail. Pour plus d’informations voir <a href="https://nine.testrun.org/info.html#account-deletion">Account deletion sur nine.testrun.org</a> ou la page respective du <a href="https://chatmail.at/relays">serveur chatmail</a> que vous utilisez.</p>
|
||||
|
||||
<h2 id="groups">
|
||||
|
||||
|
||||
Groups <a href="#groups" class="anchor"></a>
|
||||
Groupes <a href="#groups" class="anchor"></a>
|
||||
|
||||
|
||||
</h2>
|
||||
|
||||
<p>Groups let several people chat together privately with <strong>equal rights</strong>.</p>
|
||||
<p>Les groupes permettent à plusieurs personnes d’avoir une discussion. Chaque membre du groupe à les <strong>même droits</strong>.</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>Chaque membre du groupe peut changer le nom du groupe ou l’avatar, <a href="#addmembers">ajouter ou supprimer des membres</a>, activer/désactiver <a href="#ephemeralmsgs">les messages éphémères</a>, et <a href="#edit">supprimer leurs propres messages</a> sur tous les appareils des membres de la discussion.</p>
|
||||
|
||||
<p>Because all members have the same rights, groups work best among <strong>trusted friends and family</strong>.</p>
|
||||
<p>Puisque tous les membres d’une discussion ont les mêmes droits, les groupes fonctionnent mieux avec des <strong>personnes de confiance et la famille</strong>.</p>
|
||||
|
||||
<h3 id="création-dun-groupe">
|
||||
|
||||
@@ -513,7 +454,7 @@ and <a href="#edit">delete their own messages</a> from all member’s devices.</
|
||||
<h3 id="addmembers">
|
||||
|
||||
|
||||
Add and remove members <a href="#addmembers" class="anchor"></a>
|
||||
Ajouter et supprimer des membres <a href="#addmembers" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
@@ -1016,22 +957,26 @@ Relays are operated by different groups and people.</p>
|
||||
<p>By default, after installation, a relay is <strong>automatically set up</strong>,
|
||||
so you do not need to care about that.
|
||||
However, if you want to,
|
||||
you can configure relays at At <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
you can configure relays at <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>You can <strong>add</strong> a relay by scanning its QR code;
|
||||
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.</p>
|
||||
<a href="https://chatmail.at/relays">chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.
|
||||
Contacts learn your current relays automatically when you message them.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
|
||||
<p>Tap on a relay to set it as <strong>used for sending</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If you <strong>remove</strong> a relay,
|
||||
make sure another default relay was used for a sufficient amount of time.
|
||||
Otherwise, messages from your chat partners won’t reach you.
|
||||
If in doubt, remove later.</p>
|
||||
contacts who only know this relay may not reach you until you message them again.
|
||||
To stay reachable in the meantime, choose <strong>Hide from Contacts</strong> in the confirmation dialog
|
||||
instead of removing it right away.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>To <strong>show</strong> a hidden relay again, tap on it.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1538,7 +1483,7 @@ research paper published afterwards.</p>
|
||||
Vous trouverez <a href="https://delta.chat/en/2023-05-22-webxdc-security">ici un article de fond complet à propos de la sécurité du chiffrement de bout-en-bout sur internet</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Début 2023, <a href="https://cure53.de">Cure53</a> a analysé le chiffrement d’acheminement des connexions réseau de Delta Chat et testé une configuration de serveur de courriel reproductible, telle que <a href="https://delta.chat/fr/serverguide">recommandée sur ce site</a>.
|
||||
<p>Début 2023, <a href="https://cure53.de">Cure53</a> a analysé le chiffrement d’acheminement des connexions réseau de Delta Chat et testé une configuration de serveur de courriel reproductible, telle que <a href="https://delta.chat/serverguide">recommandée sur ce site</a>.
|
||||
Vous trouverez plus d’informations sur cet audit <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">sur notre blog</a> ou dans <a href="https://delta.chat/assets/blog/MER-01-report.pdf">le rapport complet ici</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<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="#apakah-delta-chat-mendukung-gambar-vidio-dan-lampiran-lainnya">Apakah Delta Chat mendukung gambar, vidio dan lampiran lainnya?</a></li>
|
||||
<li><a href="#multiple-accounts">What are profiles? How can I switch between them?</a></li>
|
||||
<li><a href="#siapa-yang-dapat-melihat-foto-profil-saya">Siapa yang dapat melihat Foto Profil saya?</a></li>
|
||||
<li><a href="#signature">Can I set a Bio/Status with Delta Chat?</a></li>
|
||||
@@ -14,6 +13,7 @@
|
||||
<li><a href="#what-does-the-green-dot-mean">What does the green dot mean?</a></li>
|
||||
<li><a href="#what-do-the-ticks-shown-beside-outgoing-messages-mean">What do the ticks shown beside outgoing messages mean?</a></li>
|
||||
<li><a href="#edit">Correct typos and delete messages after sending</a></li>
|
||||
<li><a href="#mediaquality">How is media quality handled?</a></li>
|
||||
<li><a href="#ephemeralmsgs">How do disappearing messages work?</a></li>
|
||||
<li><a href="#delold">What happens if I turn on “Delete Messages from Device”?</a></li>
|
||||
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
|
||||
@@ -218,19 +218,6 @@ You can also add a little introduction message.</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="apakah-delta-chat-mendukung-gambar-vidio-dan-lampiran-lainnya">
|
||||
|
||||
|
||||
Apakah Delta Chat mendukung gambar, vidio dan lampiran lainnya? <a href="#apakah-delta-chat-mendukung-gambar-vidio-dan-lampiran-lainnya" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Yes. 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>Attachment-</strong>
|
||||
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
|
||||
|
||||
<p>For performance, images are optimized and sent at a smaller size by default, but you can send it as a “file” to preserve the original.</p>
|
||||
|
||||
<h3 id="multiple-accounts">
|
||||
|
||||
|
||||
@@ -411,6 +398,32 @@ Notifications are not sent and there is no time limit.</p>
|
||||
<p>Note, that the original message may still be received by chat members
|
||||
who could have already replied, forwarded, saved, screenshotted or otherwise copied the message.</p>
|
||||
|
||||
<h3 id="mediaquality">
|
||||
|
||||
|
||||
How is media quality handled? <a href="#mediaquality" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<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>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>In regions with worse connectivity,
|
||||
you can choose higher compression at <strong>Settings → Chats → Outgoing Media Quality</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<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>
|
||||
|
||||
<h3 id="ephemeralmsgs">
|
||||
|
||||
|
||||
@@ -1030,22 +1043,26 @@ Relays are operated by different groups and people.</p>
|
||||
<p>By default, after installation, a relay is <strong>automatically set up</strong>,
|
||||
so you do not need to care about that.
|
||||
However, if you want to,
|
||||
you can configure relays at At <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
you can configure relays at <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>You can <strong>add</strong> a relay by scanning its QR code;
|
||||
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.</p>
|
||||
<a href="https://chatmail.at/relays">chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.
|
||||
Contacts learn your current relays automatically when you message them.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
|
||||
<p>Tap on a relay to set it as <strong>used for sending</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If you <strong>remove</strong> a relay,
|
||||
make sure another default relay was used for a sufficient amount of time.
|
||||
Otherwise, messages from your chat partners won’t reach you.
|
||||
If in doubt, remove later.</p>
|
||||
contacts who only know this relay may not reach you until you message them again.
|
||||
To stay reachable in the meantime, choose <strong>Hide from Contacts</strong> in the confirmation dialog
|
||||
instead of removing it right away.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>To <strong>show</strong> a hidden relay again, tap on it.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1561,7 +1578,7 @@ See <a href="https://delta.chat/en/2023-05-22-webxdc-security">here for the full
|
||||
<li>
|
||||
<p>2023 March, <a href="https://cure53.de">Cure53</a> analyzed both the transport encryption of
|
||||
Delta Chat’s network connections and a reproducible mail server setup as
|
||||
<a href="https://delta.chat/id/serverguide">recommended on this site</a>.
|
||||
<a href="https://delta.chat/serverguide">recommended on this site</a>.
|
||||
You can read more about the audit <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">on our blog</a>
|
||||
or read the <a href="https://delta.chat/assets/blog/MER-01-report.pdf">full report here</a>.</p>
|
||||
</li>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<li><a href="#howtoe2ee">Come posso trovare persone con cui chattare?</a></li>
|
||||
<li><a href="#perché-una-chat-è-contrassegnata-come-richiesta">Perché una chat è contrassegnata come “Richiesta”?</a></li>
|
||||
<li><a href="#come-posso-mettere-in-contatto-due-miei-amici">Come posso mettere in contatto due miei amici?</a></li>
|
||||
<li><a href="#delta-chat-supporta-immagini-video-e-altri-allegati">Delta Chat supporta immagini, video e altri allegati?</a></li>
|
||||
<li><a href="#multiple-accounts">Cosa sono i profili? Come posso passare dall’uno all’altro?</a></li>
|
||||
<li><a href="#chi-vede-la-mia-immagine-del-profilo">Chi vede la mia immagine del profilo?</a></li>
|
||||
<li><a href="#signature">Posso impostare una Biografia/Stato con Delta Chat?</a></li>
|
||||
@@ -14,6 +13,7 @@
|
||||
<li><a href="#cosa-significa-il-punto-verde">Cosa significa il punto verde?</a></li>
|
||||
<li><a href="#cosa-significano-i-segni-di-spunta-visualizzati-accanto-ai-messaggi-in-uscita">Cosa significano i segni di spunta visualizzati accanto ai messaggi in uscita?</a></li>
|
||||
<li><a href="#edit">Correggi gli errori e cancella i messaggi dopo averli inviati</a></li>
|
||||
<li><a href="#mediaquality">Come viene gestita la qualità dei media?</a></li>
|
||||
<li><a href="#ephemeralmsgs">Come funzionano i messaggi a scomparsa?</a></li>
|
||||
<li><a href="#delold">Cosa succede se attivo “Elimina Messaggi dal Dispositivo”?</a></li>
|
||||
<li><a href="#remove-account">Come posso eliminare il mio profilo chat?</a></li>
|
||||
@@ -217,19 +217,6 @@ Puoi anche aggiungere un breve messaggio di presentazione.</p>
|
||||
<p>Il secondo contatto riceverà una <strong>scheda</strong>
|
||||
e potrà toccarla per iniziare a chattare con il primo contatto.</p>
|
||||
|
||||
<h3 id="delta-chat-supporta-immagini-video-e-altri-allegati">
|
||||
|
||||
|
||||
Delta Chat supporta immagini, video e altri allegati? <a href="#delta-chat-supporta-immagini-video-e-altri-allegati" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Sì. Immagini, video, files, messaggi vocali ecc. possono essere inviati utilizzando <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Allegato-</strong>
|
||||
o <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> pulsanti <strong>Messaggio Vocale</strong></p>
|
||||
|
||||
<p>Per le prestazioni, le immagini sono ottimizzate e inviate in dimensioni inferiori per impostazione predefinita, ma è possibile inviarle come “file” per preservare l’originale.</p>
|
||||
|
||||
<h3 id="multiple-accounts">
|
||||
|
||||
|
||||
@@ -409,6 +396,32 @@ Non vengono inviate notifiche e non c’è limite di tempo.</p>
|
||||
<p>Nota che il messaggio originale potrebbe essere ancora sui dispositivi dei membri della chat
|
||||
che avrebbero già potuto rispondere, inoltrare, salvare, scattare una schermata o copiare il messaggio in altri modi.</p>
|
||||
|
||||
<h3 id="mediaquality">
|
||||
|
||||
|
||||
Come viene gestita la qualità dei media? <a href="#mediaquality" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Immagini, video, files, messaggi vocali ecc. possono essere inviati utilizzando <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Allegato-</strong>
|
||||
o <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> pulsanti <strong>Messaggio Vocale</strong></p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Per impostazione predefinita, la compressione garantisce una <strong>consegna rapida ed efficiente</strong> che rispetta i limiti di dati e di spazio di archiviazione di tutti.
|
||||
Questa soluzione è ideale per la comunicazione quotidiana.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Nelle regioni con connettività peggiore,
|
||||
è possibile scegliere una compressione più elevata in <strong>Impostazioni → Chat → Qualità Media in Uscita</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Se hai bisogno di inviare i file multimediali nella loro <strong>qualità originale</strong>, usa <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Allega → File</strong> nella chat.
|
||||
Si prega di utilizzare questo metodo con parsimonia, poiché l’invio di file originali aumenterà significativamente il consumo di dati per te e per tutti i destinatari della chat.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="ephemeralmsgs">
|
||||
|
||||
|
||||
@@ -1021,16 +1034,22 @@ Tuttavia, se lo si desidera,
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Puoi <strong>aggiungere</strong> un ripetitore scansionando il suo codice QR;<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> ne mostra alcuni noti.
|
||||
Se hai più ripetitori, riceverai messaggi su tutti.</p>
|
||||
<p>Puoi <strong>aggiungere</strong> un ripetitore scansionando il suo codice QR;
|
||||
<a href="https://chatmail.at/relays">chatmail.at/relays</a> mostra alcuni ripetitori noti.
|
||||
Se hai più ripetitori, riceverai i messaggi su tutti.
|
||||
I contatti vengono a conoscenza automaticamente dei tuoi ripetitori attuali quando invii loro un messaggio.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>L’opzione <strong>predefinita</strong> definisce quella a cui i tuoi partner di chat invieranno messaggi futuri.</p>
|
||||
<p>Tocca un ripetitore per impostarlo come <strong>utilizzato per l’invio</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Se <strong>rimuovi</strong> un ripetitore,
|
||||
assicurati che un altro ripetitore predefinito sia stato utilizzato per un periodo di tempo sufficiente.
|
||||
In caso contrario, i messaggi dei tuoi interlocutori di chat non ti arriveranno.In caso di dubbio, rimuovilo in seguito.</p>
|
||||
i contatti che conoscono solo quel ripetitore potrebbero non raggiungerti finché non invierai loro un nuovo messaggio.
|
||||
Per rimanere raggiungibile nel frattempo, seleziona <strong>Nascondi dai Contatti</strong> nella finestra di dialogo di conferma
|
||||
invece di rimuoverlo immediatamente.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Per <strong>visualizzare</strong> nuovamente un ripetitore nascosto, toccalo.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1547,7 +1566,7 @@ Vedi <a href="https://delta.chat/en/2023-05-22-webxdc-security">qui per la stori
|
||||
<li>
|
||||
<p>A partire dal 2023, <a href="https://cure53.de">Cure53</a> ha analizzato sia la crittografia del trasporto delle
|
||||
Connessioni di rete di Delta Chat e una configurazione del server di posta riproducibile come
|
||||
<a href="https://delta.chat/it/serverguide">consigliato su questo sito</a>.
|
||||
<a href="https://delta.chat/serverguide">consigliato su questo sito</a>.
|
||||
Puoi leggere ulteriori informazioni sull’audit <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">sul nostro blog</a>
|
||||
o leggere il <a href="https://delta.chat/assets/blog/MER-01-report.pdf">rapporto completo qui</a>.</p>
|
||||
</li>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<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="#ondersteunt-delta-chat-afbeeldingen-videos-en-ander-soort-bijlagen">Ondersteunt Delta Chat afbeeldingen, video’s en ander soort bijlagen?</a></li>
|
||||
<li><a href="#multiple-accounts">What are profiles? How can I switch between them?</a></li>
|
||||
<li><a href="#wie-kan-mijn-profielfoto-zien">Wie kan mijn profielfoto zien?</a></li>
|
||||
<li><a href="#signature">Can I set a Bio/Status with Delta Chat?</a></li>
|
||||
@@ -14,6 +13,7 @@
|
||||
<li><a href="#wat-betekent-die-groene-stip">Wat betekent die groene stip?</a></li>
|
||||
<li><a href="#wat-betekenen-de-vinkjes-naast-verzonden-berichten">Wat betekenen de vinkjes naast verzonden berichten?</a></li>
|
||||
<li><a href="#edit">Correct typos and delete messages after sending</a></li>
|
||||
<li><a href="#mediaquality">How is media quality handled?</a></li>
|
||||
<li><a href="#ephemeralmsgs">How do disappearing messages work?</a></li>
|
||||
<li><a href="#delold">Wat gebeurt er als ik ‘Oude berichten van server verwijderen’ inschakel?</a></li>
|
||||
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
|
||||
@@ -218,19 +218,6 @@ You can also add a little introduction message.</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="ondersteunt-delta-chat-afbeeldingen-videos-en-ander-soort-bijlagen">
|
||||
|
||||
|
||||
Ondersteunt Delta Chat afbeeldingen, video’s en ander soort bijlagen? <a href="#ondersteunt-delta-chat-afbeeldingen-videos-en-ander-soort-bijlagen" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Yes. 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>Attachment-</strong>
|
||||
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
|
||||
|
||||
<p>Om de prestaties te verhogen, worden afbeeldingen standaard geoptimaliseerd en verkleind verstuurd, maar je kunt ze als een bestand verzenden om het origineel te sturen.</p>
|
||||
|
||||
<h3 id="multiple-accounts">
|
||||
|
||||
|
||||
@@ -410,6 +397,32 @@ Notifications are not sent and there is no time limit.</p>
|
||||
<p>Note, that the original message may still be received by chat members
|
||||
who could have already replied, forwarded, saved, screenshotted or otherwise copied the message.</p>
|
||||
|
||||
<h3 id="mediaquality">
|
||||
|
||||
|
||||
How is media quality handled? <a href="#mediaquality" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<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>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>In regions with worse connectivity,
|
||||
you can choose higher compression at <strong>Settings → Chats → Outgoing Media Quality</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<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>
|
||||
|
||||
<h3 id="ephemeralmsgs">
|
||||
|
||||
|
||||
@@ -1024,22 +1037,26 @@ Relays are operated by different groups and people.</p>
|
||||
<p>By default, after installation, a relay is <strong>automatically set up</strong>,
|
||||
so you do not need to care about that.
|
||||
However, if you want to,
|
||||
you can configure relays at At <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
you can configure relays at <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>You can <strong>add</strong> a relay by scanning its QR code;
|
||||
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.</p>
|
||||
<a href="https://chatmail.at/relays">chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.
|
||||
Contacts learn your current relays automatically when you message them.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
|
||||
<p>Tap on a relay to set it as <strong>used for sending</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If you <strong>remove</strong> a relay,
|
||||
make sure another default relay was used for a sufficient amount of time.
|
||||
Otherwise, messages from your chat partners won’t reach you.
|
||||
If in doubt, remove later.</p>
|
||||
contacts who only know this relay may not reach you until you message them again.
|
||||
To stay reachable in the meantime, choose <strong>Hide from Contacts</strong> in the confirmation dialog
|
||||
instead of removing it right away.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>To <strong>show</strong> a hidden relay again, tap on it.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1555,7 +1572,7 @@ Lees <a href="https://delta.chat/en/2023-05-22-webxdc-security">hier het volledi
|
||||
<li>
|
||||
<p>Aan het begin van 2023 heeft <a href="https://cure53.de">Cure53</a> de transportversleuteling van
|
||||
Delta Chats netwerkverbindingen getest, evenals de e-mailserveropzet zoals
|
||||
<a href="https://delta.chat/nl/serverguide">beschreven op onze site</a>.
|
||||
<a href="https://delta.chat/serverguide">beschreven op onze site</a>.
|
||||
Meer informatie over deze test is te lezen <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">op ons blog</a>
|
||||
of in het <a href="https://delta.chat/assets/blog/MER-01-report.pdf">volledige verslag</a>.</p>
|
||||
</li>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<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="#czy-delta-chat-obsługuje-obrazy-filmy-i-inne-załączniki">Czy Delta Chat obsługuje obrazy, filmy i inne załączniki?</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">Can I set a Bio/Status with Delta Chat?</a></li>
|
||||
@@ -14,6 +13,7 @@
|
||||
<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">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">How can I delete my chat profile?</a></li>
|
||||
@@ -215,19 +215,6 @@ You can also add a little introduction message.</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="czy-delta-chat-obsługuje-obrazy-filmy-i-inne-załączniki">
|
||||
|
||||
|
||||
Czy Delta Chat obsługuje obrazy, filmy i inne załączniki? <a href="#czy-delta-chat-obsługuje-obrazy-filmy-i-inne-załączniki" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Tak. 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>Attachment-</strong>
|
||||
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
|
||||
|
||||
<p>Ze względu na wydajność obrazy są domyślnie optymalizowane i wysyłane w mniejszym rozmiarze, ale można je wysłać jako „plik”, aby zachować oryginał.</p>
|
||||
|
||||
<h3 id="multiple-accounts">
|
||||
|
||||
|
||||
@@ -392,6 +379,32 @@ has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
|
||||
|
||||
<p>Pamiętaj, że oryginalną wiadomość nadal mogą otrzymać członkowie czatu, którzy mogli już odpowiedzieć, przesłać dalej, zapisać, wykonać zrzut ekranu lub w inny sposób skopiować wiadomość.</p>
|
||||
|
||||
<h3 id="mediaquality">
|
||||
|
||||
|
||||
How is media quality handled? <a href="#mediaquality" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<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>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>In regions with worse connectivity,
|
||||
you can choose higher compression at <strong>Settings → Chats → Outgoing Media Quality</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<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>
|
||||
|
||||
<h3 id="ephemeralmsgs">
|
||||
|
||||
|
||||
@@ -946,22 +959,26 @@ Relays are operated by different groups and people.</p>
|
||||
<p>By default, after installation, a relay is <strong>automatically set up</strong>,
|
||||
so you do not need to care about that.
|
||||
However, if you want to,
|
||||
you can configure relays at At <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
you can configure relays at <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>You can <strong>add</strong> a relay by scanning its QR code;
|
||||
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.</p>
|
||||
<a href="https://chatmail.at/relays">chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.
|
||||
Contacts learn your current relays automatically when you message them.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
|
||||
<p>Tap on a relay to set it as <strong>used for sending</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If you <strong>remove</strong> a relay,
|
||||
make sure another default relay was used for a sufficient amount of time.
|
||||
Otherwise, messages from your chat partners won’t reach you.
|
||||
If in doubt, remove later.</p>
|
||||
contacts who only know this relay may not reach you until you message them again.
|
||||
To stay reachable in the meantime, choose <strong>Hide from Contacts</strong> in the confirmation dialog
|
||||
instead of removing it right away.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>To <strong>show</strong> a hidden relay again, tap on it.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1396,7 +1413,7 @@ od najnowszych do najstarszych:</p>
|
||||
<p>W kwietniu 2023 r. naprawiliśmy problemy z bezpieczeństwem i prywatnością w funkcji „aplikacje internetowe udostępniane na czacie”, związane z awariami piaskownicy, szczególnie w przypadku Chromium. Następnie przeprowadziliśmy niezależny audyt bezpieczeństwa od Cure53 i wszystkie wykryte problemy zostały naprawione w aplikacji z serii 1.36 wydanej w kwietniu 2023 r. <a href="https://delta.chat/en/2023-05-22-webxdc-security">Pełną historię bezpieczeństwa end-to-end w sieci można znaleźć tutaj</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>W marcu 2023 r. firma <a href="https://cure53.de">Cure53</a> przeanalizowała zarówno szyfrowanie transportu połączeń sieciowych Delta Chat, jak i powtarzalną konfigurację serwera pocztowego zgodnie z <a href="https://delta.chat/pl/serverguide">zaleceniami na tej stronie</a>. Możesz przeczytać więcej o audycie <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">na naszym blogu</a> lub przeczytać pełny raport <a href="https://delta.chat/assets/blog/MER-01-report.pdf">tutaj</a>.</p>
|
||||
<p>W marcu 2023 r. firma <a href="https://cure53.de">Cure53</a> przeanalizowała zarówno szyfrowanie transportu połączeń sieciowych Delta Chat, jak i powtarzalną konfigurację serwera pocztowego zgodnie z <a href="https://delta.chat/serverguide">zaleceniami na tej stronie</a>. Możesz przeczytać więcej o audycie <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">na naszym blogu</a> lub przeczytać pełny raport <a href="https://delta.chat/assets/blog/MER-01-report.pdf">tutaj</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>W 2020 r. firma <a href="https://includesecurity.com">Include Security</a> przeanalizowała biblioteki Rust <a href="https://github.com/deltachat/deltachat-core-rust/">core</a>, <a href="https://github.com/async-email/async-imap">IMAP</a>, <a href="https://github.com/async-email/async-smtp">SMTP</a> i <a href="https://github.com/async-email/async-native-tls">TLS</a> Delta Chat. Nie znalazła żadnych problemów krytycznych ani poważnych. W raporcie zwrócono uwagę na kilka słabych punktów o średniej wadze – same w sobie nie stanowią zagrożenia dla użytkowników Delta Chat, ponieważ zależą od środowiska, w którym używany jest Delta Chat. Ze względu na użyteczność i kompatybilność nie możemy złagodzić wszystkich z nich i zdecydowaliśmy się przedstawić zalecenia dotyczące bezpieczeństwa zagrożonym użytkownikom. Pełny raport można przeczytać <a href="https://delta.chat/assets/blog/2020-second-security-review.pdf">tutaj</a>.</p>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<li><a href="#howtoe2ee">Como posso encontrar pessoas para conversar?</a></li>
|
||||
<li><a href="#por-que-um-chat-está-marcado-como-solicitação">Por que um chat está marcado como “Solicitação”?</a></li>
|
||||
<li><a href="#como-posso-colocar-dois-dos-meus-amigos-em-contato-um-com-o-outro">Como posso colocar dois dos meus amigos em contato um com o outro?</a></li>
|
||||
<li><a href="#dá-para-mandar-imagens-vídeos-e-outros-anexos-pelo-delta-chat">Dá para mandar imagens, vídeos e outros anexos pelo Delta Chat?</a></li>
|
||||
<li><a href="#multiple-accounts">O que são perfis? Como posso alternar entre eles?</a></li>
|
||||
<li><a href="#quem-consegue-ver-a-imagem-do-meu-perfil">Quem consegue ver a imagem do meu perfil?</a></li>
|
||||
<li><a href="#signature">Posso definir uma Bio/Assinatura com o Delta Chat?</a></li>
|
||||
@@ -14,6 +13,7 @@
|
||||
<li><a href="#o-que-significa-o-ponto-verde">O que significa o ponto verde?</a></li>
|
||||
<li><a href="#o-que-significam-os-carrapatos-mostrados-ao-lado-das-mensagens-de-saída">O que significam os carrapatos mostrados ao lado das mensagens de saída?</a></li>
|
||||
<li><a href="#edit">Corrigir erros de digitação e excluir mensagens após o envio</a></li>
|
||||
<li><a href="#mediaquality">How is media quality handled?</a></li>
|
||||
<li><a href="#ephemeralmsgs">Como funcionam as mensagens efêmeras?</a></li>
|
||||
<li><a href="#delold">O que acontece se eu ativar a opção “Apagar mensagens do dispositivo”?</a></li>
|
||||
<li><a href="#remove-account">Como posso excluir minha conta?</a></li>
|
||||
@@ -218,19 +218,6 @@ Você também pode adicionar uma pequena mensagem de apresentação.</p>
|
||||
<p>O segundo contato receberá um <strong>cartão</strong>
|
||||
e poderá tocar nele para começar a conversar com o primeiro contato.</p>
|
||||
|
||||
<h3 id="dá-para-mandar-imagens-vídeos-e-outros-anexos-pelo-delta-chat">
|
||||
|
||||
|
||||
Dá para mandar imagens, vídeos e outros anexos pelo Delta Chat? <a href="#dá-para-mandar-imagens-vídeos-e-outros-anexos-pelo-delta-chat" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Sim. Imagens, vídeos, arquivos, mensagens de voz etc. podem ser enviados usando os botões de<img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Anexo-</strong>
|
||||
ou <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Mensagem de Voz</strong></p>
|
||||
|
||||
<p>Para fins de desempenho, as imagens são otimizadas e enviadas em um tamanho menor por padrão, mas você pode enviá-las como um “arquivo” para preservar o original.</p>
|
||||
|
||||
<h3 id="multiple-accounts">
|
||||
|
||||
|
||||
@@ -409,6 +396,32 @@ Não são enviadas notificações e não há limite de tempo.</p>
|
||||
<p>Observe que a mensagem original ainda pode ser recebida pelos membros do conversa
|
||||
que podem ter respondido, encaminhado, salvo, capturado a tela ou copiado a mensagem.</p>
|
||||
|
||||
<h3 id="mediaquality">
|
||||
|
||||
|
||||
How is media quality handled? <a href="#mediaquality" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Imagens, vídeos, arquivos, mensagens de voz etc. podem ser enviados usando os botões de<img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Anexo-</strong>
|
||||
ou <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Mensagem de Voz</strong></p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<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>In regions with worse connectivity,
|
||||
you can choose higher compression at <strong>Settings → Chats → Outgoing Media Quality</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<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>
|
||||
|
||||
<h3 id="ephemeralmsgs">
|
||||
|
||||
|
||||
@@ -1025,22 +1038,26 @@ Relays are operated by different groups and people.</p>
|
||||
<p>By default, after installation, a relay is <strong>automatically set up</strong>,
|
||||
so you do not need to care about that.
|
||||
However, if you want to,
|
||||
you can configure relays at At <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
you can configure relays at <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>You can <strong>add</strong> a relay by scanning its QR code;
|
||||
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.</p>
|
||||
<a href="https://chatmail.at/relays">chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.
|
||||
Contacts learn your current relays automatically when you message them.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
|
||||
<p>Tap on a relay to set it as <strong>used for sending</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If you <strong>remove</strong> a relay,
|
||||
make sure another default relay was used for a sufficient amount of time.
|
||||
Otherwise, messages from your chat partners won’t reach you.
|
||||
If in doubt, remove later.</p>
|
||||
contacts who only know this relay may not reach you until you message them again.
|
||||
To stay reachable in the meantime, choose <strong>Hide from Contacts</strong> in the confirmation dialog
|
||||
instead of removing it right away.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>To <strong>show</strong> a hidden relay again, tap on it.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1556,7 +1573,7 @@ See <a href="https://delta.chat/en/2023-05-22-webxdc-security">here for the full
|
||||
<li>
|
||||
<p>2023 March, <a href="https://cure53.de">Cure53</a> analyzed both the transport encryption of
|
||||
Delta Chat’s network connections and a reproducible mail server setup as
|
||||
<a href="https://delta.chat/pt/serverguide">recommended on this site</a>.
|
||||
<a href="https://delta.chat/serverguide">recommended on this site</a>.
|
||||
You can read more about the audit <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">on our blog</a>
|
||||
or read the <a href="https://delta.chat/assets/blog/MER-01-report.pdf">full report here</a>.</p>
|
||||
</li>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<li><a href="#howtoe2ee">Как найти людей для общения?</a></li>
|
||||
<li><a href="#почему-чат-помечен-как-запрос">Почему чат помечен как “Запрос”?</a></li>
|
||||
<li><a href="#как-я-могу-связать-двух-своих-друзей-друг-с-другом">Как я могу связать двух своих друзей друг с другом?</a></li>
|
||||
<li><a href="#поддерживает-ли-delta-chat-изображения-видео-и-другие-вложения">Поддерживает ли Delta Chat изображения, видео и другие вложения?</a></li>
|
||||
<li><a href="#multiple-accounts">Что такое профили? Как я могу переключатся между ними?</a></li>
|
||||
<li><a href="#кто-видит-изображение-моего-профиля">Кто видит изображение моего профиля?</a></li>
|
||||
<li><a href="#signature">Могу ли я установить статус/подпись в Delta Chat?</a></li>
|
||||
@@ -14,6 +13,7 @@
|
||||
<li><a href="#что-означает-зеленая-точка">Что означает зеленая точка?</a></li>
|
||||
<li><a href="#что-означают-галочки-рядом-с-исходящими-сообщениями">Что означают галочки рядом с исходящими сообщениями?</a></li>
|
||||
<li><a href="#edit">Исправление опечаток и удаление сообщений после отправки</a></li>
|
||||
<li><a href="#mediaquality">Как обеспечивается качество мультимедиа?</a></li>
|
||||
<li><a href="#ephemeralmsgs">Как работают исчезающие сообщения?</a></li>
|
||||
<li><a href="#delold">Что произойдет, если я включу функцию “Удалять сообщения с устройства”?</a></li>
|
||||
<li><a href="#remove-account">Как удалить свой профиль в чате?</a></li>
|
||||
@@ -214,19 +214,6 @@
|
||||
<p>Второй контакт получит <strong>карточку</strong>
|
||||
на которую можно нажать, чтобы начать общение с первым контактом.</p>
|
||||
|
||||
<h3 id="поддерживает-ли-delta-chat-изображения-видео-и-другие-вложения">
|
||||
|
||||
|
||||
Поддерживает ли Delta Chat изображения, видео и другие вложения? <a href="#поддерживает-ли-delta-chat-изображения-видео-и-другие-вложения" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Да. Изображения, видео, файлы, голосовые сообщения и т.д. можно отправлять с помощью кнопок <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Вложение</strong>
|
||||
или <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Голосовое сообщение</strong>.</p>
|
||||
|
||||
<p>Для повышения производительности, изображения оптимизируются и отправляются по умолчанию в уменьшенном размере, но вы можете отправить их как “файл”, чтобы сохранить оригинал.</p>
|
||||
|
||||
<h3 id="multiple-accounts">
|
||||
|
||||
|
||||
@@ -406,6 +393,32 @@
|
||||
<p>Обратите внимание, что исходное сообщение все еще может быть получено участниками чата
|
||||
которые могли уже ответить, переслать, сохранить, сделать скриншот или иным образом скопировать сообщение.</p>
|
||||
|
||||
<h3 id="mediaquality">
|
||||
|
||||
|
||||
Как обеспечивается качество мультимедиа? <a href="#mediaquality" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Изображения, видео, файлы, голосовые сообщения и т.д. можно отправлять с помощью кнопок <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Вложение</strong>
|
||||
или <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Голосовое сообщение</strong>.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>По умолчанию сжатие обеспечивает <strong>быструю и эффективную доставку</strong> сообщений, учитывая ограничения по объему данных и хранилищу у всех пользователей.
|
||||
Это идеально подходит для повседневного общения.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>В регионах с плохим качеством связи,
|
||||
вы можете выбрать более высокую степень сжатия в меню <strong>Настройки → Чаты → Качество отправляемых медиафайлов</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Если вам необходимо отправить мультимедийный файл в <strong>исходном качестве</strong>, используйте значок <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Прикрепить → Файл</strong> в чате.
|
||||
Используйте этот метод с осторожностью, так как отправка файлов в исходном качестве значительно увеличит объем трафика как для вас, так и для всех участников чата.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="ephemeralmsgs">
|
||||
|
||||
|
||||
@@ -1024,22 +1037,26 @@ PIN-код разблокировки экрана, графический кл
|
||||
<p>По умолчанию, после установки, релей <strong>настраивается автоматически</strong>,
|
||||
поэтому вам не нужно об этом волноваться.
|
||||
Однако, если хотите,
|
||||
вы можете настроить релеи в <strong>Настройки → Дополнительно → Релеи</strong>:</p>
|
||||
вы можете настроить релеи в меню <strong>Настройки → Дополнительно → Релеи</strong>:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Вы можете <strong>добавить</strong> релей, отсканировав его QR-код;
|
||||
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> содержит список известных релеев.
|
||||
Если у вас несколько релеев, вы будете получать сообщения на все из них.</p>
|
||||
на сайте <a href="https://chatmail.at/relays">chatmail.at/relays</a> представлен список известных.
|
||||
Если у вас настроено несколько релеев, сообщения будут доставляться через все из них.
|
||||
Ваши контакты автоматически узнают о текущих используемых вами релеях при обмене сообщениями.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>По умолчанию</strong> определяет релей, на который ваши собеседники будут отправлять будущие сообщения.</p>
|
||||
<p>Нажмите на релей, чтобы установить его как <strong>используемый для отправки</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Если вы <strong>удаляете</strong> релей,
|
||||
убедитесь, что другой релей по умолчанию использовался в течение достаточного количества времени.
|
||||
В противном случае сообщения от ваших собеседников не будут доходить до вас.
|
||||
Если сомневаетесь, удалите его позже.</p>
|
||||
<p>Если вы <strong>удалите</strong> релей,
|
||||
контакты, которые знают только этот релей, не смогут связаться с вами до тех пор, пока вы снова не напишите им.
|
||||
Чтобы оставаться на связи в это время, выберите опцию <strong>Скрыть из контактов</strong> во всплывающем окне
|
||||
вместо того, чтобы удалять его сразу.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Чтобы снова <strong>показать</strong> скрытый релей, нажмите на него.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1369,12 +1386,10 @@ Delta Chat вместо этого использует реализацию Ope
|
||||
<p>Таким образом, серверы могут видеть только:</p>
|
||||
|
||||
<ul>
|
||||
<li>адрес отправителя и получателя</li>
|
||||
<li>а также размер сообщения.</li>
|
||||
<li>Адреса отправителя и получателя, по умолчанию генерируются случайным образом</li>
|
||||
<li>Размер сообщения</li>
|
||||
</ul>
|
||||
|
||||
<p>По умолчанию адреса генерируются случайным образом.</p>
|
||||
|
||||
<p>Все прочие метаданные сообщений, контактов и групп содержатся в части сообщений, защищённой сквозным шифрованием.</p>
|
||||
|
||||
<h3 id="device-seizure">
|
||||
@@ -1556,11 +1571,10 @@ Applied Cryptography в ETH Цюрихе и устранили все выявл
|
||||
См. здесь <a href="https://delta.chat/en/2023-05-22-webxdc-security">полную информацию о безопасности сквозного шифрования в Интернете</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>В Марте 2023 года, <a href="https://cure53.de">Cure53</a> проанализировал как протокол защиты транспортного уровня
|
||||
сетевого соединения Delta Chat, так и воспроизводимую установку почтового сервера,
|
||||
<a href="https://delta.chat/ru/serverguide">рекомендуемую на этом сайте</a>.
|
||||
Подробнее об аудите можно узнать <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">в нашем блоге</a>
|
||||
или прочитать <a href="https://delta.chat/assets/blog/MER-01-report.pdf">полный отчёт здесь</a>.</p>
|
||||
<p>В марте 2023 года компания <a href="https://cure53.de">Cure53</a> провела анализ шифрования транспортного уровня сетевых соединений Delta Chat, а также проверку воспроизводимой конфигурации почтового сервера,
|
||||
<a href="https://delta.chat/serverguide">рекомендованной на этом сайте</a>.
|
||||
Подробнее об аудите можно узнать <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">в нашем блоге</a>,
|
||||
а <a href="https://delta.chat/assets/blog/MER-01-report.pdf">полный отчет доступен по этой ссылке</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>2020 год, <a href="https://includesecurity.com">Include Security</a> проанализировала Delta
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<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="#podporuje-delta-chat-obrázky-videá-a-iné-prílohy">Podporuje Delta Chat obrázky, videá a iné prílohy?</a></li>
|
||||
<li><a href="#multiple-accounts">What are profiles? How can I switch between them?</a></li>
|
||||
<li><a href="#kto-vidí-moju-profilovú-fotku">Kto vidí moju profilovú fotku?</a></li>
|
||||
<li><a href="#signature">Can I set a Bio/Status with Delta Chat?</a></li>
|
||||
@@ -14,6 +13,7 @@
|
||||
<li><a href="#what-does-the-green-dot-mean">What does the green dot mean?</a></li>
|
||||
<li><a href="#čo-znamenajú-zaškrtnutia-zobrazené-vedľa-odchádzajúcich-správ">Čo znamenajú zaškrtnutia zobrazené vedľa odchádzajúcich správ?</a></li>
|
||||
<li><a href="#edit">Correct typos and delete messages after sending</a></li>
|
||||
<li><a href="#mediaquality">How is media quality handled?</a></li>
|
||||
<li><a href="#ephemeralmsgs">How do disappearing messages work?</a></li>
|
||||
<li><a href="#delold">What happens if I turn on “Delete Messages from Device”?</a></li>
|
||||
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
|
||||
@@ -218,19 +218,6 @@ You can also add a little introduction message.</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="podporuje-delta-chat-obrázky-videá-a-iné-prílohy">
|
||||
|
||||
|
||||
Podporuje Delta Chat obrázky, videá a iné prílohy? <a href="#podporuje-delta-chat-obrázky-videá-a-iné-prílohy" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Yes. 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>Attachment-</strong>
|
||||
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
|
||||
|
||||
<p>For performance, images are optimized and sent at a smaller size by default, but you can send it as a “file” to preserve the original.</p>
|
||||
|
||||
<h3 id="multiple-accounts">
|
||||
|
||||
|
||||
@@ -411,6 +398,32 @@ Notifications are not sent and there is no time limit.</p>
|
||||
<p>Note, that the original message may still be received by chat members
|
||||
who could have already replied, forwarded, saved, screenshotted or otherwise copied the message.</p>
|
||||
|
||||
<h3 id="mediaquality">
|
||||
|
||||
|
||||
How is media quality handled? <a href="#mediaquality" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<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>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>In regions with worse connectivity,
|
||||
you can choose higher compression at <strong>Settings → Chats → Outgoing Media Quality</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<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>
|
||||
|
||||
<h3 id="ephemeralmsgs">
|
||||
|
||||
|
||||
@@ -1030,22 +1043,26 @@ Relays are operated by different groups and people.</p>
|
||||
<p>By default, after installation, a relay is <strong>automatically set up</strong>,
|
||||
so you do not need to care about that.
|
||||
However, if you want to,
|
||||
you can configure relays at At <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
you can configure relays at <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>You can <strong>add</strong> a relay by scanning its QR code;
|
||||
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.</p>
|
||||
<a href="https://chatmail.at/relays">chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.
|
||||
Contacts learn your current relays automatically when you message them.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
|
||||
<p>Tap on a relay to set it as <strong>used for sending</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If you <strong>remove</strong> a relay,
|
||||
make sure another default relay was used for a sufficient amount of time.
|
||||
Otherwise, messages from your chat partners won’t reach you.
|
||||
If in doubt, remove later.</p>
|
||||
contacts who only know this relay may not reach you until you message them again.
|
||||
To stay reachable in the meantime, choose <strong>Hide from Contacts</strong> in the confirmation dialog
|
||||
instead of removing it right away.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>To <strong>show</strong> a hidden relay again, tap on it.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1561,7 +1578,7 @@ See <a href="https://delta.chat/en/2023-05-22-webxdc-security">here for the full
|
||||
<li>
|
||||
<p>2023 March, <a href="https://cure53.de">Cure53</a> analyzed both the transport encryption of
|
||||
Delta Chat’s network connections and a reproducible mail server setup as
|
||||
<a href="https://delta.chat/sk/serverguide">recommended on this site</a>.
|
||||
<a href="https://delta.chat/serverguide">recommended on this site</a>.
|
||||
You can read more about the audit <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">on our blog</a>
|
||||
or read the <a href="https://delta.chat/assets/blog/MER-01-report.pdf">full report here</a>.</p>
|
||||
</li>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<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="#a-mbulon-delta-chat-i-figura-video-dhe-bashkëngjitje-të-tjera">A mbulon Delta Chat-i figura, video dhe bashkëngjitje të tjera?</a></li>
|
||||
<li><a href="#multiple-accounts">What are profiles? How can I switch between them?</a></li>
|
||||
<li><a href="#kush-e-sheh-profilin-tim">Kush e sheh profilin tim?</a></li>
|
||||
<li><a href="#signature">Can I set a Bio/Status with Delta Chat?</a></li>
|
||||
@@ -14,6 +13,7 @@
|
||||
<li><a href="#çdo-të-thotë-pika-e-gjelbër">Ç’do të thotë pika e gjelbër?</a></li>
|
||||
<li><a href="#çduan-të-thonë-shenjat-e-shfaqura-pas-mesazheve-që-dërgohen">Ç’duan të thonë shenjat e shfaqura pas mesazheve që dërgohen?</a></li>
|
||||
<li><a href="#edit">Correct typos and delete messages after sending</a></li>
|
||||
<li><a href="#mediaquality">How is media quality handled?</a></li>
|
||||
<li><a href="#ephemeralmsgs">How do disappearing messages work?</a></li>
|
||||
<li><a href="#delold">Ç’ndodh, nëse aktivizoj “Fshi prej pajisjes mesazhe të vjetër”?</a></li>
|
||||
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
|
||||
@@ -218,19 +218,6 @@ You can also add a little introduction message.</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="a-mbulon-delta-chat-i-figura-video-dhe-bashkëngjitje-të-tjera">
|
||||
|
||||
|
||||
A mbulon Delta Chat-i figura, video dhe bashkëngjitje të tjera? <a href="#a-mbulon-delta-chat-i-figura-video-dhe-bashkëngjitje-të-tjera" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Po 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>Attachment-</strong>
|
||||
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
|
||||
|
||||
<p>Si parazgjedhje, për funksionim më të mirë, figurat optimizohen dhe dërgohen në madhësi më të vogël, por mund ta dërgoni si një “kartelë”, që të ruhet origjinali.</p>
|
||||
|
||||
<h3 id="multiple-accounts">
|
||||
|
||||
|
||||
@@ -410,6 +397,32 @@ Notifications are not sent and there is no time limit.</p>
|
||||
<p>Note, that the original message may still be received by chat members
|
||||
who could have already replied, forwarded, saved, screenshotted or otherwise copied the message.</p>
|
||||
|
||||
<h3 id="mediaquality">
|
||||
|
||||
|
||||
How is media quality handled? <a href="#mediaquality" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<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>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>In regions with worse connectivity,
|
||||
you can choose higher compression at <strong>Settings → Chats → Outgoing Media Quality</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<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>
|
||||
|
||||
<h3 id="ephemeralmsgs">
|
||||
|
||||
|
||||
@@ -1032,22 +1045,26 @@ Relays are operated by different groups and people.</p>
|
||||
<p>By default, after installation, a relay is <strong>automatically set up</strong>,
|
||||
so you do not need to care about that.
|
||||
However, if you want to,
|
||||
you can configure relays at At <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
you can configure relays at <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>You can <strong>add</strong> a relay by scanning its QR code;
|
||||
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.</p>
|
||||
<a href="https://chatmail.at/relays">chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.
|
||||
Contacts learn your current relays automatically when you message them.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
|
||||
<p>Tap on a relay to set it as <strong>used for sending</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If you <strong>remove</strong> a relay,
|
||||
make sure another default relay was used for a sufficient amount of time.
|
||||
Otherwise, messages from your chat partners won’t reach you.
|
||||
If in doubt, remove later.</p>
|
||||
contacts who only know this relay may not reach you until you message them again.
|
||||
To stay reachable in the meantime, choose <strong>Hide from Contacts</strong> in the confirmation dialog
|
||||
instead of removing it right away.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>To <strong>show</strong> a hidden relay again, tap on it.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1564,7 +1581,7 @@ Shihni <a href="https://delta.chat/en/2023-05-22-webxdc-security">këtu, për sh
|
||||
<li>
|
||||
<p>Në fillim të 2023-it, <a href="https://cure53.de">Cure53</a> analizoi qoftë fshehtëzimin
|
||||
e transporteve për lidhje rrjeti të Delta Chat-it, qoftë një formësim të riprodhueshëm
|
||||
shërbyesi poste si <a href="https://delta.chat/sq/serverguide">të rekomanduarin në këtë sajt</a>.
|
||||
shërbyesi poste si <a href="https://delta.chat/serverguide">të rekomanduarin në këtë sajt</a>.
|
||||
Mund të lexoni më tepër rreth auditimit <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">në blogun tonë</a>,
|
||||
ose të lexoni <a href="https://delta.chat/assets/blog/MER-01-report.pdf">raportin e plotë këtu</a>.</p>
|
||||
</li>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<li><a href="#howtoe2ee">Як мені знайти людей для спілкування?</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="#чи-підтримує-delta-chat-вкладення-у-вигляді-фото-відео-тощо">Чи підтримує Delta Chat вкладення у вигляді фото, відео тощо?</a></li>
|
||||
<li><a href="#multiple-accounts">Що таке профілі? Як я можу перемикатися між ними?</a></li>
|
||||
<li><a href="#хто-бачить-моє-зображення-профілю">Хто бачить моє зображення профілю?</a></li>
|
||||
<li><a href="#signature">Чи можу я встановити біографію/статус у Delta Chat?</a></li>
|
||||
@@ -14,6 +13,7 @@
|
||||
<li><a href="#що-означає-зелена-точка">Що означає зелена точка?</a></li>
|
||||
<li><a href="#що-означають-галочки-біля-вихідних-повідомлень">Що означають галочки біля вихідних повідомлень?</a></li>
|
||||
<li><a href="#edit">Виправлення помилок та видалення повідомлень після надсилання</a></li>
|
||||
<li><a href="#mediaquality">How is media quality handled?</a></li>
|
||||
<li><a href="#ephemeralmsgs">Як працюють повідомлення, що зникають?</a></li>
|
||||
<li><a href="#delold">Що станеться, якщо я ввімкну «Видаляти старі повідомлення з пристрою»?</a></li>
|
||||
</ul>
|
||||
@@ -204,19 +204,6 @@ You can also add a little introduction message.</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="чи-підтримує-delta-chat-вкладення-у-вигляді-фото-відео-тощо">
|
||||
|
||||
|
||||
Чи підтримує Delta Chat вкладення у вигляді фото, відео тощо? <a href="#чи-підтримує-delta-chat-вкладення-у-вигляді-фото-відео-тощо" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<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>Attachment-</strong>
|
||||
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
|
||||
|
||||
<p>З міркувань продуктивності, зображення оптимізовані та надсилаються в меншому розмірі за замовчуванням, але ви можете надіслати їх як «файл», щоб зберегти оригінал.</p>
|
||||
|
||||
<h3 id="multiple-accounts">
|
||||
|
||||
|
||||
@@ -381,6 +368,32 @@ has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
|
||||
|
||||
<p>Зауважте, що початкове повідомлення все ще може бути отримане учасниками чату які могли вже відповісти, переслати, зберегти, зробити знімок екрану або іншим чином скопіювати повідомлення.</p>
|
||||
|
||||
<h3 id="mediaquality">
|
||||
|
||||
|
||||
How is media quality handled? <a href="#mediaquality" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<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>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>In regions with worse connectivity,
|
||||
you can choose higher compression at <strong>Settings → Chats → Outgoing Media Quality</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<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>
|
||||
|
||||
<h3 id="ephemeralmsgs">
|
||||
|
||||
|
||||
@@ -932,22 +945,26 @@ Relays are operated by different groups and people.</p>
|
||||
<p>By default, after installation, a relay is <strong>automatically set up</strong>,
|
||||
so you do not need to care about that.
|
||||
However, if you want to,
|
||||
you can configure relays at At <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
you can configure relays at <strong>Settings → Advanced → Relays</strong>:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>You can <strong>add</strong> a relay by scanning its QR code;
|
||||
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.</p>
|
||||
<a href="https://chatmail.at/relays">chatmail.at/relays</a> shows some known ones.
|
||||
If you have multiple relays, you will receive messages on all of them.
|
||||
Contacts learn your current relays automatically when you message them.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
|
||||
<p>Tap on a relay to set it as <strong>used for sending</strong>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If you <strong>remove</strong> a relay,
|
||||
make sure another default relay was used for a sufficient amount of time.
|
||||
Otherwise, messages from your chat partners won’t reach you.
|
||||
If in doubt, remove later.</p>
|
||||
contacts who only know this relay may not reach you until you message them again.
|
||||
To stay reachable in the meantime, choose <strong>Hide from Contacts</strong> in the confirmation dialog
|
||||
instead of removing it right away.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>To <strong>show</strong> a hidden relay again, tap on it.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1374,7 +1391,7 @@ you need to look it up in the “keypairs” SQLite table of a profile backup ta
|
||||
<p>Починаючи з 2023 року, ми виправили проблеми з безпекою та конфіденційністю у функції “веб застосунків, що поширені у чаті”, пов’язані зі збоями в роботі пісочниці особливо в Chromium. Згодом ми отримали незалежний аудит безпеки аудит безпеки від Cure53, і всі знайдені проблеми були виправлені в серії додатків 1.36, випущених у квітні 2023 року. Повну історію про наскрізну безпеку в Інтернеті дивіться <a href="https://delta.chat/en/2023-05-22-webxdc-security">тут</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Починаючи з 2023 року <a href="https://cure53.de">Cure53</a> проаналізував транспортне шифрування мережевих з’єднань Delta Chat і відтворюване налаштування поштового сервера як <a href="https://delta.chat/uk/serverguide">рекомендовано на цьому сайті</a>. Ви можете прочитати більше про аудит <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">у нашому блозі</a> або прочитайте <a href="https://delta.chat/assets/blog/MER-01-report.pdf">повний звіт тут</a>.</p>
|
||||
<p>Починаючи з 2023 року <a href="https://cure53.de">Cure53</a> проаналізував транспортне шифрування мережевих з’єднань Delta Chat і відтворюване налаштування поштового сервера як <a href="https://delta.chat/serverguide">рекомендовано на цьому сайті</a>. Ви можете прочитати більше про аудит <a href="https://delta.chat/en/2023-03-27-third-independent-security-audit">у нашому блозі</a> або прочитайте <a href="https://delta.chat/assets/blog/MER-01-report.pdf">повний звіт тут</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>У 2020 році <a href="https://includesecurity.com">Include Security</a> проаналізувала Rust-<a href="https://github.com/deltachat/deltachat-core-rust/">ядро</a> Delta Chat і бібліотеки <a href="https://github.com/async-email/async-imap">IMAP</a>, <a href="https://github.com/async-email/async-smtp">SMTP</a> та <a href="https://github.com/async-email/async-native-tls">TLS</a>. Він не виявив критичних або серйозних проблем. У звіті виявлено кілька слабких місць середнього ступеня тяжкості – вони самі по собі не становлять загрози для користувачів Delta Chat оскільки вони залежать від середовища, у якому використовується Delta Chat. З міркувань зручності використання та сумісності ми не можемо пом’якшити їх усі тому вирішили надати рекомендації щодо безпеки користувачам, яким загрожує небезпека. Ви можете прочитати <a href="https://delta.chat/assets/blog/2020-second-security-review.pdf">повний звіт тут</a>.</p>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -14,7 +14,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/* Basic RPC Transport implementation */
|
||||
public abstract class BaseTransport implements Rpc.Transport {
|
||||
public abstract class BaseRpcTransport implements Rpc.RpcTransport {
|
||||
private final Map<Integer, SettableFuture<JsonNode>> requestFutures = new ConcurrentHashMap<>();
|
||||
private int requestId = 0;
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
@@ -9,16 +9,16 @@ import chat.delta.rpc.types.*;
|
||||
|
||||
public class Rpc {
|
||||
|
||||
public interface Transport {
|
||||
public interface RpcTransport {
|
||||
void call(String method, JsonNode... params) throws RpcException;
|
||||
<T> T callForResult(TypeReference<T> resultType, String method, JsonNode... params) throws RpcException;
|
||||
ObjectMapper getObjectMapper();
|
||||
}
|
||||
|
||||
public final Transport transport;
|
||||
public final RpcTransport transport;
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
public Rpc(Transport transport) {
|
||||
public Rpc(RpcTransport transport) {
|
||||
this.transport = transport;
|
||||
this.mapper = transport.getObjectMapper();
|
||||
}
|
||||
@@ -53,6 +53,11 @@ public class Rpc {
|
||||
return transport.callForResult(new TypeReference<Event>(){}, "get_next_event");
|
||||
}
|
||||
|
||||
/** Waits for at least one event and return a batch of events. */
|
||||
public java.util.List<Event> getNextEventBatch() throws RpcException {
|
||||
return transport.callForResult(new TypeReference<java.util.List<Event>>(){}, "get_next_event_batch");
|
||||
}
|
||||
|
||||
public Integer addAccount() throws RpcException {
|
||||
return transport.callForResult(new TypeReference<Integer>(){}, "add_account");
|
||||
}
|
||||
@@ -284,6 +289,7 @@ public class Rpc {
|
||||
* from a server encoded in a QR code.
|
||||
* - [Self::list_transports()] to get a list of all configured transports.
|
||||
* - [Self::delete_transport()] to remove a transport.
|
||||
* - [Self::set_transport_unpublished()] to set whether contacts see this transport.
|
||||
*/
|
||||
public void addOrUpdateTransport(Integer accountId, EnteredLoginParam param) throws RpcException {
|
||||
transport.call("add_or_update_transport", mapper.valueToTree(accountId), mapper.valueToTree(param));
|
||||
@@ -307,11 +313,22 @@ public class Rpc {
|
||||
* Returns the list of all email accounts that are used as a transport in the current profile.
|
||||
* Use [Self::add_or_update_transport()] to add or change a transport
|
||||
* and [Self::delete_transport()] to delete a transport.
|
||||
* Use [Self::list_transports_ex()] to additionally query
|
||||
* whether the transports are marked as 'unpublished'.
|
||||
*/
|
||||
public java.util.List<EnteredLoginParam> listTransports(Integer accountId) throws RpcException {
|
||||
return transport.callForResult(new TypeReference<java.util.List<EnteredLoginParam>>(){}, "list_transports", mapper.valueToTree(accountId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of all email accounts that are used as a transport in the current profile.
|
||||
* Use [Self::add_or_update_transport()] to add or change a transport
|
||||
* and [Self::delete_transport()] to delete a transport.
|
||||
*/
|
||||
public java.util.List<TransportListEntry> listTransportsEx(Integer accountId) throws RpcException {
|
||||
return transport.callForResult(new TypeReference<java.util.List<TransportListEntry>>(){}, "list_transports_ex", mapper.valueToTree(accountId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the transport with the specified email address
|
||||
* (i.e. [EnteredLoginParam::addr]).
|
||||
@@ -320,6 +337,22 @@ public class Rpc {
|
||||
transport.call("delete_transport", mapper.valueToTree(accountId), mapper.valueToTree(addr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change whether the transport is unpublished.
|
||||
* <p>
|
||||
* Unpublished transports are not advertised to contacts,
|
||||
* and self-sent messages are not sent there,
|
||||
* so that we don't cause extra messages to the corresponding inbox,
|
||||
* but can still receive messages from contacts who don't know our new transport addresses yet.
|
||||
* <p>
|
||||
* The default is false, but when the user updates from a version that didn't have this flag,
|
||||
* existing secondary transports are set to unpublished,
|
||||
* so that an existing transport address doesn't suddenly get spammed with a lot of messages.
|
||||
*/
|
||||
public void setTransportUnpublished(Integer accountId, String addr, Boolean unpublished) throws RpcException {
|
||||
transport.call("set_transport_unpublished", mapper.valueToTree(accountId), mapper.valueToTree(addr), mapper.valueToTree(unpublished));
|
||||
}
|
||||
|
||||
/** Signal an ongoing process to stop. */
|
||||
public void stopOngoingProcess(Integer accountId) throws RpcException {
|
||||
transport.call("stop_ongoing_process", mapper.valueToTree(accountId));
|
||||
@@ -363,7 +396,7 @@ public class Rpc {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets messages to be processed by the bot and returns their IDs.
|
||||
* (deprecated) Gets messages to be processed by the bot and returns their IDs.
|
||||
* <p>
|
||||
* Only messages with database ID higher than `last_msg_id` config value
|
||||
* are returned. After processing the messages, the bot should
|
||||
@@ -371,6 +404,13 @@ public class Rpc {
|
||||
* or manually updating the value to avoid getting already
|
||||
* processed messages.
|
||||
* <p>
|
||||
* Deprecated 2026-04: This returns the message's id as soon as the first part arrives,
|
||||
* even if it is not fully downloaded yet.
|
||||
* The bot needs to wait for the message to be fully downloaded.
|
||||
* Since this is usually not the desired behavior,
|
||||
* bots should instead use the #DC_EVENT_INCOMING_MSG / [`types::events::EventType::IncomingMsg`]
|
||||
* event for getting notified about new messages.
|
||||
* <p>
|
||||
* [`markseen_msgs`]: Self::markseen_msgs
|
||||
*/
|
||||
public java.util.List<Integer> getNextMsgs(Integer accountId) throws RpcException {
|
||||
@@ -378,7 +418,7 @@ public class Rpc {
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for messages to be processed by the bot and returns their IDs.
|
||||
* (deprecated) Waits for messages to be processed by the bot and returns their IDs.
|
||||
* <p>
|
||||
* This function is similar to [`get_next_msgs`],
|
||||
* but waits for internal new message notification before returning.
|
||||
@@ -389,6 +429,13 @@ public class Rpc {
|
||||
* To shutdown the bot, stopping I/O can be used to interrupt
|
||||
* pending or next `wait_next_msgs` call.
|
||||
* <p>
|
||||
* Deprecated 2026-04: This returns the message's id as soon as the first part arrives,
|
||||
* even if it is not fully downloaded yet.
|
||||
* The bot needs to wait for the message to be fully downloaded.
|
||||
* Since this is usually not the desired behavior,
|
||||
* bots should instead use the #DC_EVENT_INCOMING_MSG / [`types::events::EventType::IncomingMsg`]
|
||||
* event for getting notified about new messages.
|
||||
* <p>
|
||||
* [`get_next_msgs`]: Self::get_next_msgs
|
||||
*/
|
||||
public java.util.List<Integer> waitNextMsgs(Integer accountId) throws RpcException {
|
||||
@@ -405,14 +452,6 @@ public class Rpc {
|
||||
return transport.callForResult(new TypeReference<Integer>(){}, "estimate_auto_deletion_count", mapper.valueToTree(accountId), mapper.valueToTree(fromServer), mapper.valueToTree(seconds));
|
||||
}
|
||||
|
||||
public String initiateAutocryptKeyTransfer(Integer accountId) throws RpcException {
|
||||
return transport.callForResult(new TypeReference<String>(){}, "initiate_autocrypt_key_transfer", mapper.valueToTree(accountId));
|
||||
}
|
||||
|
||||
public void continueAutocryptKeyTransfer(Integer accountId, Integer messageId, String setupCode) throws RpcException {
|
||||
transport.call("continue_autocrypt_key_transfer", mapper.valueToTree(accountId), mapper.valueToTree(messageId), mapper.valueToTree(setupCode));
|
||||
}
|
||||
|
||||
public java.util.List<Integer> getChatlistEntries(Integer accountId, Integer listFlags, String queryString, Integer queryContactId) throws RpcException {
|
||||
return transport.callForResult(new TypeReference<java.util.List<Integer>>(){}, "get_chatlist_entries", mapper.valueToTree(accountId), mapper.valueToTree(listFlags), mapper.valueToTree(queryString), mapper.valueToTree(queryContactId));
|
||||
}
|
||||
@@ -504,6 +543,8 @@ public class Rpc {
|
||||
* if `checkQr()` returns `askVerifyContact` or `askVerifyGroup`
|
||||
* an out-of-band-verification can be joined using `secure_join()`
|
||||
* <p>
|
||||
* @deprecated as of 2026-03; use create_qr_svg(get_chat_securejoin_qr_code()) instead.
|
||||
* <p>
|
||||
* chat_id: If set to a group-chat-id,
|
||||
* the Verified-Group-Invite protocol is offered in the QR code;
|
||||
* works for protected groups as well as for normal groups.
|
||||
@@ -793,6 +834,16 @@ public class Rpc {
|
||||
transport.call("marknoticed_chat", mapper.valueToTree(accountId), mapper.valueToTree(chatId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the last incoming message in the chat as _fresh_.
|
||||
* <p>
|
||||
* UI can use this to offer a "mark unread" option,
|
||||
* so that already noticed chats get a badge counter again.
|
||||
*/
|
||||
public void markfreshChat(Integer accountId, Integer chatId) throws RpcException {
|
||||
transport.call("markfresh_chat", mapper.valueToTree(accountId), mapper.valueToTree(chatId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message that is immediately followed by the last seen
|
||||
* message.
|
||||
@@ -1206,12 +1257,19 @@ public class Rpc {
|
||||
* even if there is no concurrent call to [`CommandApi::provide_backup`],
|
||||
* but will fail after 60 seconds to avoid deadlocks.
|
||||
* <p>
|
||||
* @deprecated as of 2026-03; use `create_qr_svg(get_backup_qr())` instead.
|
||||
* <p>
|
||||
* Returns the QR code rendered as an SVG image.
|
||||
*/
|
||||
public String getBackupQrSvg(Integer accountId) throws RpcException {
|
||||
return transport.callForResult(new TypeReference<String>(){}, "get_backup_qr_svg", mapper.valueToTree(accountId));
|
||||
}
|
||||
|
||||
/** Renders the given text as a QR code SVG image. */
|
||||
public String createQrSvg(String text) throws RpcException {
|
||||
return transport.callForResult(new TypeReference<String>(){}, "create_qr_svg", mapper.valueToTree(text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a backup from a remote provider.
|
||||
* <p>
|
||||
|
||||
@@ -387,6 +387,8 @@ public abstract class EventType {
|
||||
public static class IncomingCallAccepted extends EventType {
|
||||
/** ID of the chat which the message belongs to. */
|
||||
public Integer chat_id;
|
||||
/** The call was accepted from this device (process). */
|
||||
public Boolean from_this_device;
|
||||
/** ID of the info message referring to the call. */
|
||||
public Integer msg_id;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ public class Message {
|
||||
public Boolean isEdited;
|
||||
public Boolean isForwarded;
|
||||
public Boolean isInfo;
|
||||
public Boolean isSetupmessage;
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||
public Integer originalMsgId;
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||
@@ -47,8 +46,6 @@ public class Message {
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||
public Integer savedMessageId;
|
||||
public Contact sender;
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||
public String setupCodeBegin;
|
||||
/**
|
||||
* True if the message was correctly encrypted&signed, false otherwise. Historically, UIs showed a small padlock on the message then.
|
||||
* <p>
|
||||
|
||||
@@ -42,7 +42,6 @@ public abstract class MessageLoadResult {
|
||||
public Boolean isEdited;
|
||||
public Boolean isForwarded;
|
||||
public Boolean isInfo;
|
||||
public Boolean isSetupmessage;
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||
public Integer originalMsgId;
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||
@@ -57,8 +56,6 @@ public abstract class MessageLoadResult {
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||
public Integer savedMessageId;
|
||||
public Contact sender;
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||
public String setupCodeBegin;
|
||||
/**
|
||||
* True if the message was correctly encrypted&signed, false otherwise. Historically, UIs showed a small padlock on the message then.
|
||||
* <p>
|
||||
|
||||
@@ -25,6 +25,8 @@ public abstract class Qr {
|
||||
public String fingerprint;
|
||||
/** Invite number. */
|
||||
public String invitenumber;
|
||||
/** Whether the inviter supports the new Securejoin v3 protocol */
|
||||
public Boolean is_v3;
|
||||
}
|
||||
|
||||
/** Ask the user whether to join the group. */
|
||||
@@ -41,6 +43,8 @@ public abstract class Qr {
|
||||
public String grpname;
|
||||
/** Invite number. */
|
||||
public String invitenumber;
|
||||
/** Whether the inviter supports the new Securejoin v3 protocol */
|
||||
public Boolean is_v3;
|
||||
}
|
||||
|
||||
/** Ask the user whether to join the broadcast channel. */
|
||||
@@ -55,6 +59,8 @@ public abstract class Qr {
|
||||
public String grpid;
|
||||
/** Invite number. */
|
||||
public String invitenumber;
|
||||
/** Whether the inviter supports the new Securejoin v3 protocol */
|
||||
public Boolean is_v3;
|
||||
/** The user-visible name of this broadcast channel */
|
||||
public String name;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/* Autogenerated file, do not edit manually */
|
||||
package chat.delta.rpc.types;
|
||||
|
||||
public class TransportListEntry {
|
||||
/** Whether this transport is set to 'unpublished'. See `set_transport_unpublished` / `setTransportUnpublished` for details. */
|
||||
public Boolean isUnpublished;
|
||||
/** The login data entered by the user. */
|
||||
public EnteredLoginParam param;
|
||||
}
|
||||
@@ -20,38 +20,66 @@ public class DcAccounts {
|
||||
}
|
||||
}
|
||||
|
||||
public DcEventEmitter getEventEmitter () { return new DcEventEmitter(getEventEmitterCPtr()); }
|
||||
public DcJsonrpcInstance getJsonrpcInstance () { return new DcJsonrpcInstance(getJsonrpcInstanceCPtr()); }
|
||||
public void startIo () {
|
||||
public DcEventEmitter getEventEmitter() {
|
||||
return new DcEventEmitter(getEventEmitterCPtr());
|
||||
}
|
||||
|
||||
public DcJsonrpcInstance getJsonrpcInstance() {
|
||||
return new DcJsonrpcInstance(getJsonrpcInstanceCPtr());
|
||||
}
|
||||
|
||||
public void startIo() {
|
||||
for (int accountId : getAll()) {
|
||||
DcContext acc = getAccount(accountId);
|
||||
if (acc.isEnabled()) {
|
||||
acc.startIo();
|
||||
}
|
||||
}
|
||||
};
|
||||
public native void startIo2 ();
|
||||
public native void stopIo ();
|
||||
public native void maybeNetwork ();
|
||||
public native void setPushDeviceToken (String token);
|
||||
public native boolean backgroundFetch (int timeoutSeconds);
|
||||
public native void stopBackgroundFetch ();
|
||||
}
|
||||
;
|
||||
|
||||
public native int migrateAccount (String dbfile);
|
||||
public native boolean removeAccount (int accountId);
|
||||
public native int[] getAll ();
|
||||
public DcContext getAccount (int accountId) { return new DcContext(getAccountCPtr(accountId)); }
|
||||
public DcContext getSelectedAccount () { return new DcContext(getSelectedAccountCPtr()); }
|
||||
public native boolean selectAccount (int accountId);
|
||||
public native void startIo2();
|
||||
|
||||
public native void stopIo();
|
||||
|
||||
public native void maybeNetwork();
|
||||
|
||||
public native void setPushDeviceToken(String token);
|
||||
|
||||
public native boolean backgroundFetch(int timeoutSeconds);
|
||||
|
||||
public native void stopBackgroundFetch();
|
||||
|
||||
public native int migrateAccount(String dbfile);
|
||||
|
||||
public native boolean removeAccount(int accountId);
|
||||
|
||||
public native int[] getAll();
|
||||
|
||||
public DcContext getAccount(int accountId) {
|
||||
return new DcContext(getAccountCPtr(accountId));
|
||||
}
|
||||
|
||||
public DcContext getSelectedAccount() {
|
||||
return new DcContext(getSelectedAccountCPtr());
|
||||
}
|
||||
|
||||
public native boolean selectAccount(int accountId);
|
||||
|
||||
// working with raw c-data
|
||||
private long accountsCPtr; // CAVE: the name is referenced in the JNI
|
||||
private native long createAccountsCPtr (String dir, DcEventChannel channel);
|
||||
private native void unrefAccountsCPtr ();
|
||||
private native long getEventEmitterCPtr ();
|
||||
private native long getJsonrpcInstanceCPtr ();
|
||||
private native long getAccountCPtr (int accountId);
|
||||
private native long getSelectedAccountCPtr ();
|
||||
|
||||
private native long createAccountsCPtr(String dir, DcEventChannel channel);
|
||||
|
||||
private native void unrefAccountsCPtr();
|
||||
|
||||
private native long getEventEmitterCPtr();
|
||||
|
||||
private native long getJsonrpcInstanceCPtr();
|
||||
|
||||
private native long getAccountCPtr(int accountId);
|
||||
|
||||
private native long getSelectedAccountCPtr();
|
||||
|
||||
public boolean isAllChatmail() {
|
||||
for (int accountId : getAll()) {
|
||||
|
||||
@@ -10,7 +10,8 @@ public class DcBackupProvider {
|
||||
return backupProviderCPtr != 0;
|
||||
}
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unref();
|
||||
}
|
||||
@@ -22,11 +23,14 @@ public class DcBackupProvider {
|
||||
}
|
||||
}
|
||||
|
||||
public native String getQr ();
|
||||
public native String getQrSvg ();
|
||||
public native void waitForReceiver ();
|
||||
public native String getQr();
|
||||
|
||||
public native String getQrSvg();
|
||||
|
||||
public native void waitForReceiver();
|
||||
|
||||
// working with raw c-data
|
||||
private long backupProviderCPtr; // CAVE: the name is referenced in the JNI
|
||||
|
||||
private native void unrefBackupProviderCPtr();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.b44t.messenger;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class DcChat {
|
||||
|
||||
public static final int DC_CHAT_TYPE_UNDEFINED = 0;
|
||||
@@ -10,13 +12,13 @@ public class DcChat {
|
||||
public static final int DC_CHAT_TYPE_IN_BROADCAST = 165;
|
||||
|
||||
public static final int DC_CHAT_NO_CHAT = 0;
|
||||
public final static int DC_CHAT_ID_ARCHIVED_LINK = 6;
|
||||
public final static int DC_CHAT_ID_ALLDONE_HINT = 7;
|
||||
public final static int DC_CHAT_ID_LAST_SPECIAL = 9;
|
||||
public static final int DC_CHAT_ID_ARCHIVED_LINK = 6;
|
||||
public static final int DC_CHAT_ID_ALLDONE_HINT = 7;
|
||||
public static final int DC_CHAT_ID_LAST_SPECIAL = 9;
|
||||
|
||||
public final static int DC_CHAT_VISIBILITY_NORMAL = 0;
|
||||
public final static int DC_CHAT_VISIBILITY_ARCHIVED = 1;
|
||||
public final static int DC_CHAT_VISIBILITY_PINNED = 2;
|
||||
public static final int DC_CHAT_VISIBILITY_NORMAL = 0;
|
||||
public static final int DC_CHAT_VISIBILITY_ARCHIVED = 1;
|
||||
public static final int DC_CHAT_VISIBILITY_PINNED = 2;
|
||||
|
||||
private int accountId;
|
||||
|
||||
@@ -25,29 +27,46 @@ public class DcChat {
|
||||
this.chatCPtr = chatCPtr;
|
||||
}
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefChatCPtr();
|
||||
chatCPtr = 0;
|
||||
}
|
||||
|
||||
public int getAccountId () { return accountId; }
|
||||
public native int getId ();
|
||||
public native int getType ();
|
||||
public native int getVisibility ();
|
||||
public native String getName ();
|
||||
public native String getMailinglistAddr();
|
||||
public native String getProfileImage ();
|
||||
public native int getColor ();
|
||||
public native boolean isEncrypted ();
|
||||
public native boolean isUnpromoted ();
|
||||
public native boolean isSelfTalk ();
|
||||
public native boolean isDeviceTalk ();
|
||||
public native boolean canSend ();
|
||||
public native boolean isSendingLocations();
|
||||
public native boolean isMuted ();
|
||||
public native boolean isContactRequest ();
|
||||
public int getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public native int getId();
|
||||
|
||||
public native int getType();
|
||||
|
||||
public native int getVisibility();
|
||||
|
||||
public native String getName();
|
||||
|
||||
public native String getMailinglistAddr();
|
||||
|
||||
public native String getProfileImage();
|
||||
|
||||
public native int getColor();
|
||||
|
||||
public native boolean isEncrypted();
|
||||
|
||||
public native boolean isUnpromoted();
|
||||
|
||||
public native boolean isSelfTalk();
|
||||
|
||||
public native boolean isDeviceTalk();
|
||||
|
||||
public native boolean canSend();
|
||||
|
||||
public native boolean isSendingLocations();
|
||||
|
||||
public native boolean isMuted();
|
||||
|
||||
public native boolean isContactRequest();
|
||||
|
||||
// aliases and higher-level tools
|
||||
|
||||
@@ -56,6 +75,16 @@ public class DcChat {
|
||||
return type != DC_CHAT_TYPE_SINGLE;
|
||||
}
|
||||
|
||||
public boolean shallLeaveBeforeDelete(DcContext dcContext) {
|
||||
if (isInBroadcast()) {
|
||||
final int[] members = dcContext.getChatContacts(getId());
|
||||
return Util.contains(members, DcContact.DC_CONTACT_ID_SELF);
|
||||
} else if (isMultiUser() && isEncrypted() && canSend() && !isOutBroadcast()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isMailingList() {
|
||||
return getType() == DC_CHAT_TYPE_MAILINGLIST;
|
||||
}
|
||||
@@ -63,6 +92,7 @@ public class DcChat {
|
||||
public boolean isInBroadcast() {
|
||||
return getType() == DC_CHAT_TYPE_IN_BROADCAST;
|
||||
}
|
||||
|
||||
public boolean isOutBroadcast() {
|
||||
return getType() == DC_CHAT_TYPE_OUT_BROADCAST;
|
||||
}
|
||||
@@ -70,7 +100,10 @@ public class DcChat {
|
||||
// working with raw c-data
|
||||
|
||||
private long chatCPtr; // CAVE: the name is referenced in the JNI
|
||||
private native void unrefChatCPtr();
|
||||
public long getChatCPtr () { return chatCPtr; }
|
||||
|
||||
private native void unrefChatCPtr();
|
||||
|
||||
public long getChatCPtr() {
|
||||
return chatCPtr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,19 +9,34 @@ public class DcChatlist {
|
||||
this.chatlistCPtr = chatlistCPtr;
|
||||
}
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefChatlistCPtr();
|
||||
chatlistCPtr = 0;
|
||||
}
|
||||
|
||||
public int getAccountId() { return accountId; }
|
||||
public native int getCnt ();
|
||||
public native int getChatId (int index);
|
||||
public DcChat getChat (int index) { return new DcChat(accountId, getChatCPtr(index)); }
|
||||
public native int getMsgId (int index);
|
||||
public DcMsg getMsg (int index) { return new DcMsg(getMsgCPtr(index)); }
|
||||
public DcLot getSummary(int index, DcChat chat) { return new DcLot(getSummaryCPtr(index, chat==null? 0 : chat.getChatCPtr())); }
|
||||
public int getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public native int getCnt();
|
||||
|
||||
public native int getChatId(int index);
|
||||
|
||||
public DcChat getChat(int index) {
|
||||
return new DcChat(accountId, getChatCPtr(index));
|
||||
}
|
||||
|
||||
public native int getMsgId(int index);
|
||||
|
||||
public DcMsg getMsg(int index) {
|
||||
return new DcMsg(getMsgCPtr(index));
|
||||
}
|
||||
|
||||
public DcLot getSummary(int index, DcChat chat) {
|
||||
return new DcLot(getSummaryCPtr(index, chat == null ? 0 : chat.getChatCPtr()));
|
||||
}
|
||||
|
||||
public class Item {
|
||||
public DcLot summary;
|
||||
@@ -39,8 +54,12 @@ public class DcChatlist {
|
||||
|
||||
// working with raw c-data
|
||||
private long chatlistCPtr; // CAVE: the name is referenced in the JNI
|
||||
|
||||
private native void unrefChatlistCPtr();
|
||||
private native long getChatCPtr (int index);
|
||||
private native long getMsgCPtr (int index);
|
||||
private native long getSummaryCPtr (int index, long chatCPtr);
|
||||
|
||||
private native long getChatCPtr(int index);
|
||||
|
||||
private native long getMsgCPtr(int index);
|
||||
|
||||
private native long getSummaryCPtr(int index, long chatCPtr);
|
||||
}
|
||||
|
||||
@@ -2,17 +2,18 @@ package com.b44t.messenger;
|
||||
|
||||
public class DcContact {
|
||||
|
||||
public final static int DC_CONTACT_ID_SELF = 1;
|
||||
public final static int DC_CONTACT_ID_INFO = 2;
|
||||
public final static int DC_CONTACT_ID_DEVICE = 5;
|
||||
public final static int DC_CONTACT_ID_LAST_SPECIAL = 9;
|
||||
public final static int DC_CONTACT_ID_NEW_CLASSIC_CONTACT= -1; // used by the UI, not valid to the core
|
||||
public final static int DC_CONTACT_ID_NEW_GROUP = -2; // - " -
|
||||
public final static int DC_CONTACT_ID_ADD_MEMBER = -3; // - " -
|
||||
public final static int DC_CONTACT_ID_QR_INVITE = -4; // - " -
|
||||
public final static int DC_CONTACT_ID_NEW_BROADCAST = -5; // - " -
|
||||
public final static int DC_CONTACT_ID_ADD_ACCOUNT = -6; // - " -
|
||||
public final static int DC_CONTACT_ID_NEW_UNENCRYPTED_GROUP = -7; // - " -
|
||||
public static final int DC_CONTACT_ID_SELF = 1;
|
||||
public static final int DC_CONTACT_ID_INFO = 2;
|
||||
public static final int DC_CONTACT_ID_DEVICE = 5;
|
||||
public static final int DC_CONTACT_ID_LAST_SPECIAL = 9;
|
||||
public static final int DC_CONTACT_ID_NEW_CLASSIC_CONTACT =
|
||||
-1; // used by the UI, not valid to the core
|
||||
public static final int DC_CONTACT_ID_NEW_GROUP = -2; // - " -
|
||||
public static final int DC_CONTACT_ID_ADD_MEMBER = -3; // - " -
|
||||
public static final int DC_CONTACT_ID_QR_INVITE = -4; // - " -
|
||||
public static final int DC_CONTACT_ID_NEW_BROADCAST = -5; // - " -
|
||||
public static final int DC_CONTACT_ID_ADD_ACCOUNT = -6; // - " -
|
||||
public static final int DC_CONTACT_ID_NEW_UNENCRYPTED_GROUP = -7; // - " -
|
||||
|
||||
public DcContact(long contactCPtr) {
|
||||
this.contactCPtr = contactCPtr;
|
||||
@@ -25,7 +26,6 @@ public class DcContact {
|
||||
contactCPtr = 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null || !(other instanceof DcContact)) {
|
||||
@@ -33,7 +33,7 @@ public class DcContact {
|
||||
}
|
||||
|
||||
DcContact that = (DcContact) other;
|
||||
return this.getId()==that.getId();
|
||||
return this.getId() == that.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,23 +46,38 @@ public class DcContact {
|
||||
return getAddr();
|
||||
}
|
||||
|
||||
public native int getId ();
|
||||
public native String getName ();
|
||||
public native String getAuthName ();
|
||||
public native String getDisplayName ();
|
||||
public native String getAddr ();
|
||||
public native int getId();
|
||||
|
||||
public native String getName();
|
||||
|
||||
public native String getAuthName();
|
||||
|
||||
public native String getDisplayName();
|
||||
|
||||
public native String getAddr();
|
||||
|
||||
public native String getProfileImage();
|
||||
public native int getColor ();
|
||||
public native String getStatus ();
|
||||
public native long getLastSeen ();
|
||||
|
||||
public native int getColor();
|
||||
|
||||
public native String getStatus();
|
||||
|
||||
public native long getLastSeen();
|
||||
|
||||
public native boolean wasSeenRecently();
|
||||
public native boolean isBlocked ();
|
||||
public native boolean isVerified ();
|
||||
public native boolean isKeyContact ();
|
||||
public native int getVerifierId ();
|
||||
public native boolean isBot ();
|
||||
|
||||
public native boolean isBlocked();
|
||||
|
||||
public native boolean isVerified();
|
||||
|
||||
public native boolean isKeyContact();
|
||||
|
||||
public native int getVerifierId();
|
||||
|
||||
public native boolean isBot();
|
||||
|
||||
// working with raw c-data
|
||||
private long contactCPtr; // CAVE: the name is referenced in the JNI
|
||||
|
||||
private native void unrefContactCPtr();
|
||||
}
|
||||
|
||||
@@ -2,95 +2,96 @@ package com.b44t.messenger;
|
||||
|
||||
public class DcContext {
|
||||
|
||||
public final static int DC_EVENT_INFO = 100;
|
||||
public final static int DC_EVENT_WARNING = 300;
|
||||
public final static int DC_EVENT_ERROR = 400;
|
||||
public final static int DC_EVENT_ERROR_SELF_NOT_IN_GROUP = 410;
|
||||
public final static int DC_EVENT_MSGS_CHANGED = 2000;
|
||||
public final static int DC_EVENT_REACTIONS_CHANGED = 2001;
|
||||
public final static int DC_EVENT_INCOMING_REACTION = 2002;
|
||||
public final static int DC_EVENT_INCOMING_WEBXDC_NOTIFY = 2003;
|
||||
public final static int DC_EVENT_INCOMING_MSG = 2005;
|
||||
public final static int DC_EVENT_MSGS_NOTICED = 2008;
|
||||
public final static int DC_EVENT_MSG_DELIVERED = 2010;
|
||||
public final static int DC_EVENT_MSG_FAILED = 2012;
|
||||
public final static int DC_EVENT_MSG_READ = 2015;
|
||||
public final static int DC_EVENT_CHAT_MODIFIED = 2020;
|
||||
public final static int DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED = 2021;
|
||||
public final static int DC_EVENT_CHAT_DELETED = 2023;
|
||||
public final static int DC_EVENT_CONTACTS_CHANGED = 2030;
|
||||
public final static int DC_EVENT_LOCATION_CHANGED = 2035;
|
||||
public final static int DC_EVENT_CONFIGURE_PROGRESS = 2041;
|
||||
public final static int DC_EVENT_IMEX_PROGRESS = 2051;
|
||||
public final static int DC_EVENT_IMEX_FILE_WRITTEN = 2052;
|
||||
public final static int DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060;
|
||||
public final static int DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061;
|
||||
public final static int DC_EVENT_CONNECTIVITY_CHANGED = 2100;
|
||||
public final static int DC_EVENT_SELFAVATAR_CHANGED = 2110;
|
||||
public final static int DC_EVENT_WEBXDC_STATUS_UPDATE = 2120;
|
||||
public final static int DC_EVENT_WEBXDC_INSTANCE_DELETED = 2121;
|
||||
public final static int DC_EVENT_WEBXDC_REALTIME_DATA = 2150;
|
||||
public final static int DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE = 2200;
|
||||
public final static int DC_EVENT_INCOMING_CALL = 2550;
|
||||
public final static int DC_EVENT_INCOMING_CALL_ACCEPTED = 2560;
|
||||
public final static int DC_EVENT_OUTGOING_CALL_ACCEPTED = 2570;
|
||||
public final static int DC_EVENT_CALL_ENDED = 2580;
|
||||
public final static int DC_EVENT_TRANSPORTS_MODIFIED = 2600;
|
||||
public static final int DC_EVENT_INFO = 100;
|
||||
public static final int DC_EVENT_WARNING = 300;
|
||||
public static final int DC_EVENT_ERROR = 400;
|
||||
public static final int DC_EVENT_ERROR_SELF_NOT_IN_GROUP = 410;
|
||||
public static final int DC_EVENT_MSGS_CHANGED = 2000;
|
||||
public static final int DC_EVENT_REACTIONS_CHANGED = 2001;
|
||||
public static final int DC_EVENT_INCOMING_REACTION = 2002;
|
||||
public static final int DC_EVENT_INCOMING_WEBXDC_NOTIFY = 2003;
|
||||
public static final int DC_EVENT_INCOMING_MSG = 2005;
|
||||
public static final int DC_EVENT_MSGS_NOTICED = 2008;
|
||||
public static final int DC_EVENT_MSG_DELIVERED = 2010;
|
||||
public static final int DC_EVENT_MSG_FAILED = 2012;
|
||||
public static final int DC_EVENT_MSG_READ = 2015;
|
||||
public static final int DC_EVENT_MSG_DELETED = 2016;
|
||||
public static final int DC_EVENT_CHAT_MODIFIED = 2020;
|
||||
public static final int DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED = 2021;
|
||||
public static final int DC_EVENT_CHAT_DELETED = 2023;
|
||||
public static final int DC_EVENT_CONTACTS_CHANGED = 2030;
|
||||
public static final int DC_EVENT_LOCATION_CHANGED = 2035;
|
||||
public static final int DC_EVENT_CONFIGURE_PROGRESS = 2041;
|
||||
public static final int DC_EVENT_IMEX_PROGRESS = 2051;
|
||||
public static final int DC_EVENT_IMEX_FILE_WRITTEN = 2052;
|
||||
public static final int DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060;
|
||||
public static final int DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061;
|
||||
public static final int DC_EVENT_CONNECTIVITY_CHANGED = 2100;
|
||||
public static final int DC_EVENT_SELFAVATAR_CHANGED = 2110;
|
||||
public static final int DC_EVENT_WEBXDC_STATUS_UPDATE = 2120;
|
||||
public static final int DC_EVENT_WEBXDC_INSTANCE_DELETED = 2121;
|
||||
public static final int DC_EVENT_WEBXDC_REALTIME_DATA = 2150;
|
||||
public static final int DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE = 2200;
|
||||
public static final int DC_EVENT_INCOMING_CALL = 2550;
|
||||
public static final int DC_EVENT_INCOMING_CALL_ACCEPTED = 2560;
|
||||
public static final int DC_EVENT_OUTGOING_CALL_ACCEPTED = 2570;
|
||||
public static final int DC_EVENT_CALL_ENDED = 2580;
|
||||
public static final int DC_EVENT_TRANSPORTS_MODIFIED = 2600;
|
||||
|
||||
public final static int DC_IMEX_EXPORT_SELF_KEYS = 1;
|
||||
public final static int DC_IMEX_IMPORT_SELF_KEYS = 2;
|
||||
public final static int DC_IMEX_EXPORT_BACKUP = 11;
|
||||
public final static int DC_IMEX_IMPORT_BACKUP = 12;
|
||||
public static final int DC_IMEX_EXPORT_SELF_KEYS = 1;
|
||||
public static final int DC_IMEX_IMPORT_SELF_KEYS = 2;
|
||||
public static final int DC_IMEX_EXPORT_BACKUP = 11;
|
||||
public static final int DC_IMEX_IMPORT_BACKUP = 12;
|
||||
|
||||
public final static int DC_GCL_VERIFIED_ONLY = 1;
|
||||
public final static int DC_GCL_ADD_SELF = 2;
|
||||
public final static int DC_GCL_ADDRESS = 0x04;
|
||||
public final static int DC_GCL_ARCHIVED_ONLY = 0x01;
|
||||
public final static int DC_GCL_NO_SPECIALS = 0x02;
|
||||
public final static int DC_GCL_ADD_ALLDONE_HINT = 0x04;
|
||||
public final static int DC_GCL_FOR_FORWARDING = 0x08;
|
||||
public static final int DC_GCL_VERIFIED_ONLY = 1;
|
||||
public static final int DC_GCL_ADD_SELF = 2;
|
||||
public static final int DC_GCL_ADDRESS = 0x04;
|
||||
public static final int DC_GCL_ARCHIVED_ONLY = 0x01;
|
||||
public static final int DC_GCL_NO_SPECIALS = 0x02;
|
||||
public static final int DC_GCL_ADD_ALLDONE_HINT = 0x04;
|
||||
public static final int DC_GCL_FOR_FORWARDING = 0x08;
|
||||
|
||||
public final static int DC_GCM_ADDDAYMARKER = 0x01;
|
||||
public static final int DC_GCM_ADDDAYMARKER = 0x01;
|
||||
|
||||
public final static int DC_QR_ASK_VERIFYCONTACT = 200;
|
||||
public final static int DC_QR_ASK_VERIFYGROUP = 202;
|
||||
public final static int DC_QR_ASK_JOIN_BROADCAST= 204;
|
||||
public final static int DC_QR_FPR_OK = 210;
|
||||
public final static int DC_QR_FPR_MISMATCH = 220;
|
||||
public final static int DC_QR_FPR_WITHOUT_ADDR = 230;
|
||||
public final static int DC_QR_ACCOUNT = 250;
|
||||
public final static int DC_QR_BACKUP2 = 252;
|
||||
public final static int DC_QR_BACKUP_TOO_NEW = 255;
|
||||
public final static int DC_QR_WEBRTC = 260;
|
||||
public final static int DC_QR_PROXY = 271;
|
||||
public final static int DC_QR_ADDR = 320;
|
||||
public final static int DC_QR_TEXT = 330;
|
||||
public final static int DC_QR_URL = 332;
|
||||
public final static int DC_QR_ERROR = 400;
|
||||
public final static int DC_QR_WITHDRAW_VERIFYCONTACT = 500;
|
||||
public final static int DC_QR_WITHDRAW_VERIFYGROUP = 502;
|
||||
public final static int DC_QR_WITHDRAW_JOINBROADCAST = 504;
|
||||
public final static int DC_QR_REVIVE_VERIFYCONTACT = 510;
|
||||
public final static int DC_QR_REVIVE_VERIFYGROUP = 512;
|
||||
public final static int DC_QR_REVIVE_JOINBROADCAST = 514;
|
||||
public final static int DC_QR_LOGIN = 520;
|
||||
public static final int DC_QR_ASK_VERIFYCONTACT = 200;
|
||||
public static final int DC_QR_ASK_VERIFYGROUP = 202;
|
||||
public static final int DC_QR_ASK_JOIN_BROADCAST = 204;
|
||||
public static final int DC_QR_FPR_OK = 210;
|
||||
public static final int DC_QR_FPR_MISMATCH = 220;
|
||||
public static final int DC_QR_FPR_WITHOUT_ADDR = 230;
|
||||
public static final int DC_QR_ACCOUNT = 250;
|
||||
public static final int DC_QR_BACKUP2 = 252;
|
||||
public static final int DC_QR_BACKUP_TOO_NEW = 255;
|
||||
public static final int DC_QR_WEBRTC = 260;
|
||||
public static final int DC_QR_PROXY = 271;
|
||||
public static final int DC_QR_ADDR = 320;
|
||||
public static final int DC_QR_TEXT = 330;
|
||||
public static final int DC_QR_URL = 332;
|
||||
public static final int DC_QR_ERROR = 400;
|
||||
public static final int DC_QR_WITHDRAW_VERIFYCONTACT = 500;
|
||||
public static final int DC_QR_WITHDRAW_VERIFYGROUP = 502;
|
||||
public static final int DC_QR_WITHDRAW_JOINBROADCAST = 504;
|
||||
public static final int DC_QR_REVIVE_VERIFYCONTACT = 510;
|
||||
public static final int DC_QR_REVIVE_VERIFYGROUP = 512;
|
||||
public static final int DC_QR_REVIVE_JOINBROADCAST = 514;
|
||||
public static final int DC_QR_LOGIN = 520;
|
||||
|
||||
public final static int DC_SOCKET_AUTO = 0;
|
||||
public final static int DC_SOCKET_SSL = 1;
|
||||
public final static int DC_SOCKET_STARTTLS = 2;
|
||||
public final static int DC_SOCKET_PLAIN = 3;
|
||||
public static final int DC_SOCKET_AUTO = 0;
|
||||
public static final int DC_SOCKET_SSL = 1;
|
||||
public static final int DC_SOCKET_STARTTLS = 2;
|
||||
public static final int DC_SOCKET_PLAIN = 3;
|
||||
|
||||
public final static int DC_SHOW_EMAILS_OFF = 0;
|
||||
public final static int DC_SHOW_EMAILS_ACCEPTED_CONTACTS = 1;
|
||||
public final static int DC_SHOW_EMAILS_ALL = 2;
|
||||
public static final int DC_SHOW_EMAILS_OFF = 0;
|
||||
public static final int DC_SHOW_EMAILS_ACCEPTED_CONTACTS = 1;
|
||||
public static final int DC_SHOW_EMAILS_ALL = 2;
|
||||
|
||||
public final static int DC_MEDIA_QUALITY_BALANCED = 0;
|
||||
public final static int DC_MEDIA_QUALITY_WORSE = 1;
|
||||
public static final int DC_MEDIA_QUALITY_BALANCED = 0;
|
||||
public static final int DC_MEDIA_QUALITY_WORSE = 1;
|
||||
|
||||
public final static int DC_CONNECTIVITY_NOT_CONNECTED = 1000;
|
||||
public final static int DC_CONNECTIVITY_CONNECTING = 2000;
|
||||
public final static int DC_CONNECTIVITY_WORKING = 3000;
|
||||
public final static int DC_CONNECTIVITY_CONNECTED = 4000;
|
||||
public static final int DC_CONNECTIVITY_NOT_CONNECTED = 1000;
|
||||
public static final int DC_CONNECTIVITY_CONNECTING = 2000;
|
||||
public static final int DC_CONNECTIVITY_WORKING = 3000;
|
||||
public static final int DC_CONNECTIVITY_CONNECTED = 4000;
|
||||
|
||||
private static final String CONFIG_ACCOUNT_ENABLED = "ui.enabled";
|
||||
private static final String CONFIG_MUTE_MENTIONS_IF_MUTED = "ui.mute_mentions_if_muted";
|
||||
@@ -117,115 +118,227 @@ public class DcContext {
|
||||
}
|
||||
}
|
||||
|
||||
public native int getAccountId ();
|
||||
public native int getAccountId();
|
||||
|
||||
// when using DcAccounts, use DcAccounts.getEventEmitter() instead
|
||||
public DcEventEmitter getEventEmitter () { return new DcEventEmitter(getEventEmitterCPtr()); }
|
||||
public DcEventEmitter getEventEmitter() {
|
||||
return new DcEventEmitter(getEventEmitterCPtr());
|
||||
}
|
||||
|
||||
public native void setStockTranslation (int stockId, String translation);
|
||||
public native String getBlobdir ();
|
||||
public native String getLastError ();
|
||||
public native void stopOngoingProcess ();
|
||||
public native int isConfigured ();
|
||||
public native boolean open (String passphrase);
|
||||
public native boolean isOpen ();
|
||||
public native void setStockTranslation(int stockId, String translation);
|
||||
|
||||
public native String getBlobdir();
|
||||
|
||||
public native String getLastError();
|
||||
|
||||
public native void stopOngoingProcess();
|
||||
|
||||
public native int isConfigured();
|
||||
|
||||
public native boolean open(String passphrase);
|
||||
|
||||
public native boolean isOpen();
|
||||
|
||||
// when using DcAccounts, use DcAccounts.startIo() instead
|
||||
public native void startIo ();
|
||||
public native void startIo();
|
||||
|
||||
// when using DcAccounts, use DcAccounts.stopIo() instead
|
||||
public native void stopIo ();
|
||||
public native void stopIo();
|
||||
|
||||
// when using DcAccounts, use DcAccounts.maybeNetwork() instead
|
||||
public native void maybeNetwork ();
|
||||
public native void maybeNetwork();
|
||||
|
||||
public native void setConfig(String key, String value);
|
||||
|
||||
public void setConfigInt(String key, int value) {
|
||||
setConfig(key, Integer.toString(value));
|
||||
}
|
||||
|
||||
public native boolean setConfigFromQr(String qr);
|
||||
|
||||
public native String getConfig(String key);
|
||||
|
||||
public int getConfigInt(String key) {
|
||||
return getConfigInt(key, 0);
|
||||
}
|
||||
|
||||
public int getConfigInt(String key, int defValue) {
|
||||
try {
|
||||
return Integer.parseInt(getConfig(key));
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return defValue;
|
||||
}
|
||||
|
||||
public native String getInfo();
|
||||
|
||||
public native int getConnectivity();
|
||||
|
||||
public native String getConnectivityHtml();
|
||||
|
||||
public native void imex(int what, String dir);
|
||||
|
||||
public native String imexHasBackup(String dir);
|
||||
|
||||
public DcBackupProvider newBackupProvider() {
|
||||
return new DcBackupProvider(newBackupProviderCPtr());
|
||||
}
|
||||
|
||||
public native boolean receiveBackup(String qr);
|
||||
|
||||
public native boolean mayBeValidAddr(String addr);
|
||||
|
||||
public native void setConfig (String key, String value);
|
||||
public void setConfigInt (String key, int value) { setConfig(key, Integer.toString(value)); }
|
||||
public native boolean setConfigFromQr (String qr);
|
||||
public native String getConfig (String key);
|
||||
public int getConfigInt (String key) { return getConfigInt(key, 0); }
|
||||
public int getConfigInt (String key, int defValue) { try{return Integer.parseInt(getConfig(key));} catch(Exception e) {} return defValue; }
|
||||
public native String getInfo ();
|
||||
public native int getConnectivity ();
|
||||
public native String getConnectivityHtml ();
|
||||
public native String initiateKeyTransfer ();
|
||||
public native void imex (int what, String dir);
|
||||
public native String imexHasBackup (String dir);
|
||||
public DcBackupProvider newBackupProvider () { return new DcBackupProvider(newBackupProviderCPtr()); }
|
||||
public native boolean receiveBackup (String qr);
|
||||
public native boolean mayBeValidAddr (String addr);
|
||||
public native int lookupContactIdByAddr(String addr);
|
||||
public native int[] getContacts (int flags, String query);
|
||||
public native int[] getBlockedContacts ();
|
||||
public DcContact getContact (int contact_id) { return new DcContact(getContactCPtr(contact_id)); }
|
||||
public native int createContact (String name, String addr);
|
||||
public native void blockContact (int id, int block);
|
||||
public native String getContactEncrInfo (int contact_id);
|
||||
public native boolean deleteContact (int id);
|
||||
public native int addAddressBook (String adrbook);
|
||||
public DcChatlist getChatlist (int listflags, String query, int queryId) { return new DcChatlist(getAccountId(), getChatlistCPtr(listflags, query, queryId)); }
|
||||
public DcChat getChat (int chat_id) { return new DcChat(getAccountId(), getChatCPtr(chat_id)); }
|
||||
public native String getChatEncrInfo (int chat_id);
|
||||
public native void markseenMsgs (int msg_ids[]);
|
||||
public native void marknoticedChat (int chat_id);
|
||||
public native void setChatVisibility (int chat_id, int visibility);
|
||||
public native int getChatIdByContactId (int contact_id);
|
||||
|
||||
public native int[] getContacts(int flags, String query);
|
||||
|
||||
public native int[] getBlockedContacts();
|
||||
|
||||
public DcContact getContact(int contact_id) {
|
||||
return new DcContact(getContactCPtr(contact_id));
|
||||
}
|
||||
|
||||
public native int createContact(String name, String addr);
|
||||
|
||||
public native void blockContact(int id, int block);
|
||||
|
||||
public native String getContactEncrInfo(int contact_id);
|
||||
|
||||
public native boolean deleteContact(int id);
|
||||
|
||||
public native int addAddressBook(String adrbook);
|
||||
|
||||
public DcChatlist getChatlist(int listflags, String query, int queryId) {
|
||||
return new DcChatlist(getAccountId(), getChatlistCPtr(listflags, query, queryId));
|
||||
}
|
||||
|
||||
public DcChat getChat(int chat_id) {
|
||||
return new DcChat(getAccountId(), getChatCPtr(chat_id));
|
||||
}
|
||||
|
||||
public native String getChatEncrInfo(int chat_id);
|
||||
|
||||
public native void markseenMsgs(int msg_ids[]);
|
||||
|
||||
public native void marknoticedChat(int chat_id);
|
||||
|
||||
public native void setChatVisibility(int chat_id, int visibility);
|
||||
|
||||
public native int getChatIdByContactId(int contact_id);
|
||||
|
||||
public native int createChatByContactId(int contact_id);
|
||||
public native int createGroupChat (String name);
|
||||
public native int createBroadcastList ();
|
||||
public native boolean isContactInChat (int chat_id, int contact_id);
|
||||
public native int addContactToChat (int chat_id, int contact_id);
|
||||
|
||||
public native int createGroupChat(String name);
|
||||
|
||||
public native int createBroadcastList();
|
||||
|
||||
public native boolean isContactInChat(int chat_id, int contact_id);
|
||||
|
||||
public native int addContactToChat(int chat_id, int contact_id);
|
||||
|
||||
public native int removeContactFromChat(int chat_id, int contact_id);
|
||||
public native void setDraft (int chat_id, DcMsg msg/*null=delete*/);
|
||||
public DcMsg getDraft (int chat_id) { return new DcMsg(getDraftCPtr(chat_id)); }
|
||||
public native int setChatName (int chat_id, String name);
|
||||
public native int setChatProfileImage (int chat_id, String name);
|
||||
public native int[] getChatMsgs (int chat_id, int flags, int marker1before);
|
||||
public native int[] searchMsgs (int chat_id, String query);
|
||||
public native int[] getFreshMsgs ();
|
||||
public native int[] getChatMedia (int chat_id, int type1, int type2, int type3);
|
||||
public native int[] getChatContacts (int chat_id);
|
||||
public native int getChatEphemeralTimer (int chat_id);
|
||||
public native boolean setChatEphemeralTimer (int chat_id, int timer);
|
||||
public native boolean setChatMuteDuration (int chat_id, long duration);
|
||||
public native void deleteChat (int chat_id);
|
||||
public native void blockChat (int chat_id);
|
||||
public native void acceptChat (int chat_id);
|
||||
public DcMsg getMsg (int msg_id) { return new DcMsg(getMsgCPtr(msg_id)); }
|
||||
public native void sendEditRequest (int msg_id, String text);
|
||||
public native String getMsgInfo (int id);
|
||||
public native String getMsgHtml (int msg_id);
|
||||
public native void downloadFullMsg (int msg_id);
|
||||
public native int getFreshMsgCount (int chat_id);
|
||||
|
||||
public native void setDraft(int chat_id, DcMsg msg /*null=delete*/);
|
||||
|
||||
public DcMsg getDraft(int chat_id) {
|
||||
return new DcMsg(getDraftCPtr(chat_id));
|
||||
}
|
||||
|
||||
public native int setChatName(int chat_id, String name);
|
||||
|
||||
public native int setChatProfileImage(int chat_id, String name);
|
||||
|
||||
public native int[] getChatMsgs(int chat_id, int flags, int marker1before);
|
||||
|
||||
public native int[] searchMsgs(int chat_id, String query);
|
||||
|
||||
public native int[] getFreshMsgs();
|
||||
|
||||
public native int[] getChatMedia(int chat_id, int type1, int type2, int type3);
|
||||
|
||||
public native int[] getChatContacts(int chat_id);
|
||||
|
||||
public native int getChatEphemeralTimer(int chat_id);
|
||||
|
||||
public native boolean setChatEphemeralTimer(int chat_id, int timer);
|
||||
|
||||
public native boolean setChatMuteDuration(int chat_id, long duration);
|
||||
|
||||
public native void deleteChat(int chat_id);
|
||||
|
||||
public native void blockChat(int chat_id);
|
||||
|
||||
public native void acceptChat(int chat_id);
|
||||
|
||||
public DcMsg getMsg(int msg_id) {
|
||||
return new DcMsg(getMsgCPtr(msg_id));
|
||||
}
|
||||
|
||||
public native void sendEditRequest(int msg_id, String text);
|
||||
|
||||
public native String getMsgInfo(int id);
|
||||
|
||||
public native String getMsgHtml(int msg_id);
|
||||
|
||||
public native void downloadFullMsg(int msg_id);
|
||||
|
||||
public native int getFreshMsgCount(int chat_id);
|
||||
|
||||
public native int estimateDeletionCount(boolean from_server, long seconds);
|
||||
public native void deleteMsgs (int msg_ids[]);
|
||||
public native void sendDeleteRequest (int msg_ids[]);
|
||||
public native void forwardMsgs (int msg_ids[], int chat_id);
|
||||
public native void saveMsgs (int msg_ids[]);
|
||||
public native boolean resendMsgs (int msg_ids[]);
|
||||
public native int sendMsg (int chat_id, DcMsg msg);
|
||||
public native int sendTextMsg (int chat_id, String text);
|
||||
|
||||
public native void deleteMsgs(int msg_ids[]);
|
||||
|
||||
public native void sendDeleteRequest(int msg_ids[]);
|
||||
|
||||
public native void forwardMsgs(int msg_ids[], int chat_id);
|
||||
|
||||
public native void saveMsgs(int msg_ids[]);
|
||||
|
||||
public native boolean resendMsgs(int msg_ids[]);
|
||||
|
||||
public native int sendMsg(int chat_id, DcMsg msg);
|
||||
|
||||
public native int sendTextMsg(int chat_id, String text);
|
||||
|
||||
public native boolean sendWebxdcStatusUpdate(int msg_id, String payload);
|
||||
|
||||
public native String getWebxdcStatusUpdates(int msg_id, int last_known_serial);
|
||||
public native void setWebxdcIntegration (String file);
|
||||
|
||||
public native void setWebxdcIntegration(String file);
|
||||
|
||||
public native int initWebxdcIntegration(int chat_id);
|
||||
public native int addDeviceMsg (String label, DcMsg msg);
|
||||
|
||||
public native int addDeviceMsg(String label, DcMsg msg);
|
||||
|
||||
public native boolean wasDeviceMsgEverAdded(String label);
|
||||
public DcLot checkQr (String qr) { return new DcLot(checkQrCPtr(qr)); }
|
||||
public native String getSecurejoinQr (int chat_id);
|
||||
public native String getSecurejoinQrSvg (int chat_id);
|
||||
public native String createQrSvg (String payload);
|
||||
public native int joinSecurejoin (String qr);
|
||||
public native void sendLocationsToChat (int chat_id, int seconds);
|
||||
|
||||
public DcLot checkQr(String qr) {
|
||||
return new DcLot(checkQrCPtr(qr));
|
||||
}
|
||||
|
||||
public native String getSecurejoinQr(int chat_id);
|
||||
|
||||
public native String getSecurejoinQrSvg(int chat_id);
|
||||
|
||||
public native String createQrSvg(String payload);
|
||||
|
||||
public native int joinSecurejoin(String qr);
|
||||
|
||||
public native void sendLocationsToChat(int chat_id, int seconds);
|
||||
|
||||
public native boolean isSendingLocationsToChat(int chat_id);
|
||||
public DcProvider getProviderFromEmailWithDns (String email) { long cptr = getProviderFromEmailWithDnsCPtr(email); return cptr!=0 ? new DcProvider(cptr) : null; }
|
||||
|
||||
public DcProvider getProviderFromEmailWithDns(String email) {
|
||||
long cptr = getProviderFromEmailWithDnsCPtr(email);
|
||||
return cptr != 0 ? new DcProvider(cptr) : null;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return !"0".equals(getConfig(CONFIG_ACCOUNT_ENABLED));
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
setConfigInt(CONFIG_ACCOUNT_ENABLED, enabled? 1 : 0);
|
||||
setConfigInt(CONFIG_ACCOUNT_ENABLED, enabled ? 1 : 0);
|
||||
if (enabled) {
|
||||
startIo();
|
||||
} else {
|
||||
@@ -238,13 +351,13 @@ public class DcContext {
|
||||
}
|
||||
|
||||
public void setMentionsEnabled(boolean enabled) {
|
||||
setConfigInt(CONFIG_MUTE_MENTIONS_IF_MUTED, enabled? 0 : 1);
|
||||
setConfigInt(CONFIG_MUTE_MENTIONS_IF_MUTED, enabled ? 0 : 1);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
String displayname = getConfig("displayname");
|
||||
if (displayname.isEmpty()) {
|
||||
displayname = getContact(DcContact.DC_CONTACT_ID_SELF).getAddr();
|
||||
displayname = getConfig("addr");
|
||||
}
|
||||
return displayname;
|
||||
}
|
||||
@@ -258,7 +371,7 @@ public class DcContext {
|
||||
}
|
||||
|
||||
public void setMuted(boolean muted) {
|
||||
setConfigInt("is_muted", muted? 1 : 0);
|
||||
setConfigInt("is_muted", muted ? 1 : 0);
|
||||
}
|
||||
|
||||
public void restartIo() {
|
||||
@@ -270,20 +383,32 @@ public class DcContext {
|
||||
/**
|
||||
* @return true if at least one chat has location streaming enabled
|
||||
*/
|
||||
public native boolean setLocation (float latitude, float longitude, float accuracy);
|
||||
public native boolean setLocation(float latitude, float longitude, float accuracy);
|
||||
|
||||
// working with raw c-data
|
||||
private long contextCPtr; // CAVE: the name is referenced in the JNI
|
||||
|
||||
private native long createContextCPtr(String osName, String dbfile);
|
||||
private native void unrefContextCPtr ();
|
||||
|
||||
private native void unrefContextCPtr();
|
||||
|
||||
private native long getEventEmitterCPtr();
|
||||
public native long createMsgCPtr (int viewtype);
|
||||
private native long getChatlistCPtr (int listflags, String query, int queryId);
|
||||
private native long getChatCPtr (int chat_id);
|
||||
private native long getMsgCPtr (int id);
|
||||
private native long getDraftCPtr (int id);
|
||||
private native long getContactCPtr (int id);
|
||||
private native long checkQrCPtr (String qr);
|
||||
private native long getProviderFromEmailWithDnsCPtr (String addr);
|
||||
|
||||
public native long createMsgCPtr(int viewtype);
|
||||
|
||||
private native long getChatlistCPtr(int listflags, String query, int queryId);
|
||||
|
||||
private native long getChatCPtr(int chat_id);
|
||||
|
||||
private native long getMsgCPtr(int id);
|
||||
|
||||
private native long getDraftCPtr(int id);
|
||||
|
||||
private native long getContactCPtr(int id);
|
||||
|
||||
private native long checkQrCPtr(String qr);
|
||||
|
||||
private native long getProviderFromEmailWithDnsCPtr(String addr);
|
||||
|
||||
private native long newBackupProviderCPtr();
|
||||
}
|
||||
|
||||
@@ -6,20 +6,27 @@ public class DcEvent {
|
||||
this.eventCPtr = eventCPtr;
|
||||
}
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefEventCPtr();
|
||||
eventCPtr = 0;
|
||||
}
|
||||
|
||||
public native int getId ();
|
||||
public native int getData1Int ();
|
||||
public native int getData2Int ();
|
||||
public native String getData2Str ();
|
||||
public native int getId();
|
||||
|
||||
public native int getData1Int();
|
||||
|
||||
public native int getData2Int();
|
||||
|
||||
public native String getData2Str();
|
||||
|
||||
public native byte[] getData2Blob();
|
||||
|
||||
public native int getAccountId();
|
||||
|
||||
// working with raw c-data
|
||||
private long eventCPtr; // CAVE: the name is referenced in the JNI
|
||||
|
||||
private native void unrefEventCPtr();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,10 @@ public class DcEventChannel {
|
||||
|
||||
// working with raw c-data
|
||||
private long eventChannelCPtr; // CAVE: the name is referenced in the JNI
|
||||
private native long createEventChannelCPtr ();
|
||||
private native void unrefEventChannelCPtr ();
|
||||
private native long getEventEmitterCPtr ();
|
||||
|
||||
private native long createEventChannelCPtr();
|
||||
|
||||
private native void unrefEventChannelCPtr();
|
||||
|
||||
private native long getEventEmitterCPtr();
|
||||
}
|
||||
|
||||
@@ -6,19 +6,22 @@ public class DcEventEmitter {
|
||||
this.eventEmitterCPtr = eventEmitterCPtr;
|
||||
}
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefEventEmitterCPtr();
|
||||
eventEmitterCPtr = 0;
|
||||
}
|
||||
|
||||
public DcEvent getNextEvent () {
|
||||
public DcEvent getNextEvent() {
|
||||
long eventCPtr = getNextEventCPtr();
|
||||
return eventCPtr == 0 ? null : new DcEvent(eventCPtr);
|
||||
}
|
||||
|
||||
// working with raw c-data
|
||||
private long eventEmitterCPtr; // CAVE: the name is referenced in the JNI
|
||||
private native long getNextEventCPtr ();
|
||||
|
||||
private native long getNextEventCPtr();
|
||||
|
||||
private native void unrefEventEmitterCPtr();
|
||||
}
|
||||
|
||||
@@ -6,16 +6,19 @@ public class DcJsonrpcInstance {
|
||||
this.jsonrpcInstanceCPtr = jsonrpcInstanceCPtr;
|
||||
}
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefJsonrpcInstanceCPtr();
|
||||
jsonrpcInstanceCPtr = 0;
|
||||
}
|
||||
|
||||
public native void request(String request);
|
||||
|
||||
public native String getNextResponse();
|
||||
|
||||
// working with raw c-data
|
||||
private long jsonrpcInstanceCPtr; // CAVE: the name is referenced in the JNI
|
||||
|
||||
private native void unrefJsonrpcInstanceCPtr();
|
||||
}
|
||||
|
||||
@@ -2,28 +2,35 @@ package com.b44t.messenger;
|
||||
|
||||
public class DcLot {
|
||||
|
||||
public final static int DC_TEXT1_DRAFT = 1;
|
||||
public final static int DC_TEXT1_USERNAME = 2;
|
||||
public final static int DC_TEXT1_SELF = 3;
|
||||
public static final int DC_TEXT1_DRAFT = 1;
|
||||
public static final int DC_TEXT1_USERNAME = 2;
|
||||
public static final int DC_TEXT1_SELF = 3;
|
||||
|
||||
public DcLot(long lotCPtr) {
|
||||
this.lotCPtr = lotCPtr;
|
||||
}
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefLotCPtr();
|
||||
lotCPtr = 0;
|
||||
}
|
||||
|
||||
public native String getText1 ();
|
||||
public native String getText1();
|
||||
|
||||
public native int getText1Meaning();
|
||||
public native String getText2 ();
|
||||
public native long getTimestamp ();
|
||||
public native int getState ();
|
||||
public native int getId ();
|
||||
|
||||
public native String getText2();
|
||||
|
||||
public native long getTimestamp();
|
||||
|
||||
public native int getState();
|
||||
|
||||
public native int getId();
|
||||
|
||||
// working with raw c-data
|
||||
private long lotCPtr; // CAVE: the name is referenced in the JNI
|
||||
|
||||
private native void unrefLotCPtr();
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package com.b44t.messenger;
|
||||
|
||||
/**
|
||||
* Contains a list of media entries, their respective positions and ability to move through it.
|
||||
*/
|
||||
/** Contains a list of media entries, their respective positions and ability to move through it. */
|
||||
public class DcMediaGalleryElement {
|
||||
|
||||
final int[] mediaMsgs;
|
||||
int position;
|
||||
final DcContext context;
|
||||
public DcMediaGalleryElement(int[] mediaMsgs, int position, DcContext context, boolean leftIsRecent) {
|
||||
|
||||
public DcMediaGalleryElement(
|
||||
int[] mediaMsgs, int position, DcContext context, boolean leftIsRecent) {
|
||||
this.mediaMsgs = mediaMsgs;
|
||||
this.position = position;
|
||||
this.context = context;
|
||||
@@ -33,7 +33,7 @@ public class DcMediaGalleryElement {
|
||||
}
|
||||
|
||||
public void moveToPosition(int newPosition) {
|
||||
if(newPosition < 0 || newPosition >= mediaMsgs.length)
|
||||
if (newPosition < 0 || newPosition >= mediaMsgs.length)
|
||||
throw new IllegalArgumentException("can't move outside of known area.");
|
||||
position = newPosition;
|
||||
}
|
||||
|
||||
@@ -1,67 +1,63 @@
|
||||
package com.b44t.messenger;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class DcMsg {
|
||||
|
||||
public final static int DC_MSG_UNDEFINED = 0;
|
||||
public final static int DC_MSG_TEXT = 10;
|
||||
public final static int DC_MSG_IMAGE = 20;
|
||||
public final static int DC_MSG_GIF = 21;
|
||||
public final static int DC_MSG_STICKER = 23;
|
||||
public final static int DC_MSG_AUDIO = 40;
|
||||
public final static int DC_MSG_VOICE = 41;
|
||||
public final static int DC_MSG_VIDEO = 50;
|
||||
public final static int DC_MSG_FILE = 60;
|
||||
public final static int DC_MSG_CALL = 71;
|
||||
public final static int DC_MSG_WEBXDC = 80;
|
||||
public final static int DC_MSG_VCARD = 90;
|
||||
public static final int DC_MSG_UNDEFINED = 0;
|
||||
public static final int DC_MSG_TEXT = 10;
|
||||
public static final int DC_MSG_IMAGE = 20;
|
||||
public static final int DC_MSG_GIF = 21;
|
||||
public static final int DC_MSG_STICKER = 23;
|
||||
public static final int DC_MSG_AUDIO = 40;
|
||||
public static final int DC_MSG_VOICE = 41;
|
||||
public static final int DC_MSG_VIDEO = 50;
|
||||
public static final int DC_MSG_FILE = 60;
|
||||
public static final int DC_MSG_CALL = 71;
|
||||
public static final int DC_MSG_WEBXDC = 80;
|
||||
public static final int DC_MSG_VCARD = 90;
|
||||
|
||||
public final static int DC_INFO_UNKNOWN = 0;
|
||||
public final static int DC_INFO_GROUP_NAME_CHANGED = 2;
|
||||
public final static int DC_INFO_GROUP_IMAGE_CHANGED = 3;
|
||||
public final static int DC_INFO_MEMBER_ADDED_TO_GROUP = 4;
|
||||
public final static int DC_INFO_MEMBER_REMOVED_FROM_GROUP = 5;
|
||||
public final static int DC_INFO_AUTOCRYPT_SETUP_MESSAGE = 6;
|
||||
public final static int DC_INFO_SECURE_JOIN_MESSAGE = 7;
|
||||
public final static int DC_INFO_LOCATIONSTREAMING_ENABLED = 8;
|
||||
public final static int DC_INFO_LOCATION_ONLY = 9;
|
||||
public final static int DC_INFO_EPHEMERAL_TIMER_CHANGED = 10;
|
||||
public final static int DC_INFO_PROTECTION_ENABLED = 11;
|
||||
public final static int DC_INFO_INVALID_UNENCRYPTED_MAIL = 13;
|
||||
public final static int DC_INFO_WEBXDC_INFO_MESSAGE = 32;
|
||||
public final static int DC_INFO_CHAT_E2EE = 50;
|
||||
public static final int DC_INFO_UNKNOWN = 0;
|
||||
public static final int DC_INFO_GROUP_NAME_CHANGED = 2;
|
||||
public static final int DC_INFO_GROUP_IMAGE_CHANGED = 3;
|
||||
public static final int DC_INFO_MEMBER_ADDED_TO_GROUP = 4;
|
||||
public static final int DC_INFO_MEMBER_REMOVED_FROM_GROUP = 5;
|
||||
public static final int DC_INFO_SECURE_JOIN_MESSAGE = 7;
|
||||
public static final int DC_INFO_LOCATIONSTREAMING_ENABLED = 8;
|
||||
public static final int DC_INFO_LOCATION_ONLY = 9;
|
||||
public static final int DC_INFO_EPHEMERAL_TIMER_CHANGED = 10;
|
||||
public static final int DC_INFO_PROTECTION_ENABLED = 11;
|
||||
public static final int DC_INFO_INVALID_UNENCRYPTED_MAIL = 13;
|
||||
public static final int DC_INFO_WEBXDC_INFO_MESSAGE = 32;
|
||||
public static final int DC_INFO_CHAT_E2EE = 50;
|
||||
public static final int DC_INFO_CHAT_DESCRIPTION_CHANGED = 70;
|
||||
|
||||
public final static int DC_STATE_UNDEFINED = 0;
|
||||
public final static int DC_STATE_IN_FRESH = 10;
|
||||
public final static int DC_STATE_IN_NOTICED = 13;
|
||||
public final static int DC_STATE_IN_SEEN = 16;
|
||||
public final static int DC_STATE_OUT_PREPARING = 18;
|
||||
public final static int DC_STATE_OUT_DRAFT = 19;
|
||||
public final static int DC_STATE_OUT_PENDING = 20;
|
||||
public final static int DC_STATE_OUT_FAILED = 24;
|
||||
public final static int DC_STATE_OUT_DELIVERED = 26;
|
||||
public final static int DC_STATE_OUT_MDN_RCVD = 28;
|
||||
public static final int DC_STATE_UNDEFINED = 0;
|
||||
public static final int DC_STATE_IN_FRESH = 10;
|
||||
public static final int DC_STATE_IN_NOTICED = 13;
|
||||
public static final int DC_STATE_IN_SEEN = 16;
|
||||
public static final int DC_STATE_OUT_PREPARING = 18;
|
||||
public static final int DC_STATE_OUT_DRAFT = 19;
|
||||
public static final int DC_STATE_OUT_PENDING = 20;
|
||||
public static final int DC_STATE_OUT_FAILED = 24;
|
||||
public static final int DC_STATE_OUT_DELIVERED = 26;
|
||||
public static final int DC_STATE_OUT_MDN_RCVD = 28;
|
||||
|
||||
public final static int DC_DOWNLOAD_DONE = 0;
|
||||
public final static int DC_DOWNLOAD_AVAILABLE = 10;
|
||||
public final static int DC_DOWNLOAD_FAILURE = 20;
|
||||
public final static int DC_DOWNLOAD_UNDECIPHERABLE = 30;
|
||||
public final static int DC_DOWNLOAD_IN_PROGRESS = 1000;
|
||||
public static final int DC_DOWNLOAD_DONE = 0;
|
||||
public static final int DC_DOWNLOAD_AVAILABLE = 10;
|
||||
public static final int DC_DOWNLOAD_FAILURE = 20;
|
||||
public static final int DC_DOWNLOAD_UNDECIPHERABLE = 30;
|
||||
public static final int DC_DOWNLOAD_IN_PROGRESS = 1000;
|
||||
|
||||
public static final int DC_MSG_NO_ID = 0;
|
||||
public final static int DC_MSG_ID_MARKER1 = 1;
|
||||
public final static int DC_MSG_ID_DAYMARKER = 9;
|
||||
public static final int DC_MSG_ID_MARKER1 = 1;
|
||||
public static final int DC_MSG_ID_DAYMARKER = 9;
|
||||
|
||||
public final static int DC_VIDEOCHATTYPE_UNKNOWN = 0;
|
||||
public final static int DC_VIDEOCHATTYPE_BASICWEBRTC = 1;
|
||||
|
||||
private static final String TAG = DcMsg.class.getSimpleName();
|
||||
public static final int DC_VIDEOCHATTYPE_UNKNOWN = 0;
|
||||
public static final int DC_VIDEOCHATTYPE_BASICWEBRTC = 1;
|
||||
|
||||
public DcMsg(DcContext context, int viewtype) {
|
||||
msgCPtr = context.createMsgCPtr(viewtype);
|
||||
@@ -94,12 +90,10 @@ public class DcMsg {
|
||||
}
|
||||
|
||||
DcMsg that = (DcMsg) other;
|
||||
return this.getId()==that.getId() && this.getId()!=0;
|
||||
return this.getId() == that.getId() && this.getId() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* If given a message, calculates the position of the message in the chat
|
||||
*/
|
||||
/** If given a message, calculates the position of the message in the chat */
|
||||
public static int getMessagePosition(DcMsg msg, DcContext dcContext) {
|
||||
int msgs[] = dcContext.getChatMsgs(msg.getChatId(), 0, 0);
|
||||
int startingPosition = -1;
|
||||
@@ -113,62 +107,116 @@ public class DcMsg {
|
||||
return startingPosition;
|
||||
}
|
||||
|
||||
public native int getId ();
|
||||
public native String getText ();
|
||||
public native String getSubject ();
|
||||
public native long getTimestamp ();
|
||||
public native long getSortTimestamp ();
|
||||
public native int getId();
|
||||
|
||||
public native String getText();
|
||||
|
||||
public native String getSubject();
|
||||
|
||||
public native long getTimestamp();
|
||||
|
||||
public native long getSortTimestamp();
|
||||
|
||||
public native boolean hasDeviatingTimestamp();
|
||||
public native boolean hasLocation ();
|
||||
private native int getViewType ();
|
||||
public int getType () { return getDownloadState()==DC_DOWNLOAD_DONE? getViewType() : DC_MSG_TEXT; }
|
||||
public native int getInfoType ();
|
||||
public native int getInfoContactId ();
|
||||
public native int getState ();
|
||||
public native int getDownloadState ();
|
||||
public native int getChatId ();
|
||||
public native int getFromId ();
|
||||
public native int getWidth (int def);
|
||||
public native int getHeight (int def);
|
||||
public native int getDuration ();
|
||||
|
||||
public native boolean hasLocation();
|
||||
|
||||
private native int getViewType();
|
||||
|
||||
public int getType() {
|
||||
return getDownloadState() == DC_DOWNLOAD_DONE ? getViewType() : DC_MSG_TEXT;
|
||||
}
|
||||
|
||||
public native int getInfoType();
|
||||
|
||||
public native int getInfoContactId();
|
||||
|
||||
public native int getState();
|
||||
|
||||
public native int getDownloadState();
|
||||
|
||||
public native int getChatId();
|
||||
|
||||
public native int getFromId();
|
||||
|
||||
public native int getWidth(int def);
|
||||
|
||||
public native int getHeight(int def);
|
||||
|
||||
public native int getDuration();
|
||||
|
||||
public native void lateFilingMediaSize(int width, int height, int duration);
|
||||
public DcLot getSummary (DcChat chat) { return new DcLot(getSummaryCPtr(chat.getChatCPtr())); }
|
||||
public native String getSummarytext (int approx_characters);
|
||||
public native int showPadlock ();
|
||||
public boolean hasFile () { String file = getFile(); return file!=null && !file.isEmpty(); }
|
||||
public native String getFile ();
|
||||
public native String getFilemime ();
|
||||
public native String getFilename ();
|
||||
public native long getFilebytes ();
|
||||
public native byte[] getWebxdcBlob (String filename);
|
||||
public JSONObject getWebxdcInfo () {
|
||||
|
||||
public DcLot getSummary(DcChat chat) {
|
||||
return new DcLot(getSummaryCPtr(chat.getChatCPtr()));
|
||||
}
|
||||
|
||||
public native String getSummarytext(int approx_characters);
|
||||
|
||||
public native int showPadlock();
|
||||
|
||||
public boolean hasFile() {
|
||||
String file = getFile();
|
||||
return file != null && !file.isEmpty();
|
||||
}
|
||||
|
||||
public native String getFile();
|
||||
|
||||
public native String getFilemime();
|
||||
|
||||
public native String getFilename();
|
||||
|
||||
public native long getFilebytes();
|
||||
|
||||
public native byte[] getWebxdcBlob(String filename);
|
||||
|
||||
public JSONObject getWebxdcInfo() {
|
||||
try {
|
||||
String json = getWebxdcInfoJson();
|
||||
if (json != null && !json.isEmpty()) return new JSONObject(json);
|
||||
} catch(Exception e) {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return new JSONObject();
|
||||
}
|
||||
public native String getWebxdcHref ();
|
||||
public native boolean isForwarded ();
|
||||
public native boolean isInfo ();
|
||||
public native boolean hasHtml ();
|
||||
public native String getSetupCodeBegin ();
|
||||
public native void setText (String text);
|
||||
public native void setSubject (String text);
|
||||
public native void setHtml (String text);
|
||||
public native void forceSticker ();
|
||||
|
||||
public native String getWebxdcHref();
|
||||
|
||||
public native boolean isForwarded();
|
||||
|
||||
public native boolean isInfo();
|
||||
|
||||
public native boolean hasHtml();
|
||||
|
||||
public native void setText(String text);
|
||||
|
||||
public native void setSubject(String text);
|
||||
|
||||
public native void setHtml(String text);
|
||||
|
||||
public native void forceSticker();
|
||||
|
||||
public native void setFileAndDeduplicate(String file, String name, String filemime);
|
||||
public native void setDimension (int width, int height);
|
||||
public native void setDuration (int duration);
|
||||
public native void setLocation (float latitude, float longitude);
|
||||
public native String getPOILocation ();
|
||||
public void setQuote (DcMsg quote) { setQuoteCPtr(quote.msgCPtr); }
|
||||
public native String getQuotedText ();
|
||||
public native String getError ();
|
||||
|
||||
public native void setDimension(int width, int height);
|
||||
|
||||
public native void setDuration(int duration);
|
||||
|
||||
public native void setLocation(float latitude, float longitude);
|
||||
|
||||
public native String getPOILocation();
|
||||
|
||||
public void setQuote(DcMsg quote) {
|
||||
setQuoteCPtr(quote.msgCPtr);
|
||||
}
|
||||
|
||||
public native String getQuotedText();
|
||||
|
||||
public native String getError();
|
||||
|
||||
public native String getOverrideSenderName();
|
||||
public native boolean isEdited ();
|
||||
|
||||
public native boolean isEdited();
|
||||
|
||||
public String getSenderName(DcContact dcContact) {
|
||||
String overrideName = getOverrideSenderName();
|
||||
@@ -179,7 +227,7 @@ public class DcMsg {
|
||||
}
|
||||
}
|
||||
|
||||
public DcMsg getQuotedMsg () {
|
||||
public DcMsg getQuotedMsg() {
|
||||
long cPtr = getQuotedMsgCPtr();
|
||||
return cPtr != 0 ? new DcMsg(cPtr) : null;
|
||||
}
|
||||
@@ -189,17 +237,18 @@ public class DcMsg {
|
||||
return cPtr != 0 ? new DcMsg(cPtr) : null;
|
||||
}
|
||||
|
||||
public native int getOriginalMsgId ();
|
||||
public native int getSavedMsgId ();
|
||||
public native int getOriginalMsgId();
|
||||
|
||||
public native int getSavedMsgId();
|
||||
|
||||
public boolean canSave() {
|
||||
// saving info-messages out of context results in confusion, see https://github.com/deltachat/deltachat-ios/issues/2567
|
||||
// saving info-messages out of context results in confusion, see
|
||||
// https://github.com/deltachat/deltachat-ios/issues/2567
|
||||
return !isInfo();
|
||||
}
|
||||
|
||||
public File getFileAsFile() {
|
||||
if(getFile()==null)
|
||||
throw new AssertionError("expected a file to be present.");
|
||||
if (getFile() == null) throw new AssertionError("expected a file to be present.");
|
||||
return new File(getFile());
|
||||
}
|
||||
|
||||
@@ -235,32 +284,44 @@ public class DcMsg {
|
||||
public boolean isFailed() {
|
||||
return (getState() == DC_STATE_OUT_FAILED) || (!TextUtils.isEmpty(getError()));
|
||||
}
|
||||
|
||||
public boolean isPreparing() {
|
||||
return getState() == DC_STATE_OUT_PREPARING;
|
||||
}
|
||||
|
||||
public boolean isSecure() {
|
||||
return showPadlock()!=0;
|
||||
return showPadlock() != 0;
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
return getState() == DC_STATE_OUT_PENDING;
|
||||
}
|
||||
|
||||
public boolean isDelivered() {
|
||||
return getState() == DC_STATE_OUT_DELIVERED;
|
||||
}
|
||||
|
||||
public boolean isRemoteRead() {
|
||||
return getState() == DC_STATE_OUT_MDN_RCVD;
|
||||
}
|
||||
|
||||
public boolean isSeen() {
|
||||
return getState() == DC_STATE_IN_SEEN;
|
||||
}
|
||||
|
||||
|
||||
// working with raw c-data
|
||||
private long msgCPtr; // CAVE: the name is referenced in the JNI
|
||||
private native void unrefMsgCPtr ();
|
||||
private native long getSummaryCPtr (long chatCPtr);
|
||||
private native void setQuoteCPtr (long quoteCPtr);
|
||||
private native long getQuotedMsgCPtr ();
|
||||
private native long getParentCPtr ();
|
||||
private native String getWebxdcInfoJson ();
|
||||
};
|
||||
|
||||
private native void unrefMsgCPtr();
|
||||
|
||||
private native long getSummaryCPtr(long chatCPtr);
|
||||
|
||||
private native void setQuoteCPtr(long quoteCPtr);
|
||||
|
||||
private native long getQuotedMsgCPtr();
|
||||
|
||||
private native long getParentCPtr();
|
||||
|
||||
private native String getWebxdcInfoJson();
|
||||
}
|
||||
;
|
||||
|
||||
@@ -2,25 +2,29 @@ package com.b44t.messenger;
|
||||
|
||||
public class DcProvider {
|
||||
|
||||
public final static int DC_PROVIDER_STATUS_OK = 1;
|
||||
public final static int DC_PROVIDER_STATUS_PREPARATION = 2;
|
||||
public final static int DC_PROVIDER_STATUS_BROKEN = 3;
|
||||
public static final int DC_PROVIDER_STATUS_OK = 1;
|
||||
public static final int DC_PROVIDER_STATUS_PREPARATION = 2;
|
||||
public static final int DC_PROVIDER_STATUS_BROKEN = 3;
|
||||
|
||||
public DcProvider(long providerCPtr) {
|
||||
this.providerCPtr = providerCPtr;
|
||||
}
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefProviderCPtr();
|
||||
providerCPtr = 0;
|
||||
}
|
||||
|
||||
public native int getStatus ();
|
||||
public native String getBeforeLoginHint ();
|
||||
public native String getOverviewPage ();
|
||||
public native int getStatus();
|
||||
|
||||
public native String getBeforeLoginHint();
|
||||
|
||||
public native String getOverviewPage();
|
||||
|
||||
// working with raw c-data
|
||||
private long providerCPtr; // CAVE: the name is referenced in the JNI
|
||||
|
||||
private native void unrefProviderCPtr();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.b44t.messenger;
|
||||
|
||||
import chat.delta.rpc.BaseTransport;
|
||||
import chat.delta.rpc.BaseRpcTransport;
|
||||
|
||||
/* RPC transport over C FFI */
|
||||
public class FFITransport extends BaseTransport {
|
||||
public class FFITransport extends BaseRpcTransport {
|
||||
private final DcJsonrpcInstance dcJsonrpcInstance;
|
||||
|
||||
public FFITransport(DcJsonrpcInstance dcJsonrpcInstance) {
|
||||
|
||||
@@ -4,20 +4,15 @@ import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by Sergej Kravcenko on 4/24/2017.
|
||||
* Copyright (c) 2017 Sergej Kravcenko
|
||||
*/
|
||||
|
||||
/** Created by Sergej Kravcenko on 4/24/2017. Copyright (c) 2017 Sergej Kravcenko */
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<StickyHeaderGridAdapter.ViewHolder> {
|
||||
public abstract class StickyHeaderGridAdapter
|
||||
extends RecyclerView.Adapter<StickyHeaderGridAdapter.ViewHolder> {
|
||||
public static final String TAG = "StickyHeaderGridAdapter";
|
||||
|
||||
public static final int TYPE_HEADER = 0;
|
||||
@@ -37,7 +32,6 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public int getSectionItemViewType() {
|
||||
return StickyHeaderGridAdapter.externalViewType(getItemViewType());
|
||||
}
|
||||
@@ -105,16 +99,16 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
return position == 0 ? TYPE_HEADER : TYPE_ITEM;
|
||||
}
|
||||
|
||||
static private int internalViewType(int type) {
|
||||
private static int internalViewType(int type) {
|
||||
return type & 0xFF;
|
||||
}
|
||||
|
||||
static private int externalViewType(int type) {
|
||||
private static int externalViewType(int type) {
|
||||
return type >> 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
final public int getItemCount() {
|
||||
public final int getItemCount() {
|
||||
if (mSections == null) {
|
||||
calculateSections();
|
||||
}
|
||||
@@ -123,7 +117,7 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
final public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
public final ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
final int internalType = internalViewType(viewType);
|
||||
final int externalType = externalViewType(viewType);
|
||||
|
||||
@@ -138,7 +132,7 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
}
|
||||
|
||||
@Override
|
||||
final public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
public final void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
if (mSections == null) {
|
||||
calculateSections();
|
||||
}
|
||||
@@ -149,12 +143,12 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
|
||||
switch (internalType) {
|
||||
case TYPE_HEADER:
|
||||
onBindHeaderViewHolder((HeaderViewHolder)holder, section);
|
||||
onBindHeaderViewHolder((HeaderViewHolder) holder, section);
|
||||
break;
|
||||
case TYPE_ITEM:
|
||||
final ItemViewHolder itemHolder = (ItemViewHolder)holder;
|
||||
final ItemViewHolder itemHolder = (ItemViewHolder) holder;
|
||||
final int offset = getItemSectionOffset(section, position);
|
||||
onBindItemViewHolder((ItemViewHolder)holder, section, offset);
|
||||
onBindItemViewHolder((ItemViewHolder) holder, section, offset);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidParameterException("invalid viewType: " + internalType);
|
||||
@@ -162,7 +156,7 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
}
|
||||
|
||||
@Override
|
||||
final public int getItemViewType(int position) {
|
||||
public final int getItemViewType(int position) {
|
||||
final int section = getAdapterPositionSection(position);
|
||||
final Section sectionObject = mSections.get(section);
|
||||
final int sectionPosition = position - sectionObject.position;
|
||||
@@ -227,15 +221,15 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
final Section sectionObject = mSections.get(section);
|
||||
final int localPosition = position - sectionObject.position;
|
||||
if (localPosition >= sectionObject.length) {
|
||||
throw new IndexOutOfBoundsException("localPosition: " + localPosition + " >=" + sectionObject.length);
|
||||
throw new IndexOutOfBoundsException(
|
||||
"localPosition: " + localPosition + " >=" + sectionObject.length);
|
||||
}
|
||||
|
||||
return localPosition - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the section index having item or header with provided
|
||||
* provider <code>position</code>.
|
||||
* Returns the section index having item or header with provided provider <code>position</code>.
|
||||
*
|
||||
* @param position adapter position
|
||||
* @return The section containing provided adapter position.
|
||||
@@ -261,9 +255,9 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the adapter position for given <code>section</code> header. Use
|
||||
* this only for {@link RecyclerView#scrollToPosition(int)} or similar functions.
|
||||
* Never directly manipulate adapter items using this position.
|
||||
* Returns the adapter position for given <code>section</code> header. Use this only for {@link
|
||||
* RecyclerView#scrollToPosition(int)} or similar functions. Never directly manipulate adapter
|
||||
* items using this position.
|
||||
*
|
||||
* @param section section to query
|
||||
* @return The adapter position.
|
||||
@@ -273,9 +267,9 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the adapter position for given <code>section</code> and
|
||||
* <code>offset</code>. Use this only for {@link RecyclerView#scrollToPosition(int)}
|
||||
* or similar functions. Never directly manipulate adapter items using this position.
|
||||
* Returns the adapter position for given <code>section</code> and <code>offset</code>. Use this
|
||||
* only for {@link RecyclerView#scrollToPosition(int)} or similar functions. Never directly
|
||||
* manipulate adapter items using this position.
|
||||
*
|
||||
* @param section section to query
|
||||
* @param position item position inside the <code>section</code>
|
||||
@@ -306,34 +300,32 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the view type of the <code>section</code> header for the purposes
|
||||
* of view recycling.
|
||||
* Return the view type of the <code>section</code> header for the purposes of view recycling.
|
||||
*
|
||||
* <p>The default implementation of this method returns 0, making the assumption of
|
||||
* a single view type for the headers. Unlike ListView adapters, types need not
|
||||
* be contiguous. Consider using id resources to uniquely identify item view types.
|
||||
* <p>The default implementation of this method returns 0, making the assumption of a single view
|
||||
* type for the headers. Unlike ListView adapters, types need not be contiguous. Consider using id
|
||||
* resources to uniquely identify item view types.
|
||||
*
|
||||
* @param section section to query
|
||||
* @return integer value identifying the type of the view needed to represent the header in
|
||||
* <code>section</code>. Type codes need not be contiguous.
|
||||
* @return integer value identifying the type of the view needed to represent the header in <code>
|
||||
* section</code>. Type codes need not be contiguous.
|
||||
*/
|
||||
public int getSectionHeaderViewType(int section) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the view type of the item at <code>position</code> in <code>section</code> for
|
||||
* the purposes of view recycling.
|
||||
* Return the view type of the item at <code>position</code> in <code>section</code> for the
|
||||
* purposes of view recycling.
|
||||
*
|
||||
* <p>The default implementation of this method returns 0, making the assumption of
|
||||
* a single view type for the adapter. Unlike ListView adapters, types need not
|
||||
* be contiguous. Consider using id resources to uniquely identify item view types.
|
||||
* <p>The default implementation of this method returns 0, making the assumption of a single view
|
||||
* type for the adapter. Unlike ListView adapters, types need not be contiguous. Consider using id
|
||||
* resources to uniquely identify item view types.
|
||||
*
|
||||
* @param section section to query
|
||||
* @param offset section position to query
|
||||
* @return integer value identifying the type of the view needed to represent the item at
|
||||
* <code>position</code> in <code>section</code>. Type codes need not be
|
||||
* contiguous.
|
||||
* @return integer value identifying the type of the view needed to represent the item at <code>
|
||||
* position</code> in <code>section</code>. Type codes need not be contiguous.
|
||||
*/
|
||||
public int getSectionItemViewType(int section, int offset) {
|
||||
return 0;
|
||||
@@ -350,22 +342,21 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when RecyclerView needs a new {@link HeaderViewHolder} of the given type to represent
|
||||
* a header.
|
||||
* <p>
|
||||
* This new HeaderViewHolder should be constructed with a new View that can represent the headers
|
||||
* of the given type. You can either create a new View manually or inflate it from an XML
|
||||
* Called when RecyclerView needs a new {@link HeaderViewHolder} of the given type to represent a
|
||||
* header.
|
||||
*
|
||||
* <p>This new HeaderViewHolder should be constructed with a new View that can represent the
|
||||
* headers of the given type. You can either create a new View manually or inflate it from an XML
|
||||
* layout file.
|
||||
* <p>
|
||||
* The new HeaderViewHolder will be used to display items of the adapter using
|
||||
* {@link #onBindHeaderViewHolder(HeaderViewHolder, int)}. Since it will be re-used to display
|
||||
* different items in the data set, it is a good idea to cache references to sub views of
|
||||
* the View to avoid unnecessary {@link View#findViewById(int)} calls.
|
||||
*
|
||||
* @param parent The ViewGroup into which the new View will be added after it is bound to
|
||||
* an adapter position.
|
||||
* <p>The new HeaderViewHolder will be used to display items of the adapter using {@link
|
||||
* #onBindHeaderViewHolder(HeaderViewHolder, int)}. Since it will be re-used to display different
|
||||
* items in the data set, it is a good idea to cache references to sub views of the View to avoid
|
||||
* unnecessary {@link View#findViewById(int)} calls.
|
||||
*
|
||||
* @param parent The ViewGroup into which the new View will be added after it is bound to an
|
||||
* adapter position.
|
||||
* @param headerType The view type of the new View.
|
||||
*
|
||||
* @return A new ViewHolder that holds a View of the given view type.
|
||||
* @see #getSectionHeaderViewType(int)
|
||||
* @see #onBindHeaderViewHolder(HeaderViewHolder, int)
|
||||
@@ -373,22 +364,21 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
public abstract HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int headerType);
|
||||
|
||||
/**
|
||||
* Called when RecyclerView needs a new {@link ItemViewHolder} of the given type to represent
|
||||
* an item.
|
||||
* <p>
|
||||
* This new ViewHolder should be constructed with a new View that can represent the items
|
||||
* of the given type. You can either create a new View manually or inflate it from an XML
|
||||
* layout file.
|
||||
* <p>
|
||||
* The new ViewHolder will be used to display items of the adapter using
|
||||
* {@link #onBindItemViewHolder(ItemViewHolder, int, int)}. Since it will be re-used to display
|
||||
* different items in the data set, it is a good idea to cache references to sub views of
|
||||
* the View to avoid unnecessary {@link View#findViewById(int)} calls.
|
||||
* Called when RecyclerView needs a new {@link ItemViewHolder} of the given type to represent an
|
||||
* item.
|
||||
*
|
||||
* @param parent The ViewGroup into which the new View will be added after it is bound to
|
||||
* an adapter position.
|
||||
* <p>This new ViewHolder should be constructed with a new View that can represent the items of
|
||||
* the given type. You can either create a new View manually or inflate it from an XML layout
|
||||
* file.
|
||||
*
|
||||
* <p>The new ViewHolder will be used to display items of the adapter using {@link
|
||||
* #onBindItemViewHolder(ItemViewHolder, int, int)}. Since it will be re-used to display different
|
||||
* items in the data set, it is a good idea to cache references to sub views of the View to avoid
|
||||
* unnecessary {@link View#findViewById(int)} calls.
|
||||
*
|
||||
* @param parent The ViewGroup into which the new View will be added after it is bound to an
|
||||
* adapter position.
|
||||
* @param itemType The view type of the new View.
|
||||
*
|
||||
* @return A new ViewHolder that holds a View of the given view type.
|
||||
* @see #getSectionItemViewType(int, int)
|
||||
* @see #onBindItemViewHolder(ItemViewHolder, int, int)
|
||||
@@ -396,19 +386,18 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
public abstract ItemViewHolder onCreateItemViewHolder(ViewGroup parent, int itemType);
|
||||
|
||||
/**
|
||||
* Called by RecyclerView to display the data at the specified position. This method should
|
||||
* update the contents of the {@link HeaderViewHolder#itemView} to reflect the header at the given
|
||||
* Called by RecyclerView to display the data at the specified position. This method should update
|
||||
* the contents of the {@link HeaderViewHolder#itemView} to reflect the header at the given
|
||||
* position.
|
||||
* <p>
|
||||
* Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
|
||||
* again if the position of the header changes in the data set unless the header itself is
|
||||
* invalidated or the new position cannot be determined. For this reason, you should only
|
||||
* use the <code>section</code> parameter while acquiring the
|
||||
* related header data inside this method and should not keep a copy of it. If you need the
|
||||
* position of a header later on (e.g. in a click listener), use
|
||||
* {@link HeaderViewHolder#getAdapterPosition()} which will have the updated adapter
|
||||
* position. Then you can use {@link #getAdapterPositionSection(int)} to get section index.
|
||||
*
|
||||
* <p>Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
|
||||
* again if the position of the header changes in the data set unless the header itself is
|
||||
* invalidated or the new position cannot be determined. For this reason, you should only use the
|
||||
* <code>section</code> parameter while acquiring the related header data inside this method and
|
||||
* should not keep a copy of it. If you need the position of a header later on (e.g. in a click
|
||||
* listener), use {@link HeaderViewHolder#getAdapterPosition()} which will have the updated
|
||||
* adapter position. Then you can use {@link #getAdapterPositionSection(int)} to get section
|
||||
* index.
|
||||
*
|
||||
* @param viewHolder The ViewHolder which should be updated to represent the contents of the
|
||||
* header at the given position in the data set.
|
||||
@@ -417,23 +406,20 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
public abstract void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int section);
|
||||
|
||||
/**
|
||||
* Called by RecyclerView to display the data at the specified position. This method should
|
||||
* update the contents of the {@link ItemViewHolder#itemView} to reflect the item at the given
|
||||
* position.
|
||||
* <p>
|
||||
* Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
|
||||
* again if the position of the item changes in the data set unless the item itself is
|
||||
* invalidated or the new position cannot be determined. For this reason, you should only
|
||||
* use the <code>offset</code> and <code>section</code> parameters while acquiring the
|
||||
* related data item inside this method and should not keep a copy of it. If you need the
|
||||
* position of an item later on (e.g. in a click listener), use
|
||||
* {@link ItemViewHolder#getAdapterPosition()} which will have the updated adapter
|
||||
* position. Then you can use {@link #getAdapterPositionSection(int)} and
|
||||
* {@link #getItemSectionOffset(int, int)}
|
||||
* Called by RecyclerView to display the data at the specified position. This method should update
|
||||
* the contents of the {@link ItemViewHolder#itemView} to reflect the item at the given position.
|
||||
*
|
||||
* <p>Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
|
||||
* again if the position of the item changes in the data set unless the item itself is invalidated
|
||||
* or the new position cannot be determined. For this reason, you should only use the <code>offset
|
||||
* </code> and <code>section</code> parameters while acquiring the related data item inside this
|
||||
* method and should not keep a copy of it. If you need the position of an item later on (e.g. in
|
||||
* a click listener), use {@link ItemViewHolder#getAdapterPosition()} which will have the updated
|
||||
* adapter position. Then you can use {@link #getAdapterPositionSection(int)} and {@link
|
||||
* #getItemSectionOffset(int, int)}
|
||||
*
|
||||
* @param viewHolder The ViewHolder which should be updated to represent the contents of the
|
||||
* item at the given position in the data set.
|
||||
* @param viewHolder The ViewHolder which should be updated to represent the contents of the item
|
||||
* at the given position in the data set.
|
||||
* @param section The index of the section.
|
||||
* @param offset The position of the item within the section.
|
||||
*/
|
||||
@@ -443,24 +429,21 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
/**
|
||||
* Notify any registered observers that the data set has changed.
|
||||
*
|
||||
* <p>There are two different classes of data change events, item changes and structural
|
||||
* changes. Item changes are when a single item has its data updated but no positional
|
||||
* changes have occurred. Structural changes are when items are inserted, removed or moved
|
||||
* within the data set.</p>
|
||||
* <p>There are two different classes of data change events, item changes and structural changes.
|
||||
* Item changes are when a single item has its data updated but no positional changes have
|
||||
* occurred. Structural changes are when items are inserted, removed or moved within the data set.
|
||||
*
|
||||
* <p>This event does not specify what about the data set has changed, forcing
|
||||
* any observers to assume that all existing items and structure may no longer be valid.
|
||||
* LayoutManagers will be forced to fully rebind and relayout all visible views.</p>
|
||||
* <p>This event does not specify what about the data set has changed, forcing any observers to
|
||||
* assume that all existing items and structure may no longer be valid. LayoutManagers will be
|
||||
* forced to fully rebind and relayout all visible views.
|
||||
*
|
||||
* <p><code>RecyclerView</code> will attempt to synthesize visible structural change events
|
||||
* for adapters that report that they have {@link #hasStableIds() stable IDs} when
|
||||
* this method is used. This can help for the purposes of animation and visual
|
||||
* object persistence but individual item views will still need to be rebound
|
||||
* and relaid out.</p>
|
||||
* <p><code>RecyclerView</code> will attempt to synthesize visible structural change events for
|
||||
* adapters that report that they have {@link #hasStableIds() stable IDs} when this method is
|
||||
* used. This can help for the purposes of animation and visual object persistence but individual
|
||||
* item views will still need to be rebound and relaid out.
|
||||
*
|
||||
* <p>If you are writing an adapter it will always be more efficient to use the more
|
||||
* specific change events if you can. Rely on <code>notifyDataSetChanged()</code>
|
||||
* as a last resort.</p>
|
||||
* <p>If you are writing an adapter it will always be more efficient to use the more specific
|
||||
* change events if you can. Rely on <code>notifyDataSetChanged()</code> as a last resort.
|
||||
*
|
||||
* @see #notifySectionDataSetChanged(int)
|
||||
* @see #notifySectionHeaderChanged(int)
|
||||
@@ -481,8 +464,7 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
calculateSections();
|
||||
if (mSections == null) {
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
notifyItemRangeChanged(sectionObject.position, sectionObject.length);
|
||||
}
|
||||
@@ -492,8 +474,7 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
calculateSections();
|
||||
if (mSections == null) {
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
notifyItemRangeChanged(sectionObject.position, 1);
|
||||
}
|
||||
@@ -503,12 +484,12 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
calculateSections();
|
||||
if (mSections == null) {
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
|
||||
if (position >= sectionObject.itemNumber) {
|
||||
throw new IndexOutOfBoundsException("Invalid index " + position + ", size is " + sectionObject.itemNumber);
|
||||
throw new IndexOutOfBoundsException(
|
||||
"Invalid index " + position + ", size is " + sectionObject.itemNumber);
|
||||
}
|
||||
|
||||
notifyItemChanged(sectionObject.position + position + 1);
|
||||
@@ -519,8 +500,7 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
calculateSections();
|
||||
if (mSections == null) {
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
notifyItemRangeInserted(sectionObject.position, sectionObject.length);
|
||||
}
|
||||
@@ -530,12 +510,12 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
calculateSections();
|
||||
if (mSections == null) {
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
|
||||
if (position < 0 || position >= sectionObject.itemNumber) {
|
||||
throw new IndexOutOfBoundsException("Invalid index " + position + ", size is " + sectionObject.itemNumber);
|
||||
throw new IndexOutOfBoundsException(
|
||||
"Invalid index " + position + ", size is " + sectionObject.itemNumber);
|
||||
}
|
||||
|
||||
notifyItemInserted(sectionObject.position + position + 1);
|
||||
@@ -546,15 +526,16 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
calculateSections();
|
||||
if (mSections == null) {
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
|
||||
if (position < 0 || position >= sectionObject.itemNumber) {
|
||||
throw new IndexOutOfBoundsException("Invalid index " + position + ", size is " + sectionObject.itemNumber);
|
||||
throw new IndexOutOfBoundsException(
|
||||
"Invalid index " + position + ", size is " + sectionObject.itemNumber);
|
||||
}
|
||||
if (position + count > sectionObject.itemNumber) {
|
||||
throw new IndexOutOfBoundsException("Invalid index " + (position + count) + ", size is " + sectionObject.itemNumber);
|
||||
throw new IndexOutOfBoundsException(
|
||||
"Invalid index " + (position + count) + ", size is " + sectionObject.itemNumber);
|
||||
}
|
||||
|
||||
notifyItemRangeInserted(sectionObject.position + position + 1, count);
|
||||
@@ -565,8 +546,7 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
if (mSections == null) {
|
||||
calculateSections();
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
calculateSections();
|
||||
notifyItemRangeRemoved(sectionObject.position, sectionObject.length);
|
||||
@@ -577,12 +557,12 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
if (mSections == null) {
|
||||
calculateSections();
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
|
||||
if (position < 0 || position >= sectionObject.itemNumber) {
|
||||
throw new IndexOutOfBoundsException("Invalid index " + position + ", size is " + sectionObject.itemNumber);
|
||||
throw new IndexOutOfBoundsException(
|
||||
"Invalid index " + position + ", size is " + sectionObject.itemNumber);
|
||||
}
|
||||
|
||||
calculateSections();
|
||||
@@ -594,15 +574,16 @@ public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<Stick
|
||||
if (mSections == null) {
|
||||
calculateSections();
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
|
||||
if (position < 0 || position >= sectionObject.itemNumber) {
|
||||
throw new IndexOutOfBoundsException("Invalid index " + position + ", size is " + sectionObject.itemNumber);
|
||||
throw new IndexOutOfBoundsException(
|
||||
"Invalid index " + position + ", size is " + sectionObject.itemNumber);
|
||||
}
|
||||
if (position + count > sectionObject.itemNumber) {
|
||||
throw new IndexOutOfBoundsException("Invalid index " + (position + count) + ", size is " + sectionObject.itemNumber);
|
||||
throw new IndexOutOfBoundsException(
|
||||
"Invalid index " + (position + count) + ", size is " + sectionObject.itemNumber);
|
||||
}
|
||||
|
||||
calculateSections();
|
||||
|
||||
@@ -12,20 +12,15 @@ import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Created by Sergej Kravcenko on 4/24/2017.
|
||||
* Copyright (c) 2017 Sergej Kravcenko
|
||||
*/
|
||||
|
||||
/** Created by Sergej Kravcenko on 4/24/2017. Copyright (c) 2017 Sergej Kravcenko */
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager implements RecyclerView.SmoothScroller.ScrollVectorProvider {
|
||||
public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager
|
||||
implements RecyclerView.SmoothScroller.ScrollVectorProvider {
|
||||
public static final String TAG = "StickyLayoutManager";
|
||||
|
||||
private static final int DEFAULT_ROW_COUNT = 16;
|
||||
@@ -64,21 +59,18 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
PUSHED
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface to be implemented by listeners to header events from this
|
||||
* LayoutManager.
|
||||
*/
|
||||
/** The interface to be implemented by listeners to header events from this LayoutManager. */
|
||||
public interface HeaderStateChangeListener {
|
||||
/**
|
||||
* Called when a section header state changes. The position can be HeaderState.NORMAL,
|
||||
* HeaderState.STICKY, HeaderState.PUSHED.
|
||||
*
|
||||
* <p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>NORMAL - the section header is invisible or has normal position</li>
|
||||
* <li>STICKY - the section header is sticky at the top of RecyclerView</li>
|
||||
* <li>PUSHED - the section header is sticky and pushed up by next header</li>
|
||||
* </ul
|
||||
* <li>NORMAL - the section header is invisible or has normal position
|
||||
* <li>STICKY - the section header is sticky at the top of RecyclerView
|
||||
* <li>PUSHED - the section header is sticky and pushed up by next header </ul
|
||||
*
|
||||
* @param section the section index
|
||||
* @param headerView the header view, can be null if header is out of screen
|
||||
@@ -88,7 +80,6 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
void onHeaderStateChanged(int section, View headerView, HeaderState state, int pushOffset);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a vertical StickyHeaderGridLayoutManager
|
||||
*
|
||||
@@ -106,8 +97,8 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
/**
|
||||
* Sets the source to get the number of spans occupied by each item in the adapter.
|
||||
*
|
||||
* @param spanSizeLookup {@link StickyHeaderGridLayoutManager.SpanSizeLookup} instance to be used to query number of spans
|
||||
* occupied by each item
|
||||
* @param spanSizeLookup {@link StickyHeaderGridLayoutManager.SpanSizeLookup} instance to be used
|
||||
* to query number of spans occupied by each item
|
||||
*/
|
||||
public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup) {
|
||||
mSpanSizeLookup = spanSizeLookup;
|
||||
@@ -117,18 +108,22 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current {@link StickyHeaderGridLayoutManager.SpanSizeLookup} used by the StickyHeaderGridLayoutManager.
|
||||
* Returns the current {@link StickyHeaderGridLayoutManager.SpanSizeLookup} used by the
|
||||
* StickyHeaderGridLayoutManager.
|
||||
*
|
||||
* @return The current {@link StickyHeaderGridLayoutManager.SpanSizeLookup} used by the StickyHeaderGridLayoutManager.
|
||||
* @return The current {@link StickyHeaderGridLayoutManager.SpanSizeLookup} used by the
|
||||
* StickyHeaderGridLayoutManager.
|
||||
*/
|
||||
public SpanSizeLookup getSpanSizeLookup() {
|
||||
return mSpanSizeLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current {@link StickyHeaderGridLayoutManager.HeaderStateChangeListener} used by the StickyHeaderGridLayoutManager.
|
||||
* Returns the current {@link StickyHeaderGridLayoutManager.HeaderStateChangeListener} used by the
|
||||
* StickyHeaderGridLayoutManager.
|
||||
*
|
||||
* @return The current {@link StickyHeaderGridLayoutManager.HeaderStateChangeListener} used by the StickyHeaderGridLayoutManager.
|
||||
* @return The current {@link StickyHeaderGridLayoutManager.HeaderStateChangeListener} used by the
|
||||
* StickyHeaderGridLayoutManager.
|
||||
*/
|
||||
public HeaderStateChangeListener getHeaderStateChangeListener() {
|
||||
return mHeaderStateListener;
|
||||
@@ -137,18 +132,18 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
/**
|
||||
* Sets the listener to receive header state changes.
|
||||
*
|
||||
* @param listener {@link StickyHeaderGridLayoutManager.HeaderStateChangeListener} instance to be used to receive header
|
||||
* state changes
|
||||
* @param listener {@link StickyHeaderGridLayoutManager.HeaderStateChangeListener} instance to be
|
||||
* used to receive header state changes
|
||||
*/
|
||||
public void setHeaderStateChangeListener(HeaderStateChangeListener listener) {
|
||||
mHeaderStateListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of header bottom margin that overlaps first section item. Used to create header bottom edge shadows.
|
||||
* Sets the size of header bottom margin that overlaps first section item. Used to create header
|
||||
* bottom edge shadows.
|
||||
*
|
||||
* @param bottomMargin Size of header bottom margin in pixels
|
||||
*
|
||||
*/
|
||||
public void setHeaderBottomOverlapMargin(int bottomMargin) {
|
||||
mHeaderOverlapMargin = bottomMargin;
|
||||
@@ -159,10 +154,10 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
super.onAdapterChanged(oldAdapter, newAdapter);
|
||||
|
||||
try {
|
||||
mAdapter = (StickyHeaderGridAdapter)newAdapter;
|
||||
}
|
||||
catch (ClassCastException e) {
|
||||
throw new ClassCastException("Adapter used with StickyHeaderGridLayoutManager must be kind of StickyHeaderGridAdapter");
|
||||
mAdapter = (StickyHeaderGridAdapter) newAdapter;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(
|
||||
"Adapter used with StickyHeaderGridLayoutManager must be kind of StickyHeaderGridAdapter");
|
||||
}
|
||||
|
||||
removeAllViews();
|
||||
@@ -174,16 +169,17 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
super.onAttachedToWindow(view);
|
||||
|
||||
try {
|
||||
mAdapter = (StickyHeaderGridAdapter)view.getAdapter();
|
||||
}
|
||||
catch (ClassCastException e) {
|
||||
throw new ClassCastException("Adapter used with StickyHeaderGridLayoutManager must be kind of StickyHeaderGridAdapter");
|
||||
mAdapter = (StickyHeaderGridAdapter) view.getAdapter();
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(
|
||||
"Adapter used with StickyHeaderGridLayoutManager must be kind of StickyHeaderGridAdapter");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
|
||||
return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
return new LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -194,9 +190,8 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
@Override
|
||||
public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
|
||||
if (lp instanceof ViewGroup.MarginLayoutParams) {
|
||||
return new LayoutParams((ViewGroup.MarginLayoutParams)lp);
|
||||
}
|
||||
else {
|
||||
return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
|
||||
} else {
|
||||
return new LayoutParams(lp);
|
||||
}
|
||||
}
|
||||
@@ -212,8 +207,7 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
state.mAnchorSection = mAnchor.section;
|
||||
state.mAnchorItem = mAnchor.item;
|
||||
state.mAnchorOffset = mAnchor.offset;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
state.invalidateAnchor();
|
||||
}
|
||||
|
||||
@@ -225,8 +219,7 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
if (state instanceof SavedState) {
|
||||
mPendingSavedState = (SavedState) state;
|
||||
requestLayout();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Log.d(TAG, "invalid saved state class");
|
||||
}
|
||||
}
|
||||
@@ -242,12 +235,12 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Scroll the RecyclerView to make the position visible.</p>
|
||||
* Scroll the RecyclerView to make the position visible.
|
||||
*
|
||||
* <p>RecyclerView will scroll the minimum amount that is necessary to make the
|
||||
* target position visible.
|
||||
* <p>RecyclerView will scroll the minimum amount that is necessary to make the target position
|
||||
* visible.
|
||||
*
|
||||
* <p>Note that scroll position change will not be reflected until the next layout call.</p>
|
||||
* <p>Note that scroll position change will not be reflected until the next layout call.
|
||||
*
|
||||
* @param position Scroll to this adapter position
|
||||
*/
|
||||
@@ -268,15 +261,16 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
private int getExtraLayoutSpace(RecyclerView.State state) {
|
||||
if (state.hasTargetScrollPosition()) {
|
||||
return getHeight();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void smoothScrollToPosition(final RecyclerView recyclerView, RecyclerView.State state, int position) {
|
||||
final LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
|
||||
public void smoothScrollToPosition(
|
||||
final RecyclerView recyclerView, RecyclerView.State state, int position) {
|
||||
final LinearSmoothScroller linearSmoothScroller =
|
||||
new LinearSmoothScroller(recyclerView.getContext()) {
|
||||
@Override
|
||||
public int calculateDyToMakeVisible(View view, int snapPreference) {
|
||||
final RecyclerView.LayoutManager layoutManager = getLayoutManager();
|
||||
@@ -315,8 +309,7 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
if (anchor.section < 0 || anchor.section >= mAdapter.getSectionCount()) {
|
||||
anchor.reset();
|
||||
return NO_POSITION;
|
||||
}
|
||||
else if (anchor.item < 0 || anchor.item >= mAdapter.getSectionItemCount(anchor.section)) {
|
||||
} else if (anchor.item < 0 || anchor.item >= mAdapter.getSectionItemCount(anchor.section)) {
|
||||
anchor.offset = 0;
|
||||
return mAdapter.getSectionHeaderPosition(anchor.section);
|
||||
}
|
||||
@@ -326,8 +319,7 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
private int getAdapterPositionChecked(int section, int offset) {
|
||||
if (section < 0 || section >= mAdapter.getSectionCount()) {
|
||||
return NO_POSITION;
|
||||
}
|
||||
else if (offset < 0 || offset >= mAdapter.getSectionItemCount(section)) {
|
||||
} else if (offset < 0 || offset >= mAdapter.getSectionItemCount(section)) {
|
||||
return mAdapter.getSectionHeaderPosition(section);
|
||||
}
|
||||
return mAdapter.getSectionItemPosition(section, offset);
|
||||
@@ -346,13 +338,13 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
if (mPendingScrollPosition >= 0) {
|
||||
pendingAdapterPosition = mPendingScrollPosition;
|
||||
pendingAdapterOffset = mPendingScrollPositionOffset;
|
||||
}
|
||||
else if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
|
||||
pendingAdapterPosition = getAdapterPositionChecked(mPendingSavedState.mAnchorSection, mPendingSavedState.mAnchorItem);
|
||||
} else if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
|
||||
pendingAdapterPosition =
|
||||
getAdapterPositionChecked(
|
||||
mPendingSavedState.mAnchorSection, mPendingSavedState.mAnchorItem);
|
||||
pendingAdapterOffset = mPendingSavedState.mAnchorOffset;
|
||||
mPendingSavedState = null;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
pendingAdapterPosition = getAdapterPositionFromAnchor(mAnchor);
|
||||
pendingAdapterOffset = mAnchor.offset;
|
||||
}
|
||||
@@ -402,8 +394,7 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
mLayoutRows.add(new LayoutRow(v, adapterPosition, 1, top, bottom));
|
||||
adapterPosition++;
|
||||
mAverageHeaderHeight = height;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
final FillResult result = fillBottomRow(recycler, state, adapterPosition, top);
|
||||
bottom = top + result.height;
|
||||
mLayoutRows.add(new LayoutRow(result.adapterPosition, result.length, top, bottom));
|
||||
@@ -418,12 +409,12 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
|
||||
if (getBottomRow().bottom < recyclerBottom) {
|
||||
scrollVerticallyBy(getBottomRow().bottom - recyclerBottom, recycler, state);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
clearViewsAndStickHeaders(recycler, state, false);
|
||||
}
|
||||
|
||||
// If layout was caused by the pending scroll, adjust top item position and move it under sticky header
|
||||
// If layout was caused by the pending scroll, adjust top item position and move it under sticky
|
||||
// header
|
||||
if (mPendingScrollPosition >= 0) {
|
||||
mPendingScrollPosition = NO_POSITION;
|
||||
|
||||
@@ -447,14 +438,13 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
if (offset >= 0) {
|
||||
final int headerAdapterPosition = mAdapter.getSectionHeaderPosition(section);
|
||||
if (mFloatingHeaderView != null && headerAdapterPosition == mFloatingHeaderPosition) {
|
||||
return Math.max(0, getDecoratedMeasuredHeight(mFloatingHeaderView) - mHeaderOverlapMargin);
|
||||
}
|
||||
else {
|
||||
return Math.max(
|
||||
0, getDecoratedMeasuredHeight(mFloatingHeaderView) - mHeaderOverlapMargin);
|
||||
} else {
|
||||
final LayoutRow header = getHeaderRow(headerAdapterPosition);
|
||||
if (header != null) {
|
||||
return header.getHeight();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Fall back to cached header size, can be incorrect
|
||||
return mAverageHeaderHeight;
|
||||
}
|
||||
@@ -468,7 +458,8 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
private int findFirstRowItem(int adapterPosition) {
|
||||
final int section = mAdapter.getAdapterPositionSection(adapterPosition);
|
||||
int sectionPosition = mAdapter.getItemSectionOffset(section, adapterPosition);
|
||||
while (sectionPosition > 0 && mSpanSizeLookup.getSpanIndex(section, sectionPosition, mSpanCount) != 0) {
|
||||
while (sectionPosition > 0
|
||||
&& mSpanSizeLookup.getSpanIndex(section, sectionPosition, mSpanCount) != 0) {
|
||||
sectionPosition--;
|
||||
adapterPosition--;
|
||||
}
|
||||
@@ -492,7 +483,8 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
return spanWidth * spanIndex + widthCorrection;
|
||||
}
|
||||
|
||||
private FillResult fillBottomRow(RecyclerView.Recycler recycler, RecyclerView.State state, int position, int top) {
|
||||
private FillResult fillBottomRow(
|
||||
RecyclerView.Recycler recycler, RecyclerView.State state, int position, int top) {
|
||||
final int recyclerWidth = getWidth() - getPaddingLeft() - getPaddingRight();
|
||||
final int section = mAdapter.getAdapterPositionSection(position);
|
||||
int adapterPosition = position;
|
||||
@@ -508,7 +500,7 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
// Create view and fill layout params
|
||||
final int spanWidth = getSpanWidth(recyclerWidth, spanIndex, spanSize);
|
||||
final View v = recycler.getViewForPosition(adapterPosition);
|
||||
final LayoutParams params = (LayoutParams)v.getLayoutParams();
|
||||
final LayoutParams params = (LayoutParams) v.getLayoutParams();
|
||||
params.mSpanIndex = spanIndex;
|
||||
params.mSpanSize = spanSize;
|
||||
|
||||
@@ -552,7 +544,8 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
return mFillResult;
|
||||
}
|
||||
|
||||
private FillResult fillTopRow(RecyclerView.Recycler recycler, RecyclerView.State state, int position, int top) {
|
||||
private FillResult fillTopRow(
|
||||
RecyclerView.Recycler recycler, RecyclerView.State state, int position, int top) {
|
||||
final int recyclerWidth = getWidth() - getPaddingLeft() - getPaddingRight();
|
||||
final int section = mAdapter.getAdapterPositionSection(position);
|
||||
int adapterPosition = position;
|
||||
@@ -567,7 +560,7 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
// Create view and fill layout params
|
||||
final int spanWidth = getSpanWidth(recyclerWidth, spanIndex, spanSize);
|
||||
final View v = recycler.getViewForPosition(adapterPosition);
|
||||
final LayoutParams params = (LayoutParams)v.getLayoutParams();
|
||||
final LayoutParams params = (LayoutParams) v.getLayoutParams();
|
||||
params.mSpanIndex = spanIndex;
|
||||
params.mSpanSize = spanSize;
|
||||
|
||||
@@ -611,7 +604,8 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
return mFillResult;
|
||||
}
|
||||
|
||||
private void clearHiddenRows(RecyclerView.Recycler recycler, RecyclerView.State state, boolean top) {
|
||||
private void clearHiddenRows(
|
||||
RecyclerView.Recycler recycler, RecyclerView.State state, boolean top) {
|
||||
if (mLayoutRows.size() <= 0) {
|
||||
return;
|
||||
}
|
||||
@@ -623,9 +617,9 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
LayoutRow row = getTopRow();
|
||||
while (row.bottom < recyclerTop - getExtraLayoutSpace(state) || row.top > recyclerBottom) {
|
||||
if (row.header) {
|
||||
removeAndRecycleViewAt(mHeadersStartPosition + (mFloatingHeaderView != null ? 1 : 0), recycler);
|
||||
}
|
||||
else {
|
||||
removeAndRecycleViewAt(
|
||||
mHeadersStartPosition + (mFloatingHeaderView != null ? 1 : 0), recycler);
|
||||
} else {
|
||||
for (int i = 0; i < row.length; ++i) {
|
||||
removeAndRecycleViewAt(0, recycler);
|
||||
mHeadersStartPosition--;
|
||||
@@ -634,14 +628,12 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
mLayoutRows.remove(0);
|
||||
row = getTopRow();
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
LayoutRow row = getBottomRow();
|
||||
while (row.bottom < recyclerTop || row.top > recyclerBottom + getExtraLayoutSpace(state)) {
|
||||
if (row.header) {
|
||||
removeAndRecycleViewAt(getChildCount() - 1, recycler);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
for (int i = 0; i < row.length; ++i) {
|
||||
removeAndRecycleViewAt(mHeadersStartPosition - 1, recycler);
|
||||
mHeadersStartPosition--;
|
||||
@@ -653,7 +645,8 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
}
|
||||
}
|
||||
|
||||
private void clearViewsAndStickHeaders(RecyclerView.Recycler recycler, RecyclerView.State state, boolean top) {
|
||||
private void clearViewsAndStickHeaders(
|
||||
RecyclerView.Recycler recycler, RecyclerView.State state, boolean top) {
|
||||
clearHiddenRows(recycler, state, top);
|
||||
if (getChildCount() > 0) {
|
||||
stickTopHeader(recycler);
|
||||
@@ -677,7 +670,12 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
offsetChildrenVertical(offset);
|
||||
}
|
||||
|
||||
private void addRow(RecyclerView.Recycler recycler, RecyclerView.State state, boolean isTop, int adapterPosition, int top) {
|
||||
private void addRow(
|
||||
RecyclerView.Recycler recycler,
|
||||
RecyclerView.State state,
|
||||
boolean isTop,
|
||||
int adapterPosition,
|
||||
int top) {
|
||||
final int left = getPaddingLeft();
|
||||
final int right = getWidth() - getPaddingRight();
|
||||
|
||||
@@ -691,8 +689,7 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
final View v = recycler.getViewForPosition(adapterPosition);
|
||||
if (isTop) {
|
||||
addView(v, mHeadersStartPosition);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
addView(v);
|
||||
}
|
||||
measureChildWithMargins(v, 0, 0);
|
||||
@@ -701,38 +698,42 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
if (isTop) {
|
||||
layoutDecorated(v, left, top - height + margin, right, top + margin);
|
||||
mLayoutRows.add(0, new LayoutRow(v, adapterPosition, 1, top - height + margin, top));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
layoutDecorated(v, left, top, right, top + height);
|
||||
mLayoutRows.add(new LayoutRow(v, adapterPosition, 1, top, top + height - margin));
|
||||
}
|
||||
mAverageHeaderHeight = height - margin;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (isTop) {
|
||||
final FillResult result = fillTopRow(recycler, state, adapterPosition, top);
|
||||
mLayoutRows.add(0, new LayoutRow(result.adapterPosition, result.length, top - result.height, top));
|
||||
}
|
||||
else {
|
||||
mLayoutRows.add(
|
||||
0, new LayoutRow(result.adapterPosition, result.length, top - result.height, top));
|
||||
} else {
|
||||
final FillResult result = fillBottomRow(recycler, state, adapterPosition, top);
|
||||
mLayoutRows.add(new LayoutRow(result.adapterPosition, result.length, top, top + result.height));
|
||||
mLayoutRows.add(
|
||||
new LayoutRow(result.adapterPosition, result.length, top, top + result.height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addOffScreenRows(RecyclerView.Recycler recycler, RecyclerView.State state, int recyclerTop, int recyclerBottom, boolean bottom) {
|
||||
private void addOffScreenRows(
|
||||
RecyclerView.Recycler recycler,
|
||||
RecyclerView.State state,
|
||||
int recyclerTop,
|
||||
int recyclerBottom,
|
||||
boolean bottom) {
|
||||
if (bottom) {
|
||||
// Bottom
|
||||
while (true) {
|
||||
final LayoutRow bottomRow = getBottomRow();
|
||||
final int adapterPosition = bottomRow.adapterPosition + bottomRow.length;
|
||||
if (bottomRow.bottom >= recyclerBottom + getExtraLayoutSpace(state) || adapterPosition >= state.getItemCount()) {
|
||||
if (bottomRow.bottom >= recyclerBottom + getExtraLayoutSpace(state)
|
||||
|| adapterPosition >= state.getItemCount()) {
|
||||
break;
|
||||
}
|
||||
addRow(recycler, state, false, adapterPosition, bottomRow.bottom);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Top
|
||||
while (true) {
|
||||
final LayoutRow topRow = getTopRow();
|
||||
@@ -767,7 +768,8 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
// Up
|
||||
while (scrolled < dy) {
|
||||
final LayoutRow bottomRow = getBottomRow();
|
||||
final int scrollChunk = -Math.min(Math.max(bottomRow.bottom - recyclerBottom, 0), dy - scrolled);
|
||||
final int scrollChunk =
|
||||
-Math.min(Math.max(bottomRow.bottom - recyclerBottom, 0), dy - scrolled);
|
||||
|
||||
offsetRowsVertical(scrollChunk);
|
||||
scrolled -= scrollChunk;
|
||||
@@ -779,8 +781,7 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
|
||||
addRow(recycler, state, false, adapterPosition, bottomRow.bottom);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Down
|
||||
while (scrolled > dy) {
|
||||
final LayoutRow topRow = getTopRow();
|
||||
@@ -848,8 +849,7 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
private int getFirstVisiblePosition(int type, boolean visibleTop) {
|
||||
if (type == TYPE_ITEM && mHeadersStartPosition <= 0) {
|
||||
return NO_POSITION;
|
||||
}
|
||||
else if (type == TYPE_HEADER && mHeadersStartPosition >= getChildCount()) {
|
||||
} else if (type == TYPE_HEADER && mHeadersStartPosition >= getChildCount()) {
|
||||
return NO_POSITION;
|
||||
}
|
||||
|
||||
@@ -867,8 +867,7 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
if (top >= recyclerTop + headerHeight) {
|
||||
return adapterPosition;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (bottom >= recyclerTop + headerHeight) {
|
||||
return adapterPosition;
|
||||
}
|
||||
@@ -881,8 +880,7 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
private int getLastVisiblePosition(int type) {
|
||||
if (type == TYPE_ITEM && mHeadersStartPosition <= 0) {
|
||||
return NO_POSITION;
|
||||
}
|
||||
else if (type == TYPE_HEADER && mHeadersStartPosition >= getChildCount()) {
|
||||
} else if (type == TYPE_HEADER && mHeadersStartPosition >= getChildCount()) {
|
||||
return NO_POSITION;
|
||||
}
|
||||
|
||||
@@ -963,7 +961,10 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
onHeaderUnstick();
|
||||
}
|
||||
|
||||
final boolean headerStateChanged = mStickyHeaderSection != section || !mStickyHeadeState.equals(state) || state.equals(HeaderState.PUSHED);
|
||||
final boolean headerStateChanged =
|
||||
mStickyHeaderSection != section
|
||||
|| !mStickyHeadeState.equals(state)
|
||||
|| state.equals(HeaderState.PUSHED);
|
||||
|
||||
mStickyHeaderSection = section;
|
||||
mStickyHeaderView = view;
|
||||
@@ -977,7 +978,8 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
private void onHeaderUnstick() {
|
||||
if (mStickyHeaderSection != NO_POSITION) {
|
||||
if (mHeaderStateListener != null) {
|
||||
mHeaderStateListener.onHeaderStateChanged(mStickyHeaderSection, mStickyHeaderView, HeaderState.NORMAL, 0);
|
||||
mHeaderStateListener.onHeaderStateChanged(
|
||||
mStickyHeaderSection, mStickyHeaderView, HeaderState.NORMAL, 0);
|
||||
}
|
||||
mStickyHeaderSection = NO_POSITION;
|
||||
mStickyHeaderView = null;
|
||||
@@ -1013,14 +1015,16 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
mStickOffset = top - firstHeaderRow.top - offset;
|
||||
firstHeaderRow.headerView.offsetTopAndBottom(mStickOffset);
|
||||
|
||||
onHeaderChanged(section, firstHeaderRow.headerView, offset == 0 ? HeaderState.STICKY : HeaderState.PUSHED, offset);
|
||||
}
|
||||
else {
|
||||
onHeaderChanged(
|
||||
section,
|
||||
firstHeaderRow.headerView,
|
||||
offset == 0 ? HeaderState.STICKY : HeaderState.PUSHED,
|
||||
offset);
|
||||
} else {
|
||||
onHeaderUnstick();
|
||||
mStickOffset = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// We don't have first visible sector header in layout, create floating
|
||||
final LayoutRow firstVisibleRow = getFirstVisibleRow();
|
||||
if (firstVisibleRow != null) {
|
||||
@@ -1048,13 +1052,15 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
}
|
||||
|
||||
layoutDecorated(mFloatingHeaderView, left, top - offset, right, top + height - offset);
|
||||
onHeaderChanged(section, mFloatingHeaderView, offset == 0 ? HeaderState.STICKY : HeaderState.PUSHED, offset);
|
||||
}
|
||||
else {
|
||||
onHeaderChanged(
|
||||
section,
|
||||
mFloatingHeaderView,
|
||||
offset == 0 ? HeaderState.STICKY : HeaderState.PUSHED,
|
||||
offset);
|
||||
} else {
|
||||
onHeaderUnstick();
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
onHeaderUnstick();
|
||||
}
|
||||
}
|
||||
@@ -1068,7 +1074,8 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
final LayoutRow firstVisibleRow = getFirstVisibleRow();
|
||||
if (firstVisibleRow != null) {
|
||||
mAnchor.section = mAdapter.getAdapterPositionSection(firstVisibleRow.adapterPosition);
|
||||
mAnchor.item = mAdapter.getItemSectionOffset(mAnchor.section, firstVisibleRow.adapterPosition);
|
||||
mAnchor.item =
|
||||
mAdapter.getItemSectionOffset(mAnchor.section, firstVisibleRow.adapterPosition);
|
||||
mAnchor.offset = Math.min(firstVisibleRow.top - getPaddingTop(), 0);
|
||||
}
|
||||
}
|
||||
@@ -1091,7 +1098,8 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
|
||||
if (mStickyHeaderSection != NO_POSITION) {
|
||||
if (mHeaderStateListener != null) {
|
||||
mHeaderStateListener.onHeaderStateChanged(mStickyHeaderSection, mStickyHeaderView, HeaderState.NORMAL, 0);
|
||||
mHeaderStateListener.onHeaderStateChanged(
|
||||
mStickyHeaderSection, mStickyHeaderView, HeaderState.NORMAL, 0);
|
||||
}
|
||||
mStickyHeaderSection = NO_POSITION;
|
||||
mStickyHeaderView = null;
|
||||
@@ -1202,37 +1210,39 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
|
||||
/**
|
||||
* An interface to provide the number of spans each item occupies.
|
||||
* <p>
|
||||
* Default implementation sets each item to occupy exactly 1 span.
|
||||
*
|
||||
* @see StickyHeaderGridLayoutManager#setSpanSizeLookup(StickyHeaderGridLayoutManager.SpanSizeLookup)
|
||||
* <p>Default implementation sets each item to occupy exactly 1 span.
|
||||
*
|
||||
* @see
|
||||
* StickyHeaderGridLayoutManager#setSpanSizeLookup(StickyHeaderGridLayoutManager.SpanSizeLookup)
|
||||
*/
|
||||
public static abstract class SpanSizeLookup {
|
||||
public abstract static class SpanSizeLookup {
|
||||
/**
|
||||
* Returns the number of span occupied by the item in <code>section</code> at <code>position</code>.
|
||||
* Returns the number of span occupied by the item in <code>section</code> at <code>position
|
||||
* </code>.
|
||||
*
|
||||
* @param section The adapter section of the item
|
||||
* @param position The adapter position of the item in section
|
||||
* @return The number of spans occupied by the item at the provided section and position
|
||||
*/
|
||||
abstract public int getSpanSize(int section, int position);
|
||||
public abstract int getSpanSize(int section, int position);
|
||||
|
||||
/**
|
||||
* Returns the final span index of the provided position.
|
||||
*
|
||||
* <p>
|
||||
* If you override this method, you need to make sure it is consistent with
|
||||
* {@link #getSpanSize(int, int)}. StickyHeaderGridLayoutManager does not call this method for
|
||||
* each item. It is called only for the reference item and rest of the items
|
||||
* are assigned to spans based on the reference item. For example, you cannot assign a
|
||||
* position to span 2 while span 1 is empty.
|
||||
* <p>If you override this method, you need to make sure it is consistent with {@link
|
||||
* #getSpanSize(int, int)}. StickyHeaderGridLayoutManager does not call this method for each
|
||||
* item. It is called only for the reference item and rest of the items are assigned to spans
|
||||
* based on the reference item. For example, you cannot assign a position to span 2 while span 1
|
||||
* is empty.
|
||||
*
|
||||
* <p>
|
||||
*
|
||||
* @param section The adapter section of the item
|
||||
* @param position The adapter position of the item in section
|
||||
* @param spanCount The total number of spans in the grid
|
||||
* @return The final span position of the item. Should be between 0 (inclusive) and
|
||||
* <code>spanCount</code>(exclusive)
|
||||
* @return The final span position of the item. Should be between 0 (inclusive) and <code>
|
||||
* spanCount</code>(exclusive)
|
||||
*/
|
||||
public int getSpanIndex(int section, int position, int spanCount) {
|
||||
// TODO: cache them?
|
||||
@@ -1248,8 +1258,7 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
|
||||
if (spanIndex == spanCount) {
|
||||
spanIndex = 0;
|
||||
}
|
||||
else if (spanIndex > spanCount) {
|
||||
} else if (spanIndex > spanCount) {
|
||||
spanIndex = spanSize;
|
||||
}
|
||||
}
|
||||
@@ -1267,9 +1276,7 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
private int mAnchorItem;
|
||||
private int mAnchorOffset;
|
||||
|
||||
public SavedState() {
|
||||
|
||||
}
|
||||
public SavedState() {}
|
||||
|
||||
SavedState(Parcel in) {
|
||||
mAnchorSection = in.readInt();
|
||||
@@ -1303,7 +1310,8 @@ public class StickyHeaderGridLayoutManager extends RecyclerView.LayoutManager im
|
||||
dest.writeInt(mAnchorOffset);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
|
||||
public static final Parcelable.Creator<SavedState> CREATOR =
|
||||
new Parcelable.Creator<SavedState>() {
|
||||
@Override
|
||||
public SavedState createFromParcel(Parcel in) {
|
||||
return new SavedState(in);
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -20,14 +19,13 @@ import androidx.media3.session.MediaController;
|
||||
import androidx.media3.session.SessionCommand;
|
||||
import androidx.media3.session.SessionToken;
|
||||
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.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
@@ -35,12 +33,9 @@ import org.thoughtcrime.securesms.service.AudioPlaybackService;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
implements DcEventCenter.DcEventDelegate
|
||||
{
|
||||
private static final String TAG = AllMediaActivity.class.getSimpleName();
|
||||
implements DcEventCenter.DcEventDelegate {
|
||||
private static final String TAG = "AllMediaActivity";
|
||||
|
||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||
public static final String CONTACT_ID_EXTRA = "contact_id";
|
||||
@@ -51,13 +46,15 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
final int type1;
|
||||
final int type2;
|
||||
final int type3;
|
||||
|
||||
TabData(int title, int type1, int type2, int type3) {
|
||||
this.title = title;
|
||||
this.type1 = type1;
|
||||
this.type2 = type2;
|
||||
this.type3 = type3;
|
||||
}
|
||||
};
|
||||
}
|
||||
;
|
||||
|
||||
private DcContext dcContext;
|
||||
private int chatId;
|
||||
@@ -82,7 +79,9 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle, boolean ready) {
|
||||
tabs.add(new TabData(R.string.webxdc_apps, DcMsg.DC_MSG_WEBXDC, 0, 0));
|
||||
tabs.add(new TabData(R.string.tab_gallery, DcMsg.DC_MSG_IMAGE, DcMsg.DC_MSG_GIF, DcMsg.DC_MSG_VIDEO));
|
||||
tabs.add(
|
||||
new TabData(
|
||||
R.string.tab_gallery, DcMsg.DC_MSG_IMAGE, DcMsg.DC_MSG_GIF, DcMsg.DC_MSG_VIDEO));
|
||||
tabs.add(new TabData(R.string.audio, DcMsg.DC_MSG_AUDIO, DcMsg.DC_MSG_VOICE, 0));
|
||||
tabs.add(new TabData(R.string.files, DcMsg.DC_MSG_FILE, 0, 0));
|
||||
|
||||
@@ -94,7 +93,8 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
ActionBar supportActionBar = getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar.setDisplayHomeAsUpEnabled(true);
|
||||
supportActionBar.setTitle(isGlobalGallery() ? R.string.menu_all_media : R.string.apps_and_media);
|
||||
supportActionBar.setTitle(
|
||||
isGlobalGallery() ? R.string.menu_all_media : R.string.apps_and_media);
|
||||
}
|
||||
|
||||
this.tabLayout.setupWithViewPager(viewPager);
|
||||
@@ -123,22 +123,21 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleEvent(@NonNull DcEvent event) {
|
||||
}
|
||||
public void handleEvent(@NonNull DcEvent event) {}
|
||||
|
||||
private void initializeResources() {
|
||||
chatId = getIntent().getIntExtra(CHAT_ID_EXTRA, 0);
|
||||
contactId = getIntent().getIntExtra(CONTACT_ID_EXTRA, 0);
|
||||
|
||||
if (contactId!=0) {
|
||||
if (contactId != 0) {
|
||||
chatId = dcContext.getChatIdByContactId(contactId);
|
||||
}
|
||||
|
||||
if(chatId!=0) {
|
||||
if (chatId != 0) {
|
||||
DcChat dcChat = dcContext.getChat(chatId);
|
||||
if(!dcChat.isMultiUser()) {
|
||||
if (!dcChat.isMultiUser()) {
|
||||
final int[] members = dcContext.getChatContacts(chatId);
|
||||
contactId = members.length>=1? members[0] : 0;
|
||||
contactId = members.length >= 1 ? members[0] : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,22 +147,20 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void initializeMediaController() {
|
||||
SessionToken sessionToken = new SessionToken(this,
|
||||
new ComponentName(this, AudioPlaybackService.class));
|
||||
mediaControllerFuture = new MediaController.Builder(this, sessionToken)
|
||||
.buildAsync();
|
||||
mediaControllerFuture.addListener(() -> {
|
||||
SessionToken sessionToken =
|
||||
new SessionToken(this, new ComponentName(this, AudioPlaybackService.class));
|
||||
mediaControllerFuture = new MediaController.Builder(this, sessionToken).buildAsync();
|
||||
mediaControllerFuture.addListener(
|
||||
() -> {
|
||||
try {
|
||||
mediaController = mediaControllerFuture.get();
|
||||
addActivityContext(
|
||||
this.getIntent().getExtras(),
|
||||
this.getClass().getName()
|
||||
);
|
||||
addActivityContext(this.getIntent().getExtras(), this.getClass().getName());
|
||||
playbackViewModel.setMediaController(mediaController);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error connecting to audio playback service", e);
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(this));
|
||||
},
|
||||
ContextCompat.getMainExecutor(this));
|
||||
}
|
||||
|
||||
private void addActivityContext(Bundle extras, String activityClassName) {
|
||||
@@ -182,7 +179,7 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private boolean isGlobalGallery() {
|
||||
return contactId==0 && chatId==0;
|
||||
return contactId == 0 && chatId == 0;
|
||||
}
|
||||
|
||||
private class AllMediaPagerAdapter extends FragmentStatePagerAdapter {
|
||||
@@ -216,10 +213,14 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
if (data.type1 == DcMsg.DC_MSG_IMAGE) {
|
||||
fragment = new AllMediaGalleryFragment();
|
||||
args.putInt(AllMediaGalleryFragment.CHAT_ID_EXTRA, (chatId==0&&!isGlobalGallery())? -1 : chatId);
|
||||
args.putInt(
|
||||
AllMediaGalleryFragment.CHAT_ID_EXTRA,
|
||||
(chatId == 0 && !isGlobalGallery()) ? -1 : chatId);
|
||||
} else {
|
||||
fragment = new AllMediaDocumentsFragment();
|
||||
args.putInt(AllMediaDocumentsFragment.CHAT_ID_EXTRA, (chatId==0&&!isGlobalGallery())? -1 : chatId);
|
||||
args.putInt(
|
||||
AllMediaDocumentsFragment.CHAT_ID_EXTRA,
|
||||
(chatId == 0 && !isGlobalGallery()) ? -1 : chatId);
|
||||
args.putInt(AllMediaDocumentsFragment.VIEWTYPE1, data.type1);
|
||||
args.putInt(AllMediaDocumentsFragment.VIEWTYPE2, data.type2);
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.b44t.messenger.DcMsg;
|
||||
import com.codewaves.stickyheadergrid.StickyHeaderGridAdapter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.thoughtcrime.securesms.components.DocumentView;
|
||||
import org.thoughtcrime.securesms.components.WebxdcView;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||
@@ -22,10 +22,6 @@ import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
class AllMediaDocumentsAdapter extends StickyHeaderGridAdapter {
|
||||
|
||||
private final Context context;
|
||||
@@ -59,10 +55,8 @@ class AllMediaDocumentsAdapter extends StickyHeaderGridAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
AllMediaDocumentsAdapter(@NonNull Context context,
|
||||
BucketedThreadMedia media,
|
||||
ItemClickListener clickListener)
|
||||
{
|
||||
AllMediaDocumentsAdapter(
|
||||
@NonNull Context context, BucketedThreadMedia media, ItemClickListener clickListener) {
|
||||
this.context = context;
|
||||
this.media = media;
|
||||
this.itemClickListener = clickListener;
|
||||
@@ -78,23 +72,28 @@ class AllMediaDocumentsAdapter extends StickyHeaderGridAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StickyHeaderGridAdapter.HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int headerType) {
|
||||
return new HeaderHolder(LayoutInflater.from(context).inflate(R.layout.contact_selection_list_divider, parent, false));
|
||||
public StickyHeaderGridAdapter.HeaderViewHolder onCreateHeaderViewHolder(
|
||||
ViewGroup parent, int headerType) {
|
||||
return new HeaderHolder(
|
||||
LayoutInflater.from(context)
|
||||
.inflate(R.layout.contact_selection_list_divider, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemViewHolder onCreateItemViewHolder(ViewGroup parent, int itemType) {
|
||||
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.profile_document_item, parent, false));
|
||||
return new ViewHolder(
|
||||
LayoutInflater.from(context).inflate(R.layout.profile_document_item, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindHeaderViewHolder(StickyHeaderGridAdapter.HeaderViewHolder viewHolder, int section) {
|
||||
((HeaderHolder)viewHolder).textView.setText(media.getName(section));
|
||||
public void onBindHeaderViewHolder(
|
||||
StickyHeaderGridAdapter.HeaderViewHolder viewHolder, int section) {
|
||||
((HeaderHolder) viewHolder).textView.setText(media.getName(section));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindItemViewHolder(ItemViewHolder itemViewHolder, int section, int offset) {
|
||||
ViewHolder viewHolder = ((ViewHolder)itemViewHolder);
|
||||
ViewHolder viewHolder = ((ViewHolder) itemViewHolder);
|
||||
DcMsg dcMsg = media.get(section, offset);
|
||||
Slide slide = MediaUtil.getSlideForMsg(context, dcMsg);
|
||||
|
||||
@@ -104,46 +103,60 @@ class AllMediaDocumentsAdapter extends StickyHeaderGridAdapter {
|
||||
|
||||
viewHolder.audioView.setVisibility(View.VISIBLE);
|
||||
viewHolder.audioView.setPlaybackViewModel(playbackViewModel);
|
||||
viewHolder.audioView.setAudio((AudioSlide)slide);
|
||||
viewHolder.audioView.setAudio((AudioSlide) slide);
|
||||
viewHolder.audioView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg));
|
||||
viewHolder.audioView.setOnLongClickListener(view -> { itemClickListener.onMediaLongClicked(dcMsg); return true; });
|
||||
viewHolder.audioView.setOnLongClickListener(
|
||||
view -> {
|
||||
itemClickListener.onMediaLongClicked(dcMsg);
|
||||
return true;
|
||||
});
|
||||
viewHolder.audioView.disablePlayer(!selected.isEmpty());
|
||||
viewHolder.itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg));
|
||||
viewHolder.date.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else if (slide != null && slide.isWebxdcDocument()) {
|
||||
} else if (slide != null && slide.isWebxdcDocument()) {
|
||||
viewHolder.audioView.setVisibility(View.GONE);
|
||||
viewHolder.documentView.setVisibility(View.GONE);
|
||||
|
||||
viewHolder.webxdcView.setVisibility(View.VISIBLE);
|
||||
viewHolder.webxdcView.setWebxdc(dcMsg, "");
|
||||
viewHolder.webxdcView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg));
|
||||
viewHolder.webxdcView.setOnLongClickListener(view -> { itemClickListener.onMediaLongClicked(dcMsg); return true; });
|
||||
viewHolder.webxdcView.setOnLongClickListener(
|
||||
view -> {
|
||||
itemClickListener.onMediaLongClicked(dcMsg);
|
||||
return true;
|
||||
});
|
||||
viewHolder.itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg));
|
||||
viewHolder.date.setVisibility(View.GONE);
|
||||
}
|
||||
else if (slide != null && slide.hasDocument()) {
|
||||
} else if (slide != null && slide.hasDocument()) {
|
||||
viewHolder.audioView.setVisibility(View.GONE);
|
||||
viewHolder.webxdcView.setVisibility(View.GONE);
|
||||
|
||||
viewHolder.documentView.setVisibility(View.VISIBLE);
|
||||
viewHolder.documentView.setDocument((DocumentSlide)slide);
|
||||
viewHolder.documentView.setDocument((DocumentSlide) slide);
|
||||
viewHolder.documentView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg));
|
||||
viewHolder.documentView.setOnLongClickListener(view -> { itemClickListener.onMediaLongClicked(dcMsg); return true; });
|
||||
viewHolder.documentView.setOnLongClickListener(
|
||||
view -> {
|
||||
itemClickListener.onMediaLongClicked(dcMsg);
|
||||
return true;
|
||||
});
|
||||
viewHolder.itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg));
|
||||
viewHolder.date.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
viewHolder.documentView.setVisibility(View.GONE);
|
||||
viewHolder.audioView.setVisibility(View.GONE);
|
||||
viewHolder.webxdcView.setVisibility(View.GONE);
|
||||
viewHolder.date.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
viewHolder.itemView.setOnLongClickListener(view -> { itemClickListener.onMediaLongClicked(dcMsg); return true; });
|
||||
viewHolder.itemView.setOnLongClickListener(
|
||||
view -> {
|
||||
itemClickListener.onMediaLongClicked(dcMsg);
|
||||
return true;
|
||||
});
|
||||
viewHolder.itemView.setSelected(selected.contains(dcMsg));
|
||||
|
||||
viewHolder.date.setText(DateUtils.getBriefRelativeTimeSpanString(context, dcMsg.getTimestamp()));
|
||||
viewHolder.date.setText(
|
||||
DateUtils.getBriefRelativeTimeSpanString(context, dcMsg.getTimestamp()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -184,6 +197,7 @@ class AllMediaDocumentsAdapter extends StickyHeaderGridAdapter {
|
||||
|
||||
interface ItemClickListener {
|
||||
void onMediaClicked(@NonNull DcMsg mediaRecord);
|
||||
|
||||
void onMediaLongClicked(DcMsg mediaRecord);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
@@ -20,25 +19,20 @@ import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager;
|
||||
|
||||
import java.util.Set;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class AllMediaDocumentsFragment
|
||||
extends MessageSelectorFragment
|
||||
public class AllMediaDocumentsFragment extends MessageSelectorFragment
|
||||
implements LoaderManager.LoaderCallbacks<BucketedThreadMediaLoader.BucketedThreadMedia>,
|
||||
AllMediaDocumentsAdapter.ItemClickListener
|
||||
{
|
||||
AllMediaDocumentsAdapter.ItemClickListener {
|
||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||
public static final String VIEWTYPE1 = "viewtype1";
|
||||
public static final String VIEWTYPE2 = "viewtype2";
|
||||
@@ -64,7 +58,8 @@ public class AllMediaDocumentsFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.profile_documents_fragment, container, false);
|
||||
|
||||
this.recyclerView = ViewUtil.findById(view, R.id.recycler_view);
|
||||
@@ -74,11 +69,12 @@ public class AllMediaDocumentsFragment
|
||||
// add padding to avoid content hidden behind system bars
|
||||
ViewUtil.applyWindowInsets(recyclerView, true, false, true, true);
|
||||
|
||||
AllMediaDocumentsAdapter adapter = new AllMediaDocumentsAdapter(getContext(),
|
||||
new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()),
|
||||
this);
|
||||
AllMediaDocumentsAdapter adapter =
|
||||
new AllMediaDocumentsAdapter(
|
||||
getContext(), new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()), this);
|
||||
this.recyclerView.setAdapter(adapter);
|
||||
adapter.setPlaybackViewModel(new ViewModelProvider(requireActivity()).get(AudioPlaybackViewModel.class));
|
||||
adapter.setPlaybackViewModel(
|
||||
new ViewModelProvider(requireActivity()).get(AudioPlaybackViewModel.class));
|
||||
this.recyclerView.setLayoutManager(gridManager);
|
||||
this.recyclerView.setHasFixedSize(true);
|
||||
|
||||
@@ -109,12 +105,15 @@ public class AllMediaDocumentsFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<BucketedThreadMediaLoader.BucketedThreadMedia> onCreateLoader(int i, Bundle bundle) {
|
||||
public Loader<BucketedThreadMediaLoader.BucketedThreadMedia> onCreateLoader(
|
||||
int i, Bundle bundle) {
|
||||
return new BucketedThreadMediaLoader(getContext(), chatId, viewtype1, viewtype2, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<BucketedThreadMediaLoader.BucketedThreadMedia> loader, BucketedThreadMediaLoader.BucketedThreadMedia bucketedThreadMedia) {
|
||||
public void onLoadFinished(
|
||||
Loader<BucketedThreadMediaLoader.BucketedThreadMedia> loader,
|
||||
BucketedThreadMediaLoader.BucketedThreadMedia bucketedThreadMedia) {
|
||||
((AllMediaDocumentsAdapter) recyclerView.getAdapter()).setMedia(bucketedThreadMedia);
|
||||
((AllMediaDocumentsAdapter) recyclerView.getAdapter()).notifyAllSectionsDataSetChanged();
|
||||
|
||||
@@ -122,7 +121,7 @@ public class AllMediaDocumentsFragment
|
||||
if (chatId == DC_CHAT_NO_CHAT) {
|
||||
if (viewtype1 == DcMsg.DC_MSG_WEBXDC) {
|
||||
noMedia.setText(R.string.all_apps_empty_hint);
|
||||
} else if (viewtype1 == DcMsg.DC_MSG_FILE){
|
||||
} else if (viewtype1 == DcMsg.DC_MSG_FILE) {
|
||||
noMedia.setText(R.string.all_files_empty_hint);
|
||||
} else {
|
||||
noMedia.setText(R.string.tab_all_media_empty_hint);
|
||||
@@ -137,7 +136,8 @@ public class AllMediaDocumentsFragment
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<BucketedThreadMediaLoader.BucketedThreadMedia> cursorLoader) {
|
||||
((AllMediaDocumentsAdapter) recyclerView.getAdapter()).setMedia(new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()));
|
||||
((AllMediaDocumentsAdapter) recyclerView.getAdapter())
|
||||
.setMedia(new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -168,7 +168,7 @@ public class AllMediaDocumentsFragment
|
||||
|
||||
private void handleMediaPreviewClick(@NonNull DcMsg dcMsg) {
|
||||
// audio is started by the play-button
|
||||
if (dcMsg.getType()==DcMsg.DC_MSG_AUDIO || dcMsg.getType()==DcMsg.DC_MSG_VOICE) {
|
||||
if (dcMsg.getType() == DcMsg.DC_MSG_AUDIO || dcMsg.getType() == DcMsg.DC_MSG_VOICE) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -216,7 +216,8 @@ public class AllMediaDocumentsFragment
|
||||
}
|
||||
menu.findItem(R.id.menu_resend).setVisible(canResend);
|
||||
|
||||
boolean webxdcApp = singleSelection && messageRecords.iterator().next().getType() == DcMsg.DC_MSG_WEBXDC;
|
||||
boolean webxdcApp =
|
||||
singleSelection && messageRecords.iterator().next().getType() == DcMsg.DC_MSG_WEBXDC;
|
||||
menu.findItem(R.id.menu_add_to_home_screen).setVisible(webxdcApp);
|
||||
}
|
||||
|
||||
@@ -243,20 +244,26 @@ public class AllMediaDocumentsFragment
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem menuItem) {
|
||||
int itemId = menuItem.getItemId();
|
||||
AudioPlaybackViewModel playbackViewModel = new ViewModelProvider(requireActivity()).get(AudioPlaybackViewModel.class);
|
||||
AudioPlaybackViewModel playbackViewModel =
|
||||
new ViewModelProvider(requireActivity()).get(AudioPlaybackViewModel.class);
|
||||
if (itemId == R.id.details) {
|
||||
handleDisplayDetails(getSelectedMessageRecord(getListAdapter().getSelectedMedia()));
|
||||
mode.finish();
|
||||
return true;
|
||||
} else if (itemId == R.id.delete) {
|
||||
handleDeleteMessages(chatId, getListAdapter().getSelectedMedia(), playbackViewModel::stopByIds, playbackViewModel::stopByIds);
|
||||
handleDeleteMessages(
|
||||
chatId,
|
||||
getListAdapter().getSelectedMedia(),
|
||||
playbackViewModel::stopByIds,
|
||||
playbackViewModel::stopByIds);
|
||||
mode.finish();
|
||||
return true;
|
||||
} else if (itemId == R.id.share) {
|
||||
handleShare(getSelectedMessageRecord(getListAdapter().getSelectedMedia()));
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_add_to_home_screen) {
|
||||
WebxdcActivity.addToHomeScreen(getActivity(), getSelectedMessageRecord(getListAdapter().getSelectedMedia()).getId());
|
||||
WebxdcActivity.addToHomeScreen(
|
||||
getActivity(), getSelectedMessageRecord(getListAdapter().getSelectedMedia()).getId());
|
||||
mode.finish();
|
||||
return true;
|
||||
} else if (itemId == R.id.show_in_chat) {
|
||||
|
||||
@@ -5,22 +5,18 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.b44t.messenger.DcMsg;
|
||||
import com.codewaves.stickyheadergrid.StickyHeaderGridAdapter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader.BucketedThreadMedia;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
class AllMediaGalleryAdapter extends StickyHeaderGridAdapter {
|
||||
|
||||
private final Context context;
|
||||
@@ -50,11 +46,11 @@ class AllMediaGalleryAdapter extends StickyHeaderGridAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
AllMediaGalleryAdapter(@NonNull Context context,
|
||||
AllMediaGalleryAdapter(
|
||||
@NonNull Context context,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
BucketedThreadMedia media,
|
||||
ItemClickListener clickListener)
|
||||
{
|
||||
ItemClickListener clickListener) {
|
||||
this.context = context;
|
||||
this.glideRequests = glideRequests;
|
||||
this.media = media;
|
||||
@@ -67,25 +63,30 @@ class AllMediaGalleryAdapter extends StickyHeaderGridAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StickyHeaderGridAdapter.HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int headerType) {
|
||||
return new HeaderHolder(LayoutInflater.from(context).inflate(R.layout.contact_selection_list_divider, parent, false));
|
||||
public StickyHeaderGridAdapter.HeaderViewHolder onCreateHeaderViewHolder(
|
||||
ViewGroup parent, int headerType) {
|
||||
return new HeaderHolder(
|
||||
LayoutInflater.from(context)
|
||||
.inflate(R.layout.contact_selection_list_divider, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemViewHolder onCreateItemViewHolder(ViewGroup parent, int itemType) {
|
||||
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.profile_gallery_item, parent, false));
|
||||
return new ViewHolder(
|
||||
LayoutInflater.from(context).inflate(R.layout.profile_gallery_item, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindHeaderViewHolder(StickyHeaderGridAdapter.HeaderViewHolder viewHolder, int section) {
|
||||
((HeaderHolder)viewHolder).textView.setText(media.getName(section));
|
||||
public void onBindHeaderViewHolder(
|
||||
StickyHeaderGridAdapter.HeaderViewHolder viewHolder, int section) {
|
||||
((HeaderHolder) viewHolder).textView.setText(media.getName(section));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindItemViewHolder(ItemViewHolder viewHolder, int section, int offset) {
|
||||
DcMsg mediaRecord = media.get(section, offset);
|
||||
ThumbnailView thumbnailView = ((ViewHolder)viewHolder).imageView;
|
||||
View selectedIndicator = ((ViewHolder)viewHolder).selectedIndicator;
|
||||
ThumbnailView thumbnailView = ((ViewHolder) viewHolder).imageView;
|
||||
View selectedIndicator = ((ViewHolder) viewHolder).selectedIndicator;
|
||||
Slide slide = MediaUtil.getSlideForMsg(context, mediaRecord);
|
||||
|
||||
if (slide != null) {
|
||||
@@ -93,7 +94,8 @@ class AllMediaGalleryAdapter extends StickyHeaderGridAdapter {
|
||||
}
|
||||
|
||||
thumbnailView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord));
|
||||
thumbnailView.setOnLongClickListener(view -> {
|
||||
thumbnailView.setOnLongClickListener(
|
||||
view -> {
|
||||
itemClickListener.onMediaLongClicked(mediaRecord);
|
||||
return true;
|
||||
});
|
||||
@@ -139,6 +141,7 @@ class AllMediaGalleryAdapter extends StickyHeaderGridAdapter {
|
||||
|
||||
interface ItemClickListener {
|
||||
void onMediaClicked(@NonNull DcMsg mediaRecord);
|
||||
|
||||
void onMediaLongClicked(DcMsg mediaRecord);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,19 +12,17 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager;
|
||||
|
||||
import java.util.Set;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
@@ -32,13 +30,9 @@ import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class AllMediaGalleryFragment
|
||||
extends MessageSelectorFragment
|
||||
public class AllMediaGalleryFragment extends MessageSelectorFragment
|
||||
implements LoaderManager.LoaderCallbacks<BucketedThreadMediaLoader.BucketedThreadMedia>,
|
||||
AllMediaGalleryAdapter.ItemClickListener
|
||||
{
|
||||
AllMediaGalleryAdapter.ItemClickListener {
|
||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||
|
||||
protected TextView noMedia;
|
||||
@@ -58,7 +52,8 @@ public class AllMediaGalleryFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.profile_gallery_fragment, container, false);
|
||||
|
||||
this.recyclerView = ViewUtil.findById(view, R.id.media_grid);
|
||||
@@ -68,7 +63,9 @@ public class AllMediaGalleryFragment
|
||||
// add padding to avoid content hidden behind system bars
|
||||
ViewUtil.applyWindowInsets(recyclerView, true, false, true, true);
|
||||
|
||||
this.recyclerView.setAdapter(new AllMediaGalleryAdapter(getContext(),
|
||||
this.recyclerView.setAdapter(
|
||||
new AllMediaGalleryAdapter(
|
||||
getContext(),
|
||||
GlideApp.with(this),
|
||||
new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()),
|
||||
this));
|
||||
@@ -94,7 +91,9 @@ public class AllMediaGalleryFragment
|
||||
}
|
||||
|
||||
private int getCols() {
|
||||
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE? 5 : 3;
|
||||
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
? 5
|
||||
: 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,12 +106,16 @@ public class AllMediaGalleryFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<BucketedThreadMediaLoader.BucketedThreadMedia> onCreateLoader(int i, Bundle bundle) {
|
||||
return new BucketedThreadMediaLoader(getContext(), chatId, DcMsg.DC_MSG_IMAGE, DcMsg.DC_MSG_GIF, DcMsg.DC_MSG_VIDEO);
|
||||
public Loader<BucketedThreadMediaLoader.BucketedThreadMedia> onCreateLoader(
|
||||
int i, Bundle bundle) {
|
||||
return new BucketedThreadMediaLoader(
|
||||
getContext(), chatId, DcMsg.DC_MSG_IMAGE, DcMsg.DC_MSG_GIF, DcMsg.DC_MSG_VIDEO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<BucketedThreadMediaLoader.BucketedThreadMedia> loader, BucketedThreadMediaLoader.BucketedThreadMedia bucketedThreadMedia) {
|
||||
public void onLoadFinished(
|
||||
Loader<BucketedThreadMediaLoader.BucketedThreadMedia> loader,
|
||||
BucketedThreadMediaLoader.BucketedThreadMedia bucketedThreadMedia) {
|
||||
((AllMediaGalleryAdapter) recyclerView.getAdapter()).setMedia(bucketedThreadMedia);
|
||||
((AllMediaGalleryAdapter) recyclerView.getAdapter()).notifyAllSectionsDataSetChanged();
|
||||
|
||||
@@ -125,7 +128,8 @@ public class AllMediaGalleryFragment
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<BucketedThreadMediaLoader.BucketedThreadMedia> cursorLoader) {
|
||||
((AllMediaGalleryAdapter) recyclerView.getAdapter()).setMedia(new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()));
|
||||
((AllMediaGalleryAdapter) recyclerView.getAdapter())
|
||||
.setMedia(new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.net.LinkProperties;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
@@ -19,14 +18,19 @@ import androidx.work.ExistingPeriodicWorkPolicy;
|
||||
import androidx.work.NetworkType;
|
||||
import androidx.work.PeriodicWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
import com.b44t.messenger.DcAccounts;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
import com.b44t.messenger.DcEventChannel;
|
||||
import com.b44t.messenger.DcEventEmitter;
|
||||
import com.b44t.messenger.FFITransport;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.thoughtcrime.securesms.calls.CallCoordinator;
|
||||
import org.thoughtcrime.securesms.connect.AccountManager;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
@@ -36,7 +40,6 @@ import org.thoughtcrime.securesms.connect.KeepAliveService;
|
||||
import org.thoughtcrime.securesms.connect.NetworkStateReceiver;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider;
|
||||
import org.thoughtcrime.securesms.geolocation.DcLocationManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.notifications.FcmReceiveService;
|
||||
import org.thoughtcrime.securesms.notifications.InChatSounds;
|
||||
@@ -48,16 +51,8 @@ import org.thoughtcrime.securesms.util.SignalProtocolLoggerProvider;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.webxdc.WebxdcGarbageCollectionWorker;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
|
||||
public class ApplicationContext extends MultiDexApplication {
|
||||
private static final String TAG = ApplicationContext.class.getSimpleName();
|
||||
private static final String TAG = "ApplicationContext";
|
||||
private static final Object initLock = new Object();
|
||||
private static volatile boolean isInitialized = false;
|
||||
|
||||
@@ -65,7 +60,6 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
private Rpc rpc;
|
||||
private DcContext dcContext;
|
||||
|
||||
private DcLocationManager dcLocationManager;
|
||||
private DcEventCenter eventCenter;
|
||||
private NotificationCenter notificationCenter;
|
||||
private JobManager jobManager;
|
||||
@@ -76,7 +70,7 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
private int debugOnLinkPropertiesChangedCount;
|
||||
|
||||
public static ApplicationContext getInstance(@NonNull Context context) {
|
||||
return (ApplicationContext)context.getApplicationContext();
|
||||
return (ApplicationContext) context.getApplicationContext();
|
||||
}
|
||||
|
||||
private static void ensureInitialized() {
|
||||
@@ -93,8 +87,8 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DcAccounts instance, waiting for initialization if necessary.
|
||||
* This method is thread-safe and will block until initialization is complete.
|
||||
* Get DcAccounts instance, waiting for initialization if necessary. This method is thread-safe
|
||||
* and will block until initialization is complete.
|
||||
*/
|
||||
public static DcAccounts getDcAccounts() {
|
||||
ensureInitialized();
|
||||
@@ -102,8 +96,8 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Rpc instance, waiting for initialization if necessary.
|
||||
* This method is thread-safe and will block until initialization is complete.
|
||||
* Get Rpc instance, waiting for initialization if necessary. This method is thread-safe and will
|
||||
* block until initialization is complete.
|
||||
*/
|
||||
public Rpc getRpc() {
|
||||
ensureInitialized();
|
||||
@@ -111,8 +105,8 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DcContext instance, waiting for initialization if necessary.
|
||||
* This method is thread-safe and will block until initialization is complete.
|
||||
* Get DcContext instance, waiting for initialization if necessary. This method is thread-safe and
|
||||
* will block until initialization is complete.
|
||||
*/
|
||||
public DcContext getDcContext() {
|
||||
ensureInitialized();
|
||||
@@ -121,8 +115,8 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
|
||||
/**
|
||||
* Set DcContext instance. This should only be called by AccountManager when switching accounts,
|
||||
* which only happens after initial initialization is complete.
|
||||
* This method is thread-safe but does NOT trigger initialization or notify waiting threads.
|
||||
* which only happens after initial initialization is complete. This method is thread-safe but
|
||||
* does NOT trigger initialization or notify waiting threads.
|
||||
*/
|
||||
public void setDcContext(DcContext dcContext) {
|
||||
synchronized (initLock) {
|
||||
@@ -131,17 +125,8 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DcLocationManager instance, waiting for initialization if necessary.
|
||||
* This method is thread-safe and will block until initialization is complete.
|
||||
*/
|
||||
public DcLocationManager getLocationManager() {
|
||||
ensureInitialized();
|
||||
return dcLocationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DcEventCenter instance, waiting for initialization if necessary.
|
||||
* This method is thread-safe and will block until initialization is complete.
|
||||
* Get DcEventCenter instance, waiting for initialization if necessary. This method is thread-safe
|
||||
* and will block until initialization is complete.
|
||||
*/
|
||||
public DcEventCenter getEventCenter() {
|
||||
ensureInitialized();
|
||||
@@ -149,8 +134,8 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get NotificationCenter instance, waiting for initialization if necessary.
|
||||
* This method is thread-safe and will block until initialization is complete.
|
||||
* Get NotificationCenter instance, waiting for initialization if necessary. This method is
|
||||
* thread-safe and will block until initialization is complete.
|
||||
*/
|
||||
public NotificationCenter getNotificationCenter() {
|
||||
ensureInitialized();
|
||||
@@ -161,12 +146,15 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
|
||||
Thread.setDefaultUncaughtExceptionHandler(
|
||||
(thread, throwable) -> {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
throwable.printStackTrace(new PrintWriter(stringWriter, true));
|
||||
String errorMsg = "Android " + Build.VERSION.RELEASE +":\n" + stringWriter.getBuffer().toString();
|
||||
String errorMsg =
|
||||
"Android " + Build.VERSION.RELEASE + ":\n" + stringWriter.getBuffer().toString();
|
||||
errorMsg += "\n" + LogViewFragment.grabLogcat();
|
||||
String subject = "ArcaneChat " + BuildConfig.VERSION_NAME + "-" + BuildConfig.FLAVOR + " Crash Report";
|
||||
String subject =
|
||||
"ArcaneChat " + BuildConfig.VERSION_NAME + "-" + BuildConfig.FLAVOR + " Crash Report";
|
||||
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
|
||||
@@ -179,7 +167,8 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
|
||||
try {
|
||||
ApplicationContext.this.finalize();
|
||||
} catch (Throwable e) {}
|
||||
} catch (Throwable e) {
|
||||
}
|
||||
});
|
||||
|
||||
// if (LeakCanary.isInAnalyzerProcess(this)) {
|
||||
@@ -194,14 +183,16 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
System.loadLibrary("native-utils");
|
||||
|
||||
// Initialize DcAccounts in background to avoid ANR during SQL migrations
|
||||
Util.runOnBackground(() -> {
|
||||
Util.runOnBackground(
|
||||
() -> {
|
||||
synchronized (initLock) {
|
||||
try {
|
||||
DcEventChannel eventChannel = new DcEventChannel();
|
||||
DcEventEmitter emitter = eventChannel.getEventEmitter();
|
||||
eventCenter = new DcEventCenter(this);
|
||||
|
||||
new Thread(() -> {
|
||||
new Thread(
|
||||
() -> {
|
||||
Log.i(TAG, "Starting event loop");
|
||||
while (true) {
|
||||
DcEvent event = emitter.getNextEvent();
|
||||
@@ -217,13 +208,17 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
}
|
||||
}
|
||||
Log.i("DeltaChat", "shutting down event handler");
|
||||
}, "eventThread").start();
|
||||
},
|
||||
"eventThread")
|
||||
.start();
|
||||
|
||||
dcAccounts = new DcAccounts(new File(getFilesDir(), "accounts").getAbsolutePath(), eventChannel);
|
||||
dcAccounts =
|
||||
new DcAccounts(
|
||||
new File(getFilesDir(), "accounts").getAbsolutePath(), eventChannel);
|
||||
Log.i(TAG, "DcAccounts created");
|
||||
rpc = new Rpc(new FFITransport(dcAccounts.getJsonrpcInstance()));
|
||||
Log.i(TAG, "Rpc created");
|
||||
AccountManager.getInstance().migrateToDcAccounts(this);
|
||||
AccountManager.getInstance().migrateToDcAccounts(this, dcAccounts);
|
||||
|
||||
int[] allAccounts = dcAccounts.getAll();
|
||||
Log.i(TAG, "Number of profiles: " + allAccounts.length);
|
||||
@@ -231,12 +226,28 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
DcContext ac = dcAccounts.getAccount(accountId);
|
||||
if (!ac.isOpen()) {
|
||||
try {
|
||||
DatabaseSecret secret = DatabaseSecretProvider.getOrCreateDatabaseSecret(this, accountId);
|
||||
DatabaseSecret secret =
|
||||
DatabaseSecretProvider.getOrCreateDatabaseSecret(this, accountId);
|
||||
boolean res = ac.open(secret.asString());
|
||||
if (res) Log.i(TAG, "Successfully opened account " + accountId + ", path: " + ac.getBlobdir());
|
||||
else Log.e(TAG, "Error opening account " + accountId + ", path: " + ac.getBlobdir());
|
||||
if (res)
|
||||
Log.i(
|
||||
TAG,
|
||||
"Successfully opened account "
|
||||
+ accountId
|
||||
+ ", path: "
|
||||
+ ac.getBlobdir());
|
||||
else
|
||||
Log.e(
|
||||
TAG, "Error opening account " + accountId + ", path: " + ac.getBlobdir());
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to open account " + accountId + ", path: " + ac.getBlobdir() + ": " + e);
|
||||
Log.e(
|
||||
TAG,
|
||||
"Failed to open account "
|
||||
+ accountId
|
||||
+ ", path: "
|
||||
+ ac.getBlobdir()
|
||||
+ ": "
|
||||
+ e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -245,7 +256,8 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
// 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
|
||||
// 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
|
||||
}
|
||||
@@ -259,13 +271,13 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
}
|
||||
dcContext = dcAccounts.getSelectedAccount();
|
||||
notificationCenter = new NotificationCenter(this);
|
||||
dcLocationManager = new DcLocationManager(this, dcContext);
|
||||
|
||||
isInitialized = true;
|
||||
initLock.notifyAll();
|
||||
Log.i(TAG, "DcAccounts initialization complete");
|
||||
|
||||
// set translations before starting I/O to avoid sending untranslated MDNs (issue #2288)
|
||||
// set translations before starting I/O to avoid sending untranslated MDNs (issue
|
||||
// #2288)
|
||||
DcHelper.setStockTranslations(this);
|
||||
|
||||
dcAccounts.startIo();
|
||||
@@ -289,32 +301,49 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
ConnectivityManager connectivityManager =
|
||||
(ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
connectivityManager.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback() {
|
||||
connectivityManager.registerDefaultNetworkCallback(
|
||||
new ConnectivityManager.NetworkCallback() {
|
||||
@Override
|
||||
public void onAvailable(@NonNull android.net.Network network) {
|
||||
Log.i("DeltaChat", "++++++++++++++++++ NetworkCallback.onAvailable() #" + debugOnAvailableCount++);
|
||||
Log.i(
|
||||
"DeltaChat",
|
||||
"++++++++++++++++++ NetworkCallback.onAvailable() #" + debugOnAvailableCount++);
|
||||
getDcAccounts().maybeNetwork();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockedStatusChanged(@NonNull android.net.Network network, boolean blocked) {
|
||||
Log.i("DeltaChat", "++++++++++++++++++ NetworkCallback.onBlockedStatusChanged() #" + debugOnBlockedStatusChangedCount++);
|
||||
public void onBlockedStatusChanged(
|
||||
@NonNull android.net.Network network, boolean blocked) {
|
||||
Log.i(
|
||||
"DeltaChat",
|
||||
"++++++++++++++++++ NetworkCallback.onBlockedStatusChanged() #"
|
||||
+ debugOnBlockedStatusChangedCount++);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCapabilitiesChanged(@NonNull android.net.Network network, NetworkCapabilities networkCapabilities) {
|
||||
public void onCapabilitiesChanged(
|
||||
@NonNull android.net.Network network, NetworkCapabilities networkCapabilities) {
|
||||
// usually called after onAvailable(), so a maybeNetwork seems contraproductive
|
||||
Log.i("DeltaChat", "++++++++++++++++++ NetworkCallback.onCapabilitiesChanged() #" + debugOnCapabilitiesChangedCount++);
|
||||
Log.i(
|
||||
"DeltaChat",
|
||||
"++++++++++++++++++ NetworkCallback.onCapabilitiesChanged() #"
|
||||
+ debugOnCapabilitiesChangedCount++);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLinkPropertiesChanged(@NonNull android.net.Network network, LinkProperties linkProperties) {
|
||||
Log.i("DeltaChat", "++++++++++++++++++ NetworkCallback.onLinkPropertiesChanged() #" + debugOnLinkPropertiesChangedCount++);
|
||||
public void onLinkPropertiesChanged(
|
||||
@NonNull android.net.Network network, LinkProperties linkProperties) {
|
||||
Log.i(
|
||||
"DeltaChat",
|
||||
"++++++++++++++++++ NetworkCallback.onLinkPropertiesChanged() #"
|
||||
+ debugOnLinkPropertiesChangedCount++);
|
||||
}
|
||||
});
|
||||
} // no else: use old method for debugging
|
||||
BroadcastReceiver networkStateReceiver = new NetworkStateReceiver();
|
||||
registerReceiver(networkStateReceiver, new IntentFilter(android.net.ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
registerReceiver(
|
||||
networkStateReceiver,
|
||||
new IntentFilter(android.net.ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
|
||||
KeepAliveService.maybeStartSelf(this);
|
||||
|
||||
@@ -322,16 +351,23 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
initializeJobManager();
|
||||
InChatSounds.getInstance(this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
EglUtils.getEglBase();
|
||||
CallCoordinator.getInstance(this);
|
||||
}
|
||||
|
||||
DynamicTheme.setDefaultDayNightMode(this);
|
||||
|
||||
IntentFilter filter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
|
||||
registerReceiver(new BroadcastReceiver() {
|
||||
registerReceiver(
|
||||
new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Util.localeChanged();
|
||||
DcHelper.setStockTranslations(context);
|
||||
}
|
||||
}, filter);
|
||||
},
|
||||
filter);
|
||||
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
||||
|
||||
@@ -342,31 +378,34 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
// MAYBE TODO: i think the ApplicationContext is also created
|
||||
// when the app is stated by FetchWorker timeouts.
|
||||
// in this case, the normal threads shall not be started.
|
||||
Constraints constraints = new Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build();
|
||||
PeriodicWorkRequest fetchWorkRequest = new PeriodicWorkRequest.Builder(
|
||||
Constraints constraints =
|
||||
new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build();
|
||||
PeriodicWorkRequest fetchWorkRequest =
|
||||
new PeriodicWorkRequest.Builder(
|
||||
FetchWorker.class,
|
||||
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, // usually 15 minutes
|
||||
TimeUnit.MILLISECONDS,
|
||||
PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, // the start may be preferred by up to 5 minutes, so we run every 10-15 minutes
|
||||
PeriodicWorkRequest
|
||||
.MIN_PERIODIC_FLEX_MILLIS, // the start may be preferred by up to 5 minutes,
|
||||
// so we run every 10-15 minutes
|
||||
TimeUnit.MILLISECONDS)
|
||||
.setConstraints(constraints)
|
||||
.build();
|
||||
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
|
||||
"FetchWorker",
|
||||
ExistingPeriodicWorkPolicy.KEEP,
|
||||
fetchWorkRequest);
|
||||
WorkManager.getInstance(this)
|
||||
.enqueueUniquePeriodicWork(
|
||||
"FetchWorker", ExistingPeriodicWorkPolicy.KEEP, fetchWorkRequest);
|
||||
}
|
||||
|
||||
PeriodicWorkRequest webxdcGarbageCollectionRequest = new PeriodicWorkRequest.Builder(
|
||||
PeriodicWorkRequest webxdcGarbageCollectionRequest =
|
||||
new PeriodicWorkRequest.Builder(
|
||||
WebxdcGarbageCollectionWorker.class,
|
||||
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS,
|
||||
TimeUnit.MILLISECONDS,
|
||||
PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS,
|
||||
TimeUnit.MILLISECONDS)
|
||||
.build();
|
||||
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
|
||||
WorkManager.getInstance(this)
|
||||
.enqueueUniquePeriodicWork(
|
||||
"WebxdcGarbageCollectionWorker",
|
||||
ExistingPeriodicWorkPolicy.KEEP,
|
||||
webxdcGarbageCollectionRequest);
|
||||
@@ -374,6 +413,14 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
Log.i("DeltaChat", "+++++++++++ ApplicationContext.onCreate() finished ++++++++++");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
super.onTerminate();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
EglUtils.release();
|
||||
}
|
||||
}
|
||||
|
||||
public JobManager getJobManager() {
|
||||
return jobManager;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
@@ -30,19 +31,17 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.preferences.AdvancedPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.AppearancePreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.PrivacyPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.PrivacyPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference;
|
||||
import org.thoughtcrime.securesms.qr.BackupTransferActivity;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
@@ -55,14 +54,12 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
* The Activity for application preference display and management.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarActivity
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener
|
||||
{
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final String PREFERENCE_CATEGORY_PROFILE = "preference_category_profile";
|
||||
private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "preference_category_notifications";
|
||||
private static final String PREFERENCE_CATEGORY_NOTIFICATIONS =
|
||||
"preference_category_notifications";
|
||||
private static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance";
|
||||
private static final String PREFERENCE_CATEGORY_CHATS = "preference_category_chats";
|
||||
private static final String PREFERENCE_CATEGORY_PRIVACY = "preference_category_privacy";
|
||||
@@ -89,18 +86,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (resultCode == RESULT_OK && requestCode == ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS) {
|
||||
showBackupProvider();
|
||||
return;
|
||||
}
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment);
|
||||
fragment.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
@@ -125,24 +110,41 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
||||
|
||||
public void showBackupProvider() {
|
||||
Intent intent = new Intent(this, BackupTransferActivity.class);
|
||||
intent.putExtra(BackupTransferActivity.TRANSFER_MODE, BackupTransferActivity.TransferMode.SENDER_SHOW_QR.getInt());
|
||||
intent.putExtra(
|
||||
BackupTransferActivity.TRANSFER_MODE,
|
||||
BackupTransferActivity.TransferMode.SENDER_SHOW_QR.getInt());
|
||||
startActivity(intent);
|
||||
overridePendingTransition(0, 0); // let the activity appear in the same way as the other pages (which are mostly fragments)
|
||||
overridePendingTransition(
|
||||
0, 0); // let the activity appear in the same way as the other pages (which are mostly
|
||||
// fragments)
|
||||
finishAffinity(); // see comment (**2) in BackupTransferActivity.doFinish()
|
||||
}
|
||||
|
||||
public static class ApplicationPreferenceFragment extends CorrectedPreferenceFragment implements DcEventCenter.DcEventDelegate {
|
||||
public static class ApplicationPreferenceFragment extends CorrectedPreferenceFragment
|
||||
implements DcEventCenter.DcEventDelegate {
|
||||
private ActivityResultLauncher<Intent> screenLockLauncher;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
screenLockLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == RESULT_OK) {
|
||||
((ApplicationPreferencesActivity) getActivity()).showBackupProvider();
|
||||
}
|
||||
});
|
||||
|
||||
this.findPreference(PREFERENCE_CATEGORY_PROFILE)
|
||||
.setOnPreferenceClickListener(new ProfileClickListener());
|
||||
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
|
||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_NOTIFICATIONS));
|
||||
.setOnPreferenceClickListener(
|
||||
new CategoryClickListener(PREFERENCE_CATEGORY_NOTIFICATIONS));
|
||||
this.findPreference(PREFERENCE_CATEGORY_CONNECTIVITY)
|
||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_CONNECTIVITY));
|
||||
.setOnPreferenceClickListener(
|
||||
new CategoryClickListener(PREFERENCE_CATEGORY_CONNECTIVITY));
|
||||
this.findPreference(PREFERENCE_CATEGORY_APPEARANCE)
|
||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE));
|
||||
this.findPreference(PREFERENCE_CATEGORY_CHATS)
|
||||
@@ -160,7 +162,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
||||
this.findPreference(PREFERENCE_CATEGORY_HELP)
|
||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_HELP));
|
||||
|
||||
DcHelper.getEventCenter(getActivity()).addObserver(DcContext.DC_EVENT_CONNECTIVITY_CHANGED, this);
|
||||
DcHelper.getEventCenter(getActivity())
|
||||
.addObserver(DcContext.DC_EVENT_CONNECTIVITY_CHANGED, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -172,7 +175,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
//noinspection ConstantConditions
|
||||
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.menu_settings);
|
||||
((ApplicationPreferencesActivity) getActivity())
|
||||
.getSupportActionBar()
|
||||
.setTitle(R.string.menu_settings);
|
||||
setCategorySummaries();
|
||||
}
|
||||
|
||||
@@ -186,12 +191,14 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
||||
public void handleEvent(@NonNull DcEvent event) {
|
||||
if (event.getId() == DcContext.DC_EVENT_CONNECTIVITY_CHANGED) {
|
||||
this.findPreference(PREFERENCE_CATEGORY_CONNECTIVITY)
|
||||
.setSummary(DcHelper.getConnectivitySummary(getActivity(), getString(R.string.connectivity_connected)));
|
||||
.setSummary(
|
||||
DcHelper.getConnectivitySummary(
|
||||
getActivity(), getString(R.string.connectivity_connected)));
|
||||
}
|
||||
}
|
||||
|
||||
private void setCategorySummaries() {
|
||||
((ProfilePreference)this.findPreference(PREFERENCE_CATEGORY_PROFILE)).refresh();
|
||||
((ProfilePreference) this.findPreference(PREFERENCE_CATEGORY_PROFILE)).refresh();
|
||||
|
||||
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
|
||||
.setSummary(NotificationsPreferenceFragment.getSummary(getActivity()));
|
||||
@@ -202,7 +209,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
||||
this.findPreference(PREFERENCE_CATEGORY_PRIVACY)
|
||||
.setSummary(PrivacyPreferenceFragment.getSummary(getActivity()));
|
||||
this.findPreference(PREFERENCE_CATEGORY_CONNECTIVITY)
|
||||
.setSummary(DcHelper.getConnectivitySummary(getActivity(), getString(R.string.connectivity_connected)));
|
||||
.setSummary(
|
||||
DcHelper.getConnectivitySummary(
|
||||
getActivity(), getString(R.string.connectivity_connected)));
|
||||
this.findPreference(PREFERENCE_CATEGORY_HELP)
|
||||
.setSummary(AdvancedPreferenceFragment.getVersion(getActivity()));
|
||||
}
|
||||
@@ -220,14 +229,21 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
||||
|
||||
switch (category) {
|
||||
case PREFERENCE_CATEGORY_NOTIFICATIONS:
|
||||
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getActivity());
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || notificationManager.areNotificationsEnabled()) {
|
||||
NotificationManagerCompat notificationManager =
|
||||
NotificationManagerCompat.from(getActivity());
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
|
||||
|| notificationManager.areNotificationsEnabled()) {
|
||||
fragment = new NotificationsPreferenceFragment();
|
||||
} else {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.notifications_disabled)
|
||||
.setMessage(R.string.perm_explain_access_to_notifications_denied)
|
||||
.setPositiveButton(R.string.perm_continue, (dialog, which) -> getActivity().startActivity(Permissions.getApplicationSettingsIntent(getActivity())))
|
||||
.setPositiveButton(
|
||||
R.string.perm_continue,
|
||||
(dialog, which) ->
|
||||
getActivity()
|
||||
.startActivity(
|
||||
Permissions.getApplicationSettingsIntent(getActivity())))
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
@@ -245,14 +261,20 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
||||
fragment = new PrivacyPreferenceFragment();
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_MULTIDEVICE:
|
||||
if (!ScreenLockUtil.applyScreenLock(getActivity(), getString(R.string.multidevice_title),
|
||||
getString(R.string.multidevice_this_creates_a_qr_code) + "\n\n" + getString(R.string.enter_system_secret_to_continue),
|
||||
ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS)) {
|
||||
if (!ScreenLockUtil.applyScreenLock(
|
||||
getActivity(),
|
||||
getString(R.string.multidevice_title),
|
||||
getString(R.string.multidevice_this_creates_a_qr_code)
|
||||
+ "\n\n"
|
||||
+ getString(R.string.enter_system_secret_to_continue),
|
||||
screenLockLauncher)) {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.multidevice_title)
|
||||
.setMessage(R.string.multidevice_this_creates_a_qr_code)
|
||||
.setPositiveButton(R.string.perm_continue,
|
||||
(dialog, which) -> ((ApplicationPreferencesActivity)getActivity()).showBackupProvider())
|
||||
.setPositiveButton(
|
||||
R.string.perm_continue,
|
||||
(dialog, which) ->
|
||||
((ApplicationPreferencesActivity) getActivity()).showBackupProvider())
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
;
|
||||
@@ -297,7 +319,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
public void onRequestPermissionsResult(
|
||||
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
|
||||
public class AttachContactActivity extends ContactSelectionActivity {
|
||||
|
||||
public static final String CONTACT_ID_EXTRA = "contact_id_extra";
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.activity.EdgeToEdge;
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -15,17 +13,14 @@ import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.Prefs;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
|
||||
public abstract class BaseActionBarActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = BaseActionBarActivity.class.getSimpleName();
|
||||
private static final String TAG = "BaseActionBarActivity";
|
||||
protected DynamicTheme dynamicTheme = new DynamicTheme();
|
||||
|
||||
protected void onPreCreate() {
|
||||
@@ -40,11 +35,13 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
|
||||
// Only enable Edge-to-Edge if it is well supported
|
||||
if (ViewUtil.isEdgeToEdgeSupported()) {
|
||||
// docs says to use: WindowCompat.enableEdgeToEdge(getWindow());
|
||||
// but it actually makes things worse, the next takes care of setting the 3-buttons navigation bar background
|
||||
// but it actually makes things worse, the next takes care of setting the 3-buttons navigation
|
||||
// bar background
|
||||
EdgeToEdge.enable(this);
|
||||
|
||||
// force white text in status bar so it visible over background color
|
||||
WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView()).setAppearanceLightStatusBars(false);
|
||||
WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView())
|
||||
.setAppearanceLightStatusBars(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,14 +65,12 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modified from: http://stackoverflow.com/a/13098824
|
||||
*/
|
||||
/** Modified from: http://stackoverflow.com/a/13098824 */
|
||||
private void forceOverflowMenu() {
|
||||
try {
|
||||
ViewConfiguration config = ViewConfiguration.get(this);
|
||||
Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
|
||||
if(menuKeyField != null) {
|
||||
if (menuKeyField != null) {
|
||||
menuKeyField.setAccessible(true);
|
||||
menuKeyField.setBoolean(config, false);
|
||||
}
|
||||
@@ -86,32 +81,24 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
public void makeSearchMenuVisible(final Menu menu, final MenuItem searchItem, boolean visible) {
|
||||
public void makeSearchMenuVisible(final Menu menu, final MenuItem searchItem) {
|
||||
for (int i = 0; i < menu.size(); ++i) {
|
||||
MenuItem item = menu.getItem(i);
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.menu_search_up || id == R.id.menu_search_down) {
|
||||
item.setVisible(visible);
|
||||
} else if (id == R.id.menu_search_counter) {
|
||||
item.setVisible(false); // always hide menu_search_counter initially
|
||||
} else if (item == searchItem) {
|
||||
; // searchItem is just always visible
|
||||
} else {
|
||||
item.setVisible(!visible); // if search is shown, other items are hidden - and the other way round
|
||||
item.setVisible(true);
|
||||
} else if (item != searchItem) {
|
||||
item.setVisible(false); // hide all other items
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected <T extends Fragment> T initFragment(@IdRes int target,
|
||||
@NonNull T fragment)
|
||||
{
|
||||
protected <T extends Fragment> T initFragment(@IdRes int target, @NonNull T fragment) {
|
||||
return initFragment(target, fragment, null);
|
||||
}
|
||||
|
||||
protected <T extends Fragment> T initFragment(@IdRes int target,
|
||||
@NonNull T fragment,
|
||||
@Nullable Bundle extras)
|
||||
{
|
||||
protected <T extends Fragment> T initFragment(
|
||||
@IdRes int target, @NonNull T fragment, @Nullable Bundle extras) {
|
||||
Bundle args = new Bundle();
|
||||
|
||||
if (extras != null) {
|
||||
@@ -119,7 +106,8 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
fragment.setArguments(args);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(target, fragment)
|
||||
.commitAllowingStateLoss();
|
||||
return fragment;
|
||||
|
||||
@@ -6,28 +6,22 @@ import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
|
||||
public abstract class BaseConversationItem extends LinearLayout
|
||||
implements BindableConversationItem
|
||||
{
|
||||
implements BindableConversationItem {
|
||||
static final long PULSE_HIGHLIGHT_MILLIS = 500;
|
||||
|
||||
protected DcMsg messageRecord;
|
||||
@@ -41,7 +35,8 @@ public abstract class BaseConversationItem extends LinearLayout
|
||||
|
||||
protected @NonNull Set<DcMsg> batchSelected = new HashSet<>();
|
||||
|
||||
protected final PassthroughClickListener passthroughClickListener = new PassthroughClickListener();
|
||||
protected final PassthroughClickListener passthroughClickListener =
|
||||
new PassthroughClickListener();
|
||||
|
||||
public BaseConversationItem(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
@@ -50,12 +45,12 @@ public abstract class BaseConversationItem extends LinearLayout
|
||||
this.rpc = DcHelper.getRpc(context);
|
||||
}
|
||||
|
||||
protected void bindPartial(@NonNull DcMsg messageRecord,
|
||||
protected void bindPartial(
|
||||
@NonNull DcMsg messageRecord,
|
||||
@NonNull DcChat dcChat,
|
||||
@NonNull Set<DcMsg> batchSelected,
|
||||
boolean pulseHighlight,
|
||||
@NonNull Recipient conversationRecipient)
|
||||
{
|
||||
@NonNull Recipient conversationRecipient) {
|
||||
this.messageRecord = messageRecord;
|
||||
this.dcChat = dcChat;
|
||||
this.batchSelected = batchSelected;
|
||||
@@ -64,9 +59,9 @@ public abstract class BaseConversationItem extends LinearLayout
|
||||
}
|
||||
|
||||
protected void setInteractionState(DcMsg messageRecord, boolean pulseHighlight) {
|
||||
final int[] attributes = new int[] {
|
||||
R.attr.conversation_item_background,
|
||||
R.attr.conversation_item_background_animated,
|
||||
final int[] attributes =
|
||||
new int[] {
|
||||
R.attr.conversation_item_background, R.attr.conversation_item_background_animated,
|
||||
};
|
||||
|
||||
if (batchSelected.contains(messageRecord)) {
|
||||
@@ -100,7 +95,8 @@ public abstract class BaseConversationItem extends LinearLayout
|
||||
|
||||
protected void onAccessibilityClick() {}
|
||||
|
||||
protected class PassthroughClickListener implements View.OnLongClickListener, View.OnClickListener {
|
||||
protected class PassthroughClickListener
|
||||
implements View.OnLongClickListener, View.OnClickListener {
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
@@ -137,13 +133,15 @@ public abstract class BaseConversationItem extends LinearLayout
|
||||
TextView detailsText = view.findViewById(R.id.details_text);
|
||||
detailsText.setText(messageRecord.getError());
|
||||
|
||||
AlertDialog d = new AlertDialog.Builder(context)
|
||||
AlertDialog d =
|
||||
new AlertDialog.Builder(context)
|
||||
.setView(view)
|
||||
.setTitle(R.string.error)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.create();
|
||||
d.show();
|
||||
} else if (messageRecord.getInfoType() == DcMsg.DC_INFO_CHAT_E2EE || messageRecord.getInfoType() == DcMsg.DC_INFO_PROTECTION_ENABLED) {
|
||||
} else if (messageRecord.getInfoType() == DcMsg.DC_INFO_CHAT_E2EE
|
||||
|| messageRecord.getInfoType() == DcMsg.DC_INFO_PROTECTION_ENABLED) {
|
||||
DcHelper.showProtectionEnabledDialog(context);
|
||||
} else if (messageRecord.getInfoType() == DcMsg.DC_INFO_INVALID_UNENCRYPTED_MAIL) {
|
||||
DcHelper.showInvalidUnencryptedDialog(context);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class BaseConversationListAdapter<T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T> {
|
||||
public abstract class BaseConversationListAdapter<T extends RecyclerView.ViewHolder>
|
||||
extends RecyclerView.Adapter<T> {
|
||||
protected final Set<Long> batchSet = Collections.synchronizedSet(new HashSet<Long>());
|
||||
protected boolean batchMode = false;
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@ import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
@@ -27,36 +27,39 @@ import androidx.core.content.pm.ShortcutInfoCompat;
|
||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContact;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.connect.DirectShareUtil;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.ShareUtil;
|
||||
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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class BaseConversationListFragment extends Fragment implements ActionMode.Callback {
|
||||
private static final String TAG = "BaseConversationListFragment";
|
||||
protected ActionMode actionMode;
|
||||
protected PulsingFloatingActionButton fab;
|
||||
|
||||
protected abstract boolean offerToArchive();
|
||||
|
||||
protected abstract void setFabVisibility(boolean isActionMode);
|
||||
|
||||
protected abstract BaseConversationListAdapter getListAdapter();
|
||||
|
||||
protected void onItemClick(long chatId) {
|
||||
if (actionMode == null) {
|
||||
((ConversationSelectedListener) requireActivity()).onCreateConversation((int)chatId);
|
||||
((ConversationSelectedListener) requireActivity()).onCreateConversation((int) chatId);
|
||||
} else {
|
||||
BaseConversationListAdapter adapter = getListAdapter();
|
||||
adapter.toggleThreadInBatchSet(chatId);
|
||||
@@ -71,8 +74,9 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void onItemLongClick(long chatId) {
|
||||
actionMode = ((AppCompatActivity)requireActivity()).startSupportActionMode(this);
|
||||
actionMode = ((AppCompatActivity) requireActivity()).startSupportActionMode(this);
|
||||
|
||||
if (actionMode != null) {
|
||||
getListAdapter().initializeBatchMode(true);
|
||||
@@ -89,32 +93,52 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
Intent intent = new Intent(getActivity(), NewConversationActivity.class);
|
||||
if (isRelayingMessageContent(getActivity())) {
|
||||
if (isActionMode) {
|
||||
fab.setOnClickListener(v -> {
|
||||
fab.setOnClickListener(
|
||||
v -> {
|
||||
final Set<Long> selectedChats = getListAdapter().getBatchSelections();
|
||||
ArrayList<Uri> uris = getSharedUris(getActivity());
|
||||
String message;
|
||||
if (isForwarding(getActivity())) {
|
||||
message = String.format(Util.getLocale(), getString(R.string.ask_forward_multiple), selectedChats.size());
|
||||
message =
|
||||
String.format(
|
||||
Util.getLocale(),
|
||||
getString(R.string.ask_forward_multiple),
|
||||
selectedChats.size());
|
||||
} else if (!uris.isEmpty()) {
|
||||
message = String.format(Util.getLocale(), getString(R.string.ask_send_files_to_selected_chats), uris.size(), selectedChats.size());
|
||||
message =
|
||||
String.format(
|
||||
Util.getLocale(),
|
||||
getString(R.string.ask_send_files_to_selected_chats),
|
||||
uris.size(),
|
||||
selectedChats.size());
|
||||
} else {
|
||||
message = String.format(Util.getLocale(), getString(R.string.share_text_multiple_chats), selectedChats.size(), getSharedText(getActivity()));
|
||||
message =
|
||||
String.format(
|
||||
Util.getLocale(),
|
||||
getString(R.string.share_text_multiple_chats),
|
||||
selectedChats.size(),
|
||||
getSharedText(getActivity()));
|
||||
}
|
||||
|
||||
Context context = getContext();
|
||||
if (context != null) {
|
||||
if (SendRelayedMessageUtil.containsVideoType(context, uris)) {
|
||||
message += "\n\n" + getString(R.string.videos_sent_without_recoding);
|
||||
}
|
||||
new AlertDialog.Builder(context)
|
||||
.setMessage(message)
|
||||
.setCancelable(false)
|
||||
.setNegativeButton(android.R.string.cancel, ((dialog, which) -> {}))
|
||||
.setPositiveButton(R.string.menu_send, (dialog, which) -> {
|
||||
SendRelayedMessageUtil.immediatelyRelay(getActivity(), selectedChats.toArray(new Long[selectedChats.size()]));
|
||||
.setPositiveButton(
|
||||
R.string.menu_send,
|
||||
(dialog, which) -> {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
SendRelayedMessageUtil.immediatelyRelay(
|
||||
activity,
|
||||
selectedChats.toArray(new Long[selectedChats.size()]),
|
||||
() -> activity.finish());
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
getActivity().finish();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
@@ -132,8 +156,8 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
final Set<Long> selectedChats = getListAdapter().getBatchSelections();
|
||||
for (long chatId : selectedChats) {
|
||||
DcChat dcChat = dcContext.getChat((int)chatId);
|
||||
if (dcChat.getVisibility()!=DcChat.DC_CHAT_VISIBILITY_PINNED) {
|
||||
DcChat dcChat = dcContext.getChat((int) chatId);
|
||||
if (dcChat.getVisibility() != DcChat.DC_CHAT_VISIBILITY_PINNED) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -144,7 +168,7 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
final Set<Long> selectedChats = getListAdapter().getBatchSelections();
|
||||
for (long chatId : selectedChats) {
|
||||
DcChat dcChat = dcContext.getChat((int)chatId);
|
||||
DcChat dcChat = dcContext.getChat((int) chatId);
|
||||
if (!dcChat.isMuted()) {
|
||||
return true;
|
||||
}
|
||||
@@ -152,13 +176,26 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean areSomeSelectedChatsFresh() {
|
||||
DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
final Set<Long> selectedChats = getListAdapter().getBatchSelections();
|
||||
for (long chatId : selectedChats) {
|
||||
if (dcContext.getFreshMsgCount((int) chatId) > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handlePinAllSelected() {
|
||||
final DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
final Set<Long> selectedConversations = new HashSet<Long>(getListAdapter().getBatchSelections());
|
||||
final Set<Long> selectedConversations =
|
||||
new HashSet<Long>(getListAdapter().getBatchSelections());
|
||||
boolean doPin = areSomeSelectedChatsUnpinned();
|
||||
for (long chatId : selectedConversations) {
|
||||
dcContext.setChatVisibility((int)chatId,
|
||||
doPin? DcChat.DC_CHAT_VISIBILITY_PINNED : DcChat.DC_CHAT_VISIBILITY_NORMAL);
|
||||
dcContext.setChatVisibility(
|
||||
(int) chatId,
|
||||
doPin ? DcChat.DC_CHAT_VISIBILITY_PINNED : DcChat.DC_CHAT_VISIBILITY_NORMAL);
|
||||
}
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
@@ -168,11 +205,14 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
|
||||
private void handleMuteAllSelected() {
|
||||
final DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
final Set<Long> selectedConversations = new HashSet<Long>(getListAdapter().getBatchSelections());
|
||||
final Set<Long> selectedConversations =
|
||||
new HashSet<Long>(getListAdapter().getBatchSelections());
|
||||
if (areSomeSelectedChatsUnmuted()) {
|
||||
MuteDialog.show(getActivity(), duration -> {
|
||||
MuteDialog.show(
|
||||
getActivity(),
|
||||
duration -> {
|
||||
for (long chatId : selectedConversations) {
|
||||
dcContext.setChatMuteDuration((int)chatId, duration);
|
||||
dcContext.setChatMuteDuration((int) chatId, duration);
|
||||
}
|
||||
|
||||
if (actionMode != null) {
|
||||
@@ -183,7 +223,7 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
} else {
|
||||
// unmute
|
||||
for (long chatId : selectedConversations) {
|
||||
dcContext.setChatMuteDuration((int)chatId, 0);
|
||||
dcContext.setChatMuteDuration((int) chatId, 0);
|
||||
}
|
||||
|
||||
if (actionMode != null) {
|
||||
@@ -195,9 +235,10 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
|
||||
private void handleMarknoticedSelected() {
|
||||
final DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
final Set<Long> selectedConversations = new HashSet<Long>(getListAdapter().getBatchSelections());
|
||||
final Set<Long> selectedConversations =
|
||||
new HashSet<Long>(getListAdapter().getBatchSelections());
|
||||
for (long chatId : selectedConversations) {
|
||||
dcContext.marknoticedChat((int)chatId);
|
||||
dcContext.marknoticedChat((int) chatId);
|
||||
}
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
@@ -205,10 +246,31 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMarkfreshSelected() {
|
||||
final Set<Long> selectedConversations =
|
||||
new HashSet<Long>(getListAdapter().getBatchSelections());
|
||||
|
||||
final Rpc rpc = DcHelper.getRpc(requireActivity());
|
||||
try {
|
||||
int accId = rpc.getSelectedAccountId();
|
||||
for (long chatId : selectedConversations) {
|
||||
rpc.markfreshChat(accId, (int) chatId);
|
||||
}
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "RPC error", e);
|
||||
}
|
||||
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private void handleArchiveAllSelected() {
|
||||
final DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
final Set<Long> selectedConversations = new HashSet<Long>(getListAdapter().getBatchSelections());
|
||||
final Set<Long> selectedConversations =
|
||||
new HashSet<Long>(getListAdapter().getBatchSelections());
|
||||
final boolean archive = offerToArchive();
|
||||
|
||||
int snackBarTitleId;
|
||||
@@ -219,10 +281,8 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
int count = selectedConversations.size();
|
||||
String snackBarTitle = getResources().getQuantityString(snackBarTitleId, count, count);
|
||||
|
||||
new SnackbarAsyncTask<Void>(getView(), snackBarTitle,
|
||||
getString(R.string.undo),
|
||||
Snackbar.LENGTH_LONG, true)
|
||||
{
|
||||
new SnackbarAsyncTask<Void>(
|
||||
getView(), snackBarTitle, getString(R.string.undo), Snackbar.LENGTH_LONG, true) {
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
@@ -237,16 +297,18 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
@Override
|
||||
protected void executeAction(@Nullable Void parameter) {
|
||||
for (long chatId : selectedConversations) {
|
||||
dcContext.setChatVisibility((int)chatId,
|
||||
archive? DcChat.DC_CHAT_VISIBILITY_ARCHIVED : DcChat.DC_CHAT_VISIBILITY_NORMAL);
|
||||
dcContext.setChatVisibility(
|
||||
(int) chatId,
|
||||
archive ? DcChat.DC_CHAT_VISIBILITY_ARCHIVED : DcChat.DC_CHAT_VISIBILITY_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void reverseAction(@Nullable Void parameter) {
|
||||
for (long threadId : selectedConversations) {
|
||||
dcContext.setChatVisibility((int)threadId,
|
||||
archive? DcChat.DC_CHAT_VISIBILITY_NORMAL : DcChat.DC_CHAT_VISIBILITY_ARCHIVED);
|
||||
dcContext.setChatVisibility(
|
||||
(int) threadId,
|
||||
archive ? DcChat.DC_CHAT_VISIBILITY_NORMAL : DcChat.DC_CHAT_VISIBILITY_ARCHIVED);
|
||||
}
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
@@ -262,34 +324,56 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
final String alertText;
|
||||
if (chatsCount == 1) {
|
||||
long chatId = selectedChats.iterator().next();
|
||||
alertText = activity.getResources().getString(R.string.ask_delete_named_chat, dcContext.getChat((int)chatId).getName());
|
||||
alertText =
|
||||
activity
|
||||
.getResources()
|
||||
.getString(R.string.ask_delete_named_chat, dcContext.getChat((int) chatId).getName());
|
||||
} else {
|
||||
alertText = activity.getResources().getQuantityString(R.plurals.ask_delete_chat, chatsCount, chatsCount);
|
||||
alertText =
|
||||
activity
|
||||
.getResources()
|
||||
.getQuantityString(R.plurals.ask_delete_chat, chatsCount, chatsCount);
|
||||
}
|
||||
|
||||
String alertButton = getString(R.string.delete_for_me);
|
||||
for (long chatId : selectedChats) {
|
||||
if (dcContext.getChat((int) chatId).shallLeaveBeforeDelete(dcContext)) {
|
||||
alertButton = getString(R.string.menu_leave_and_delete);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
alert.setMessage(alertText);
|
||||
alert.setCancelable(true);
|
||||
|
||||
alert.setPositiveButton(R.string.delete, (dialog, which) -> {
|
||||
|
||||
alert.setPositiveButton(
|
||||
alertButton,
|
||||
(dialog, which) -> {
|
||||
if (!selectedChats.isEmpty()) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
private ProgressDialog dialog;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
dialog = ProgressDialog.show(getActivity(),
|
||||
dialog =
|
||||
ProgressDialog.show(
|
||||
getActivity(),
|
||||
"",
|
||||
requireActivity().getString(R.string.one_moment),
|
||||
true, false);
|
||||
true,
|
||||
false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
int accountId = dcContext.getAccountId();
|
||||
for (long chatId : selectedChats) {
|
||||
DcHelper.getNotificationCenter(requireContext()).removeNotifications(accountId, (int) chatId);
|
||||
DcHelper.getNotificationCenter(requireContext())
|
||||
.removeNotifications(accountId, (int) chatId);
|
||||
if (dcContext.getChat((int) chatId).shallLeaveBeforeDelete(dcContext)) {
|
||||
dcContext.removeContactFromChat((int) chatId, DcContact.DC_CONTACT_ID_SELF);
|
||||
}
|
||||
dcContext.deleteChat((int) chatId);
|
||||
DirectShareUtil.clearShortcut(requireContext(), (int) chatId);
|
||||
}
|
||||
@@ -331,16 +415,24 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
intent.putExtra(ShareActivity.EXTRA_CHAT_ID, chat.getId());
|
||||
|
||||
Recipient recipient = new Recipient(activity, chat);
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
Util.runOnAnyBackgroundThread(
|
||||
() -> {
|
||||
Bitmap avatar = DirectShareUtil.getIconForShortcut(activity, recipient);
|
||||
ShortcutInfoCompat shortcutInfoCompat = new ShortcutInfoCompat.Builder(activity, "chat-" + dcContext.getAccountId() + "-" + chat.getId())
|
||||
ShortcutInfoCompat shortcutInfoCompat =
|
||||
new ShortcutInfoCompat.Builder(
|
||||
activity, "chat-" + dcContext.getAccountId() + "-" + chat.getId())
|
||||
.setShortLabel(chat.getName())
|
||||
.setIcon(IconCompat.createWithAdaptiveBitmap(avatar))
|
||||
.setIntent(intent)
|
||||
.build();
|
||||
Util.runOnMain(() -> {
|
||||
Util.runOnMain(
|
||||
() -> {
|
||||
if (!ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfoCompat, null)) {
|
||||
Toast.makeText(activity, "ErrAddToHomescreen: requestPinShortcut() failed", Toast.LENGTH_LONG).show();
|
||||
Toast.makeText(
|
||||
activity,
|
||||
"ErrAddToHomescreen: requestPinShortcut() failed",
|
||||
Toast.LENGTH_LONG)
|
||||
.show();
|
||||
} else if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
@@ -354,6 +446,7 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
if (!isRelayingMessageContent(requireActivity())) {
|
||||
final int selectedCount = getListAdapter().getBatchSelections().size();
|
||||
menu.findItem(R.id.menu_add_to_home_screen).setVisible(selectedCount == 1);
|
||||
|
||||
MenuItem archiveItem = menu.findItem(R.id.menu_archive_selected);
|
||||
if (offerToArchive()) {
|
||||
archiveItem.setIcon(R.drawable.ic_archive_white_24dp);
|
||||
@@ -362,6 +455,7 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
archiveItem.setIcon(R.drawable.ic_unarchive_white_24dp);
|
||||
archiveItem.setTitle(R.string.menu_unarchive_chat);
|
||||
}
|
||||
|
||||
MenuItem pinItem = menu.findItem(R.id.menu_pin_selected);
|
||||
if (areSomeSelectedChatsUnpinned()) {
|
||||
pinItem.setIcon(R.drawable.ic_pin_white);
|
||||
@@ -370,12 +464,17 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
pinItem.setIcon(R.drawable.ic_unpin_white);
|
||||
pinItem.setTitle(R.string.unpin_chat);
|
||||
}
|
||||
|
||||
MenuItem muteItem = menu.findItem(R.id.menu_mute_selected);
|
||||
if (areSomeSelectedChatsUnmuted()) {
|
||||
muteItem.setTitle(R.string.menu_mute);
|
||||
} else {
|
||||
muteItem.setTitle(R.string.menu_unmute);
|
||||
}
|
||||
|
||||
final boolean someAreFresh = areSomeSelectedChatsFresh();
|
||||
menu.findItem(R.id.menu_marknoticed_selected).setVisible(someAreFresh);
|
||||
menu.findItem(R.id.menu_markfresh_selected).setVisible(!someAreFresh);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,7 +482,8 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
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
|
||||
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) {
|
||||
@@ -428,6 +528,9 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
} else if (itemId == R.id.menu_marknoticed_selected) {
|
||||
handleMarknoticedSelected();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_markfresh_selected) {
|
||||
handleMarkfreshSelected();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_add_to_home_screen) {
|
||||
handleAddToHomeScreen();
|
||||
return true;
|
||||
@@ -451,6 +554,7 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
|
||||
public interface ConversationSelectedListener {
|
||||
void onCreateConversation(int chatId);
|
||||
|
||||
void onSwitchToArchive();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,17 @@ package org.thoughtcrime.securesms;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import java.util.Set;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioView;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface BindableConversationItem extends Unbindable {
|
||||
void bind(@NonNull DcMsg messageRecord,
|
||||
void bind(
|
||||
@NonNull DcMsg messageRecord,
|
||||
@NonNull DcChat dcChat,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Set<DcMsg> batchSelected,
|
||||
@@ -29,9 +27,15 @@ public interface BindableConversationItem extends Unbindable {
|
||||
|
||||
interface EventListener {
|
||||
void onQuoteClicked(DcMsg messageRecord);
|
||||
|
||||
void onJumpToOriginalClicked(DcMsg messageRecord);
|
||||
|
||||
void onShowFullClicked(DcMsg messageRecord);
|
||||
|
||||
void onDownloadClicked(DcMsg messageRecord);
|
||||
|
||||
void onReactionClicked(DcMsg messageRecord);
|
||||
|
||||
void onStickerClicked(DcMsg messageRecord);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.b44t.messenger.DcLot;
|
||||
|
||||
import java.util.Set;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface BindableConversationListItem extends Unbindable {
|
||||
|
||||
public void bind(@NonNull ThreadRecord thread,
|
||||
public void bind(
|
||||
@NonNull ThreadRecord thread,
|
||||
int msgId,
|
||||
@NonNull DcLot dcSummary,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Set<Long> selectedThreads, boolean batchMode);
|
||||
@NonNull Set<Long> selectedThreads,
|
||||
boolean batchMode);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -14,10 +13,8 @@ import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
|
||||
import org.thoughtcrime.securesms.connect.DcContactsLoader;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
@@ -39,17 +36,18 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home: finish(); return true;
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class BlockedAndShareContactsFragment
|
||||
extends Fragment
|
||||
public static class BlockedAndShareContactsFragment extends Fragment
|
||||
implements LoaderManager.LoaderCallbacks<DcContactsLoader.Ret>,
|
||||
DcEventCenter.DcEventDelegate, ContactSelectionListAdapter.ItemClickListener {
|
||||
|
||||
DcEventCenter.DcEventDelegate,
|
||||
ContactSelectionListAdapter.ItemClickListener {
|
||||
|
||||
private RecyclerView recyclerView;
|
||||
private TextView emptyStateView;
|
||||
@@ -81,11 +79,8 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void initializeAdapter() {
|
||||
ContactSelectionListAdapter adapter = new ContactSelectionListAdapter(getActivity(),
|
||||
GlideApp.with(this),
|
||||
this,
|
||||
false,
|
||||
false);
|
||||
ContactSelectionListAdapter adapter =
|
||||
new ContactSelectionListAdapter(getActivity(), GlideApp.with(this), this, false, false);
|
||||
recyclerView.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@@ -112,7 +107,7 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
public void handleEvent(@NonNull DcEvent event) {
|
||||
if (event.getId()==DcContext.DC_EVENT_CONTACTS_CHANGED) {
|
||||
if (event.getId() == DcContext.DC_EVENT_CONTACTS_CHANGED) {
|
||||
restartLoader();
|
||||
}
|
||||
}
|
||||
@@ -131,7 +126,9 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
|
||||
.setMessage(R.string.ask_unblock_contact)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.menu_unblock_contact, (dialog, which) -> unblockContact(item.getContactId())).show();
|
||||
.setPositiveButton(
|
||||
R.string.menu_unblock_contact, (dialog, which) -> unblockContact(item.getContactId()))
|
||||
.show();
|
||||
}
|
||||
|
||||
private void unblockContact(int contactId) {
|
||||
@@ -143,5 +140,4 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
public void onItemLongClick(ContactSelectionListItem view) {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,12 +2,9 @@ package org.thoughtcrime.securesms;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
|
||||
@@ -35,7 +32,9 @@ public class ConnectivityActivity extends WebViewActivity implements DcEventCent
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
final String connectivityHtml = DcHelper.getContext(this).getConnectivityHtml()
|
||||
final String connectivityHtml =
|
||||
DcHelper.getContext(this)
|
||||
.getConnectivityHtml()
|
||||
.replace("</style>", " html { color-scheme: dark light; }</style>");
|
||||
webView.loadDataWithBaseURL(null, connectivityHtml, "text/html", "utf-8", null);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -29,7 +28,6 @@ import java.util.List;
|
||||
* Activity container for selecting a list of contacts.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class ContactMultiSelectionActivity extends ContactSelectionActivity {
|
||||
|
||||
@@ -74,7 +72,8 @@ public class ContactMultiSelectionActivity extends ContactSelectionActivity {
|
||||
List<Integer> selectedContacts = contactsFragment.getSelectedContacts();
|
||||
List<Integer> deselectedContacts = contactsFragment.getDeselectedContacts();
|
||||
resultIntent.putIntegerArrayListExtra(CONTACTS_EXTRA, new ArrayList<>(selectedContacts));
|
||||
resultIntent.putIntegerArrayListExtra(DESELECTED_CONTACTS_EXTRA, new ArrayList<>(deselectedContacts));
|
||||
resultIntent.putIntegerArrayListExtra(
|
||||
DESELECTED_CONTACTS_EXTRA, new ArrayList<>(deselectedContacts));
|
||||
setResult(RESULT_OK, resultIntent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.thoughtcrime.securesms;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
@@ -27,12 +26,10 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
* Base activity container for selecting a list of contacts.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public abstract class ContactSelectionActivity extends PassphraseRequiredActionBarActivity
|
||||
implements ContactSelectionListFragment.OnContactSelectedListener
|
||||
{
|
||||
private static final String TAG = ContactSelectionActivity.class.getSimpleName();
|
||||
implements ContactSelectionListFragment.OnContactSelectedListener {
|
||||
private static final String TAG = "ContactSelectionActivity";
|
||||
|
||||
protected ContactSelectionListFragment contactsFragment;
|
||||
|
||||
@@ -69,7 +66,9 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
contactsFragment = (ContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
|
||||
contactsFragment =
|
||||
(ContactSelectionListFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
|
||||
contactsFragment.setOnContactSelectedListener(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
|
||||
import static org.thoughtcrime.securesms.util.ShareUtil.isRelayingMessageContent;
|
||||
|
||||
import android.app.Activity;
|
||||
@@ -31,7 +30,8 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
@@ -41,11 +41,14 @@ import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.b44t.messenger.DcContact;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.thoughtcrime.securesms.connect.DcContactsLoader;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
@@ -57,29 +60,19 @@ import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Fragment for selecting a one or more contacts from a list.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class ContactSelectionListFragment extends Fragment
|
||||
implements LoaderManager.LoaderCallbacks<DcContactsLoader.Ret>,
|
||||
DcEventCenter.DcEventDelegate
|
||||
{
|
||||
private static final String TAG = ContactSelectionListFragment.class.getSimpleName();
|
||||
implements LoaderManager.LoaderCallbacks<DcContactsLoader.Ret>, DcEventCenter.DcEventDelegate {
|
||||
private static final String TAG = "ContactSelectionListFragment";
|
||||
|
||||
public static final String MULTI_SELECT = "multi_select";
|
||||
public static final String SELECT_UNENCRYPTED_EXTRA = "select_unencrypted_extra";
|
||||
public static final String ALLOW_CREATION = "allow_creation";
|
||||
public static final String PRESELECTED_CONTACTS = "preselected_contacts";
|
||||
public static final int CONTACT_ADDR_RESULT_CODE = 61123;
|
||||
|
||||
private DcContext dcContext;
|
||||
|
||||
@@ -91,14 +84,28 @@ public class ContactSelectionListFragment extends Fragment
|
||||
private TextView emptyView;
|
||||
private ActionMode actionMode;
|
||||
private ActionMode.Callback actionModeCallback;
|
||||
private ActivityResultLauncher<Intent> newContactLauncher;
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle icicle) {
|
||||
super.onActivityCreated(icicle);
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
|
||||
dcContext = DcHelper.getContext(getActivity());
|
||||
DcHelper.getEventCenter(getActivity()).addObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
|
||||
initializeCursor();
|
||||
dcContext = DcHelper.getContext(requireContext());
|
||||
newContactLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
|
||||
int contactId =
|
||||
result.getData().getIntExtra(NewContactActivity.CONTACT_ID_EXTRA, 0);
|
||||
if (contactId != 0) {
|
||||
selectedContacts.add(contactId);
|
||||
deselectedContacts.remove(contactId);
|
||||
}
|
||||
LoaderManager.getInstance(this)
|
||||
.restartLoader(0, null, ContactSelectionListFragment.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -114,7 +121,8 @@ public class ContactSelectionListFragment extends Fragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
public View onCreateView(
|
||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false);
|
||||
|
||||
recyclerView = ViewUtil.findById(view, R.id.recycler_view);
|
||||
@@ -124,7 +132,8 @@ public class ContactSelectionListFragment extends Fragment
|
||||
ViewUtil.applyWindowInsets(recyclerView, true, false, true, true);
|
||||
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
actionModeCallback = new ActionMode.Callback() {
|
||||
actionModeCallback =
|
||||
new ActionMode.Callback() {
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
|
||||
MenuInflater inflater = getActivity().getMenuInflater();
|
||||
@@ -162,6 +171,10 @@ public class ContactSelectionListFragment extends Fragment
|
||||
}
|
||||
};
|
||||
|
||||
DcHelper.getEventCenter(requireActivity())
|
||||
.addObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
|
||||
initializeCursor();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -192,17 +205,23 @@ public class ContactSelectionListFragment extends Fragment
|
||||
}
|
||||
|
||||
private void handleDeleteSelected() {
|
||||
AlertDialog dialog = new AlertDialog.Builder(getActivity())
|
||||
AlertDialog dialog =
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setMessage(R.string.ask_delete_contacts)
|
||||
.setPositiveButton(R.string.delete, (d, i) -> {
|
||||
.setPositiveButton(
|
||||
R.string.delete,
|
||||
(d, i) -> {
|
||||
ContactSelectionListAdapter adapter = getContactSelectionListAdapter();
|
||||
final SparseIntArray actionModeSelection = adapter.getActionModeSelection().clone();
|
||||
new Thread(() -> {
|
||||
final SparseIntArray actionModeSelection =
|
||||
adapter.getActionModeSelection().clone();
|
||||
new Thread(
|
||||
() -> {
|
||||
for (int index = 0; index < actionModeSelection.size(); index++) {
|
||||
int contactId = actionModeSelection.valueAt(index);
|
||||
dcContext.deleteContact(contactId);
|
||||
}
|
||||
}).start();
|
||||
})
|
||||
.start();
|
||||
adapter.resetActionModeSelection();
|
||||
actionMode.finish();
|
||||
})
|
||||
@@ -216,7 +235,8 @@ public class ContactSelectionListFragment extends Fragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
public void onRequestPermissionsResult(
|
||||
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@@ -247,15 +267,14 @@ public class ContactSelectionListFragment extends Fragment
|
||||
}
|
||||
|
||||
private void initializeCursor() {
|
||||
ContactSelectionListAdapter adapter = new ContactSelectionListAdapter(getActivity(),
|
||||
GlideApp.with(this),
|
||||
new ListClickListener(),
|
||||
isMulti(),
|
||||
true);
|
||||
ContactSelectionListAdapter adapter =
|
||||
new ContactSelectionListAdapter(
|
||||
getActivity(), GlideApp.with(this), new ListClickListener(), isMulti(), true);
|
||||
selectedContacts = adapter.getSelectedContacts();
|
||||
deselectedContacts = new HashSet<>();
|
||||
ArrayList<Integer> preselectedContacts = getActivity().getIntent().getIntegerArrayListExtra(PRESELECTED_CONTACTS);
|
||||
if(preselectedContacts!=null) {
|
||||
ArrayList<Integer> preselectedContacts =
|
||||
getActivity().getIntent().getIntegerArrayListExtra(PRESELECTED_CONTACTS);
|
||||
if (preselectedContacts != null) {
|
||||
selectedContacts.addAll(preselectedContacts);
|
||||
}
|
||||
recyclerView.setAdapter(adapter);
|
||||
@@ -270,11 +289,20 @@ public class ContactSelectionListFragment extends Fragment
|
||||
public Loader<DcContactsLoader.Ret> onCreateLoader(int id, Bundle args) {
|
||||
final boolean allowCreation = getActivity().getIntent().getBooleanExtra(ALLOW_CREATION, true);
|
||||
final boolean addCreateContactLink = allowCreation && isUnencrypted();
|
||||
final boolean addCreateGroupLinks = allowCreation && !isRelayingMessageContent(getActivity()) && !isMulti();
|
||||
final boolean addCreateGroupLinks =
|
||||
allowCreation && !isRelayingMessageContent(getActivity()) && !isMulti();
|
||||
final boolean addScanQRLink = allowCreation && !isMulti();
|
||||
|
||||
final int listflags = DcContext.DC_GCL_ADD_SELF | (isUnencrypted()? DcContext.DC_GCL_ADDRESS : 0);
|
||||
return new DcContactsLoader(getActivity(), listflags, cursorFilter, addCreateGroupLinks, addCreateContactLink, addScanQRLink, false);
|
||||
final int listflags =
|
||||
DcContext.DC_GCL_ADD_SELF | (isUnencrypted() ? DcContext.DC_GCL_ADDRESS : 0);
|
||||
return new DcContactsLoader(
|
||||
getActivity(),
|
||||
listflags,
|
||||
cursorFilter,
|
||||
addCreateGroupLinks,
|
||||
addCreateContactLink,
|
||||
addScanQRLink,
|
||||
false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -298,8 +326,7 @@ public class ContactSelectionListFragment extends Fragment
|
||||
|
||||
private class ListClickListener implements ContactSelectionListAdapter.ItemClickListener {
|
||||
@Override
|
||||
public void onItemClick(ContactSelectionListItem contact, boolean handleActionMode)
|
||||
{
|
||||
public void onItemClick(ContactSelectionListItem contact, boolean handleActionMode) {
|
||||
if (handleActionMode) {
|
||||
if (actionMode != null) {
|
||||
updateActionModeState(actionMode);
|
||||
@@ -314,7 +341,7 @@ public class ContactSelectionListFragment extends Fragment
|
||||
intent.putExtra(NewContactActivity.ADDR_EXTRA, cursorFilter);
|
||||
}
|
||||
if (isMulti()) {
|
||||
startActivityForResult(intent, CONTACT_ADDR_RESULT_CODE);
|
||||
newContactLauncher.launch(intent);
|
||||
} else {
|
||||
requireContext().startActivity(intent);
|
||||
}
|
||||
@@ -340,7 +367,7 @@ public class ContactSelectionListFragment extends Fragment
|
||||
@Override
|
||||
public void onItemLongClick(ContactSelectionListItem view) {
|
||||
if (actionMode == null) {
|
||||
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback);
|
||||
actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(actionModeCallback);
|
||||
} else {
|
||||
updateActionModeState(actionMode);
|
||||
}
|
||||
@@ -353,25 +380,13 @@ public class ContactSelectionListFragment extends Fragment
|
||||
|
||||
public interface OnContactSelectedListener {
|
||||
void onContactSelected(int contactId);
|
||||
|
||||
void onContactDeselected(int contactId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleEvent(@NonNull DcEvent event) {
|
||||
if (event.getId()==DcContext.DC_EVENT_CONTACTS_CHANGED) {
|
||||
getLoaderManager().restartLoader(0, null, ContactSelectionListFragment.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int reqCode, int resultCode, final Intent data) {
|
||||
super.onActivityResult(reqCode, resultCode, data);
|
||||
if (resultCode == Activity.RESULT_OK && reqCode == CONTACT_ADDR_RESULT_CODE) {
|
||||
int contactId = data.getIntExtra(NewContactActivity.CONTACT_ID_EXTRA, 0);
|
||||
if (contactId != 0) {
|
||||
selectedContacts.add(contactId);
|
||||
deselectedContacts.remove(contactId);
|
||||
}
|
||||
if (event.getId() == DcContext.DC_EVENT_CONTACTS_CHANGED) {
|
||||
getLoaderManager().restartLoader(0, null, ContactSelectionListFragment.this);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,16 +23,20 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioView;
|
||||
@@ -45,31 +49,20 @@ import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A DC adapter for a conversation thread. Ultimately
|
||||
* used by ConversationActivity to display a conversation
|
||||
* thread in a ListActivity.
|
||||
* A DC adapter for a conversation thread. Ultimately used by ConversationActivity to display a
|
||||
* conversation thread in a ListActivity.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
// FIXME: this breaks type checks, that is why there are so many casts.
|
||||
public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
public class ConversationAdapter<V extends View & BindableConversationItem>
|
||||
extends RecyclerView.Adapter
|
||||
implements StickyHeaderDecoration.StickyHeaderAdapter<HeaderViewHolder>
|
||||
{
|
||||
implements StickyHeaderDecoration.StickyHeaderAdapter<HeaderViewHolder> {
|
||||
|
||||
private static final int MAX_CACHE_SIZE = 40;
|
||||
private final Map<Integer,SoftReference<DcMsg>> recordCache =
|
||||
Collections.synchronizedMap(new LRUCache<Integer,SoftReference<DcMsg>>(MAX_CACHE_SIZE));
|
||||
private final Map<Integer, SoftReference<DcMsg>> recordCache =
|
||||
Collections.synchronizedMap(new LRUCache<Integer, SoftReference<DcMsg>>(MAX_CACHE_SIZE));
|
||||
|
||||
private static final int MESSAGE_TYPE_OUTGOING = 0;
|
||||
private static final int MESSAGE_TYPE_INCOMING = 1;
|
||||
@@ -110,7 +103,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <V extends View & BindableConversationItem> V getView() {
|
||||
return (V)itemView;
|
||||
return (V) itemView;
|
||||
}
|
||||
|
||||
public BindableConversationItem getItem() {
|
||||
@@ -118,16 +111,14 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isActive() {
|
||||
return dcMsgList.length > 0;
|
||||
}
|
||||
|
||||
public @NonNull DcChat getChat(){
|
||||
public @NonNull DcChat getChat() {
|
||||
return dcChat;
|
||||
}
|
||||
|
||||
|
||||
public void setLastSeen(long timestamp) {
|
||||
lastSeen = timestamp;
|
||||
}
|
||||
@@ -151,14 +142,14 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (position<0 || position>=dcMsgList.length) {
|
||||
if (position < 0 || position >= dcMsgList.length) {
|
||||
return 0;
|
||||
}
|
||||
return dcMsgList[dcMsgList.length-1-position];
|
||||
return dcMsgList[dcMsgList.length - 1 - position];
|
||||
}
|
||||
|
||||
public @NonNull DcMsg getMsg(int position) {
|
||||
if(position<0 || position>=dcMsgList.length) {
|
||||
if (position < 0 || position >= dcMsgList.length) {
|
||||
return new DcMsg(0);
|
||||
}
|
||||
|
||||
@@ -170,7 +161,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
}
|
||||
}
|
||||
|
||||
final DcMsg fromDb = dcContext.getMsg((int)getItemId(position));
|
||||
final DcMsg fromDb = dcContext.getMsg((int) getItemId(position));
|
||||
recordCache.put(position, new SoftReference<>(fromDb));
|
||||
return fromDb;
|
||||
}
|
||||
@@ -183,12 +174,10 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
this.audioPlayPauseListener = audioPlayPauseListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position of the message with msgId in the chat list, counted from the top
|
||||
*/
|
||||
/** Returns the position of the message with msgId in the chat list, counted from the top */
|
||||
public int msgIdToPosition(int msgId) {
|
||||
for(int i=0; i<dcMsgList.length; i++ ) {
|
||||
if(dcMsgList[i]==msgId) {
|
||||
for (int i = 0; i < dcMsgList.length; i++) {
|
||||
if (dcMsgList[i] == msgId) {
|
||||
return dcMsgList.length - 1 - i;
|
||||
}
|
||||
}
|
||||
@@ -213,13 +202,14 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface ItemClickListener extends BindableConversationItem.EventListener {
|
||||
void onItemClick(DcMsg item);
|
||||
|
||||
void onItemLongClick(DcMsg item, View view);
|
||||
}
|
||||
|
||||
public ConversationAdapter(@NonNull Context context,
|
||||
public ConversationAdapter(
|
||||
@NonNull Context context,
|
||||
@NonNull DcChat dcChat,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@Nullable ItemClickListener clickListener,
|
||||
@@ -238,7 +228,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||
ConversationAdapter.ViewHolder holder = (ConversationAdapter.ViewHolder)viewHolder;
|
||||
ConversationAdapter.ViewHolder holder = (ConversationAdapter.ViewHolder) viewHolder;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
if (position == positionToPulseHighlight) {
|
||||
@@ -248,9 +238,20 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
}
|
||||
|
||||
long elapsed = now - pulseHighlightingSince;
|
||||
boolean pulseHighlight = (positionCurrentlyPulseHighlighting == position && elapsed < PULSE_HIGHLIGHT_MILLIS);
|
||||
boolean pulseHighlight =
|
||||
(positionCurrentlyPulseHighlighting == position && elapsed < PULSE_HIGHLIGHT_MILLIS);
|
||||
|
||||
holder.getItem().bind(getMsg(position), dcChat, glideRequests, batchSelected, recipient, pulseHighlight, playbackViewModel, audioPlayPauseListener);
|
||||
holder
|
||||
.getItem()
|
||||
.bind(
|
||||
getMsg(position),
|
||||
dcChat,
|
||||
glideRequests,
|
||||
batchSelected,
|
||||
recipient,
|
||||
pulseHighlight,
|
||||
playbackViewModel,
|
||||
audioPlayPauseListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -263,12 +264,14 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
final V itemView = ViewUtil.inflate(inflater, parent, getLayoutForViewType(viewType));
|
||||
itemView.setOnClickListener(view -> {
|
||||
itemView.setOnClickListener(
|
||||
view -> {
|
||||
if (clickListener != null) {
|
||||
clickListener.onItemClick(itemView.getMessageRecord());
|
||||
}
|
||||
});
|
||||
itemView.setOnLongClickListener(view -> {
|
||||
itemView.setOnLongClickListener(
|
||||
view -> {
|
||||
if (clickListener != null) {
|
||||
clickListener.onItemLongClick(itemView.getMessageRecord(), view);
|
||||
}
|
||||
@@ -284,14 +287,19 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
case MESSAGE_TYPE_THUMBNAIL_OUTGOING:
|
||||
case MESSAGE_TYPE_DOCUMENT_OUTGOING:
|
||||
case MESSAGE_TYPE_STICKER_OUTGOING:
|
||||
case MESSAGE_TYPE_OUTGOING: return R.layout.conversation_item_sent;
|
||||
case MESSAGE_TYPE_OUTGOING:
|
||||
return R.layout.conversation_item_sent;
|
||||
case MESSAGE_TYPE_AUDIO_INCOMING:
|
||||
case MESSAGE_TYPE_THUMBNAIL_INCOMING:
|
||||
case MESSAGE_TYPE_DOCUMENT_INCOMING:
|
||||
case MESSAGE_TYPE_STICKER_INCOMING:
|
||||
case MESSAGE_TYPE_INCOMING: return R.layout.conversation_item_received;
|
||||
case MESSAGE_TYPE_INFO: return R.layout.conversation_item_update;
|
||||
default: throw new IllegalArgumentException("unsupported item view type given to ConversationAdapter");
|
||||
case MESSAGE_TYPE_INCOMING:
|
||||
return R.layout.conversation_item_received;
|
||||
case MESSAGE_TYPE_INFO:
|
||||
return R.layout.conversation_item_update;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported item view type given to ConversationAdapter");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,21 +309,18 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
int type = dcMsg.getType();
|
||||
if (dcMsg.isInfo()) {
|
||||
return MESSAGE_TYPE_INFO;
|
||||
}
|
||||
else if (type==DcMsg.DC_MSG_AUDIO || type==DcMsg.DC_MSG_VOICE) {
|
||||
return dcMsg.isOutgoing()? MESSAGE_TYPE_AUDIO_OUTGOING : MESSAGE_TYPE_AUDIO_INCOMING;
|
||||
}
|
||||
else if (type==DcMsg.DC_MSG_FILE) {
|
||||
return dcMsg.isOutgoing()? MESSAGE_TYPE_DOCUMENT_OUTGOING : MESSAGE_TYPE_DOCUMENT_INCOMING;
|
||||
}
|
||||
else if (type==DcMsg.DC_MSG_IMAGE || type==DcMsg.DC_MSG_GIF || type==DcMsg.DC_MSG_VIDEO) {
|
||||
return dcMsg.isOutgoing()? MESSAGE_TYPE_THUMBNAIL_OUTGOING : MESSAGE_TYPE_THUMBNAIL_INCOMING;
|
||||
}
|
||||
else if (type == DcMsg.DC_MSG_STICKER) {
|
||||
return dcMsg.isOutgoing()? MESSAGE_TYPE_STICKER_OUTGOING : MESSAGE_TYPE_STICKER_INCOMING;
|
||||
}
|
||||
else {
|
||||
return dcMsg.isOutgoing()? MESSAGE_TYPE_OUTGOING : MESSAGE_TYPE_INCOMING;
|
||||
} else if (type == DcMsg.DC_MSG_AUDIO || type == DcMsg.DC_MSG_VOICE) {
|
||||
return dcMsg.isOutgoing() ? MESSAGE_TYPE_AUDIO_OUTGOING : MESSAGE_TYPE_AUDIO_INCOMING;
|
||||
} else if (type == DcMsg.DC_MSG_FILE) {
|
||||
return dcMsg.isOutgoing() ? MESSAGE_TYPE_DOCUMENT_OUTGOING : MESSAGE_TYPE_DOCUMENT_INCOMING;
|
||||
} else if (type == DcMsg.DC_MSG_IMAGE
|
||||
|| type == DcMsg.DC_MSG_GIF
|
||||
|| type == DcMsg.DC_MSG_VIDEO) {
|
||||
return dcMsg.isOutgoing() ? MESSAGE_TYPE_THUMBNAIL_OUTGOING : MESSAGE_TYPE_THUMBNAIL_INCOMING;
|
||||
} else if (type == DcMsg.DC_MSG_STICKER) {
|
||||
return dcMsg.isOutgoing() ? MESSAGE_TYPE_STICKER_OUTGOING : MESSAGE_TYPE_STICKER_INCOMING;
|
||||
} else {
|
||||
return dcMsg.isOutgoing() ? MESSAGE_TYPE_OUTGOING : MESSAGE_TYPE_INCOMING;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,7 +343,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
}
|
||||
|
||||
public void pulseHighlightItem(int position) {
|
||||
if (position>=0 && position < getItemCount()) {
|
||||
if (position >= 0 && position < getItemCount()) {
|
||||
positionToPulseHighlight = position;
|
||||
notifyItemChanged(position);
|
||||
}
|
||||
@@ -369,18 +374,17 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
|
||||
@Override
|
||||
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) {
|
||||
return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.conversation_item_header, parent, false));
|
||||
return new HeaderViewHolder(
|
||||
LayoutInflater.from(getContext())
|
||||
.inflate(R.layout.conversation_item_header, parent, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* date header view
|
||||
*/
|
||||
/** date header view */
|
||||
@Override
|
||||
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
|
||||
viewHolder.setText(DateUtils.getRelativeDate(getContext(), getSortTimestamp(position)));
|
||||
}
|
||||
|
||||
|
||||
public void changeData(@Nullable int[] dcMsgList) {
|
||||
// should be called when there are new messages
|
||||
this.dcMsgList = dcMsgList == null ? new int[0] : dcMsgList;
|
||||
@@ -405,7 +409,6 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
|
||||
int count = getItemCount();
|
||||
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
DcMsg msg = getMsg(i);
|
||||
if (msg.isOutgoing() || msg.getTimestamp() <= lastSeen) {
|
||||
@@ -417,11 +420,16 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
}
|
||||
|
||||
public HeaderViewHolder onCreateLastSeenViewHolder(ViewGroup parent) {
|
||||
return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.conversation_item_last_seen, parent, false));
|
||||
return new HeaderViewHolder(
|
||||
LayoutInflater.from(getContext())
|
||||
.inflate(R.layout.conversation_item_last_seen, parent, false));
|
||||
}
|
||||
|
||||
public void onBindLastSeenViewHolder(HeaderViewHolder viewHolder, int position) {
|
||||
viewHolder.setText(getContext().getResources().getQuantityString(R.plurals.chat_n_new_messages, (position + 1), (position + 1)));
|
||||
viewHolder.setText(
|
||||
getContext()
|
||||
.getResources()
|
||||
.getQuantityString(R.plurals.chat_n_new_messages, (position + 1), (position + 1)));
|
||||
}
|
||||
|
||||
static class LastSeenHeader extends StickyHeaderDecoration {
|
||||
@@ -433,28 +441,41 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasHeader(RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) {
|
||||
protected boolean hasHeader(
|
||||
RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) {
|
||||
return adapter.isActive() && position == adapter.getLastSeenPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getHeaderTop(RecyclerView parent, View child, View header, int adapterPos, int layoutPos) {
|
||||
protected int getHeaderTop(
|
||||
RecyclerView parent, View child, View header, int adapterPos, int layoutPos) {
|
||||
return parent.getLayoutManager().getDecoratedTop(child);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HeaderViewHolder getHeader(RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) {
|
||||
protected HeaderViewHolder getHeader(
|
||||
RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) {
|
||||
HeaderViewHolder viewHolder = adapter.onCreateLastSeenViewHolder(parent);
|
||||
adapter.onBindLastSeenViewHolder(viewHolder, position);
|
||||
|
||||
int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
|
||||
int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);
|
||||
int heightSpec =
|
||||
View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);
|
||||
|
||||
int childWidth = ViewGroup.getChildMeasureSpec(widthSpec, parent.getPaddingLeft() + parent.getPaddingRight(), viewHolder.itemView.getLayoutParams().width);
|
||||
int childHeight = ViewGroup.getChildMeasureSpec(heightSpec, parent.getPaddingTop() + parent.getPaddingBottom(), viewHolder.itemView.getLayoutParams().height);
|
||||
int childWidth =
|
||||
ViewGroup.getChildMeasureSpec(
|
||||
widthSpec,
|
||||
parent.getPaddingLeft() + parent.getPaddingRight(),
|
||||
viewHolder.itemView.getLayoutParams().width);
|
||||
int childHeight =
|
||||
ViewGroup.getChildMeasureSpec(
|
||||
heightSpec,
|
||||
parent.getPaddingTop() + parent.getPaddingBottom(),
|
||||
viewHolder.itemView.getLayoutParams().height);
|
||||
|
||||
viewHolder.itemView.measure(childWidth, childHeight);
|
||||
viewHolder.itemView.layout(0, 0, viewHolder.itemView.getMeasuredWidth(), viewHolder.itemView.getMeasuredHeight());
|
||||
viewHolder.itemView.layout(
|
||||
0, 0, viewHolder.itemView.getMeasuredWidth(), viewHolder.itemView.getMeasuredHeight());
|
||||
|
||||
return viewHolder;
|
||||
}
|
||||
|
||||
@@ -37,21 +37,29 @@ import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContact;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import org.thoughtcrime.securesms.ConversationAdapter.ItemClickListener;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||
import org.thoughtcrime.securesms.components.reminder.DozeReminder;
|
||||
@@ -70,27 +78,15 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.ConversationAdaptiveActionsToolbar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public class ConversationFragment extends MessageSelectorFragment
|
||||
{
|
||||
private static final String TAG = ConversationFragment.class.getSimpleName();
|
||||
public class ConversationFragment extends MessageSelectorFragment {
|
||||
private static final String TAG = "ConversationFragment";
|
||||
|
||||
private static final int SCROLL_ANIMATION_THRESHOLD = 50;
|
||||
private static final int CODE_ADD_EDIT_CONTACT = 77;
|
||||
|
||||
private final ActionModeCallback actionModeCallback = new ActionModeCallback();
|
||||
private final ItemClickListener selectionClickListener = new ConversationFragmentItemClickListener();
|
||||
private final ItemClickListener selectionClickListener =
|
||||
new ConversationFragmentItemClickListener();
|
||||
|
||||
private ConversationFragmentListener listener;
|
||||
|
||||
@@ -130,12 +126,15 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
markseenDebouncer = new Debouncer(800);
|
||||
reloadTimer = new Timer("reloadTimer", false);
|
||||
reloadTimer.scheduleAtFixedRate(new TimerTask() {
|
||||
reloadTimer.scheduleAtFixedRate(
|
||||
new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Util.runOnMain(ConversationFragment.this::reloadList);
|
||||
}
|
||||
}, 60 * 1000, 60 * 1000);
|
||||
},
|
||||
60 * 1000,
|
||||
60 * 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -150,19 +149,20 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
scrollToBottomButton.setOnClickListener(v -> scrollToBottom());
|
||||
|
||||
final SetStartingPositionLinearLayoutManager layoutManager = new SetStartingPositionLinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, true);
|
||||
final SetStartingPositionLinearLayoutManager layoutManager =
|
||||
new SetStartingPositionLinearLayoutManager(
|
||||
getActivity(), LinearLayoutManager.VERTICAL, true);
|
||||
|
||||
list.setHasFixedSize(false);
|
||||
list.setLayoutManager(layoutManager);
|
||||
list.setItemAnimator(null);
|
||||
|
||||
new ConversationItemSwipeCallback(
|
||||
msg -> actionMode == null,
|
||||
this::handleReplyMessage
|
||||
).attachToRecyclerView(list);
|
||||
new ConversationItemSwipeCallback(msg -> actionMode == null, this::handleReplyMessage)
|
||||
.attachToRecyclerView(list);
|
||||
|
||||
// setLayerType() is needed to allow larger items (long texts in our case)
|
||||
// with hardware layers, drawing may result in errors as "OpenGLRenderer: Path too large to be rendered into a texture"
|
||||
// with hardware layers, drawing may result in errors as "OpenGLRenderer: Path too large to be
|
||||
// rendered into a texture"
|
||||
list.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
|
||||
|
||||
if (pendingAddBottomInsets) {
|
||||
@@ -192,26 +192,21 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
private void setNoMessageText() {
|
||||
DcChat dcChat = getListAdapter().getChat();
|
||||
if(dcChat.isMultiUser()){
|
||||
if (dcChat.isMultiUser()) {
|
||||
if (dcChat.isInBroadcast() || dcChat.isOutBroadcast()) {
|
||||
noMessageTextView.setText(R.string.chat_new_channel_hint);
|
||||
} else if (dcChat.isUnpromoted()) {
|
||||
noMessageTextView.setText(R.string.chat_new_group_hint);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
noMessageTextView.setText(R.string.chat_no_messages);
|
||||
}
|
||||
}
|
||||
else if(dcChat.isSelfTalk()) {
|
||||
} else if (dcChat.isSelfTalk()) {
|
||||
noMessageTextView.setText(R.string.saved_messages_explain);
|
||||
}
|
||||
else if(dcChat.isDeviceTalk()) {
|
||||
} else if (dcChat.isDeviceTalk()) {
|
||||
noMessageTextView.setText(R.string.device_talk_explain);
|
||||
}
|
||||
else if(!dcChat.isEncrypted()) {
|
||||
} else if (!dcChat.isEncrypted()) {
|
||||
noMessageTextView.setText(R.string.chat_unencrypted_explanation);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
String message = getString(R.string.chat_new_one_to_one_hint, dcChat.getName());
|
||||
noMessageTextView.setText(message);
|
||||
}
|
||||
@@ -249,13 +244,14 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
this.listener = (ConversationFragmentListener)activity;
|
||||
this.listener = (ConversationFragmentListener) activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Util.runOnBackground(() -> {
|
||||
Util.runOnBackground(
|
||||
() -> {
|
||||
try {
|
||||
rpc.marknoticedChat(rpc.getSelectedAccountId(), (int) chatId);
|
||||
} catch (RpcException e) {
|
||||
@@ -272,7 +268,6 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
@@ -311,7 +306,10 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
}
|
||||
final int lastSeenPosition = getListAdapter().getLastSeenPosition() + 1;
|
||||
if (lastSeenPosition > 0) {
|
||||
list.post(() -> ((LinearLayoutManager)list.getLayoutManager()).scrollToPositionWithOffset(lastSeenPosition, list.getHeight()));
|
||||
list.post(
|
||||
() ->
|
||||
((LinearLayoutManager) list.getLayoutManager())
|
||||
.scrollToPositionWithOffset(lastSeenPosition, list.getHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,9 +318,13 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
this.chatId = this.getActivity().getIntent().getIntExtra(ConversationActivity.CHAT_ID_EXTRA, -1);
|
||||
this.recipient = Recipient.from(getActivity(), Address.fromChat((int)this.chatId));
|
||||
this.startingPosition = this.getActivity().getIntent().getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1);
|
||||
this.chatId =
|
||||
this.getActivity().getIntent().getIntExtra(ConversationActivity.CHAT_ID_EXTRA, -1);
|
||||
this.recipient = Recipient.from(getActivity(), Address.fromChat((int) this.chatId));
|
||||
this.startingPosition =
|
||||
this.getActivity()
|
||||
.getIntent()
|
||||
.getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1);
|
||||
this.firstLoad = true;
|
||||
|
||||
OnScrollListener scrollListener = new ConversationScrollListener(getActivity());
|
||||
@@ -331,7 +333,13 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
private void initializeListAdapter() {
|
||||
if (this.recipient != null && this.chatId != -1) {
|
||||
ConversationAdapter adapter = new ConversationAdapter(getActivity(), this.recipient.getChat(), GlideApp.with(this), selectionClickListener, this.recipient);
|
||||
ConversationAdapter adapter =
|
||||
new ConversationAdapter(
|
||||
getActivity(),
|
||||
this.recipient.getChat(),
|
||||
GlideApp.with(this),
|
||||
selectionClickListener,
|
||||
this.recipient);
|
||||
list.setAdapter(adapter);
|
||||
AudioPlaybackViewModel playbackViewModel =
|
||||
new ViewModelProvider(requireActivity()).get(AudioPlaybackViewModel.class);
|
||||
@@ -350,7 +358,8 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "RPC error", e);
|
||||
}
|
||||
SetStartingPositionLinearLayoutManager layoutManager = (SetStartingPositionLinearLayoutManager) list.getLayoutManager();
|
||||
SetStartingPositionLinearLayoutManager layoutManager =
|
||||
(SetStartingPositionLinearLayoutManager) list.getLayoutManager();
|
||||
if (startingPosition > -1) {
|
||||
layoutManager.setStartingPosition(startingPosition);
|
||||
} else if (freshMsgs > 0) {
|
||||
@@ -389,7 +398,7 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
menu.findItem(R.id.menu_context_edit).setVisible(false);
|
||||
menu.findItem(R.id.menu_context_reply_privately).setVisible(false);
|
||||
menu.findItem(R.id.menu_add_to_home_screen).setVisible(false);
|
||||
//menu.findItem(R.id.menu_toggle_save).setVisible(false);
|
||||
// menu.findItem(R.id.menu_toggle_save).setVisible(false);
|
||||
} else {
|
||||
DcMsg messageRecord = messageRecords.iterator().next();
|
||||
DcChat chat = getListAdapter().getChat();
|
||||
@@ -397,10 +406,12 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
menu.findItem(R.id.menu_context_share).setVisible(messageRecord.hasFile());
|
||||
boolean canReply = canReplyToMsg(messageRecord);
|
||||
menu.findItem(R.id.menu_context_reply).setVisible(chat.canSend() && canReply);
|
||||
menu.findItem(R.id.menu_context_edit).setVisible(chat.isEncrypted() && chat.canSend() && canEditMsg(messageRecord));
|
||||
menu.findItem(R.id.menu_context_edit)
|
||||
.setVisible(chat.isEncrypted() && chat.canSend() && canEditMsg(messageRecord));
|
||||
boolean showReplyPrivately = chat.isMultiUser() && !messageRecord.isOutgoing() && canReply;
|
||||
menu.findItem(R.id.menu_context_reply_privately).setVisible(showReplyPrivately);
|
||||
menu.findItem(R.id.menu_add_to_home_screen).setVisible(messageRecord.getType() == DcMsg.DC_MSG_WEBXDC);
|
||||
menu.findItem(R.id.menu_add_to_home_screen)
|
||||
.setVisible(messageRecord.getType() == DcMsg.DC_MSG_WEBXDC);
|
||||
|
||||
/*
|
||||
boolean saved = messageRecord.getSavedMsgId() != 0;
|
||||
@@ -449,7 +460,11 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
}
|
||||
|
||||
static boolean canEditMsg(DcMsg dcMsg) {
|
||||
return dcMsg.isOutgoing() && !dcMsg.isInfo() && dcMsg.getType() != DcMsg.DC_MSG_CALL && !dcMsg.hasHtml() && !dcMsg.getText().isEmpty();
|
||||
return dcMsg.isOutgoing()
|
||||
&& !dcMsg.isInfo()
|
||||
&& dcMsg.getType() != DcMsg.DC_MSG_CALL
|
||||
&& !dcMsg.hasHtml()
|
||||
&& !dcMsg.getText().isEmpty();
|
||||
}
|
||||
|
||||
public void handleClearChat() {
|
||||
@@ -477,17 +492,19 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
}
|
||||
|
||||
public void scrollToTop() {
|
||||
ConversationAdapter adapter = (ConversationAdapter)list.getAdapter();
|
||||
if (adapter.getItemCount()>0) {
|
||||
final int pos = adapter.getItemCount()-1;
|
||||
list.post(() -> {
|
||||
ConversationAdapter adapter = (ConversationAdapter) list.getAdapter();
|
||||
if (adapter.getItemCount() > 0) {
|
||||
final int pos = adapter.getItemCount() - 1;
|
||||
list.post(
|
||||
() -> {
|
||||
list.getLayoutManager().scrollToPosition(pos);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void scrollToBottom() {
|
||||
if (((LinearLayoutManager) list.getLayoutManager()).findFirstVisibleItemPosition() < SCROLL_ANIMATION_THRESHOLD
|
||||
if (((LinearLayoutManager) list.getLayoutManager()).findFirstVisibleItemPosition()
|
||||
< SCROLL_ANIMATION_THRESHOLD
|
||||
&& !AccessibilityUtil.areAnimationsDisabled(getContext())) {
|
||||
list.smoothScrollToPosition(0);
|
||||
} else {
|
||||
@@ -511,7 +528,8 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
private void handleCopyMessage(final Set<DcMsg> dcMsgsSet) {
|
||||
List<DcMsg> dcMsgsList = new LinkedList<>(dcMsgsSet);
|
||||
Collections.sort(dcMsgsList, (lhs, rhs) -> Long.compare(lhs.getDateReceived(), rhs.getDateReceived()));
|
||||
Collections.sort(
|
||||
dcMsgsList, (lhs, rhs) -> Long.compare(lhs.getDateReceived(), rhs.getDateReceived()));
|
||||
boolean singleMsg = dcMsgsList.size() == 1;
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
@@ -538,7 +556,11 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
if (result.length() > 0) {
|
||||
Util.writeTextToClipboard(getActivity(), result.toString());
|
||||
Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.copied_to_clipboard), Toast.LENGTH_LONG).show();
|
||||
Toast.makeText(
|
||||
getActivity(),
|
||||
getActivity().getResources().getString(R.string.copied_to_clipboard),
|
||||
Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,6 +576,51 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSaveSticker(final DcMsg message) {
|
||||
if (message.getType() != DcMsg.DC_MSG_STICKER) {
|
||||
return;
|
||||
}
|
||||
|
||||
File stickerFile = message.getFileAsFile();
|
||||
if (stickerFile == null || !stickerFile.exists()) {
|
||||
Toast.makeText(getContext(), R.string.error, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create stickers directory in internal storage
|
||||
File stickersDir = new File(getContext().getFilesDir(), "stickers");
|
||||
if (!stickersDir.exists()) {
|
||||
stickersDir.mkdirs();
|
||||
}
|
||||
|
||||
// Copy sticker to stickers directory
|
||||
String extension = stickerFile.getName();
|
||||
int dotPos = extension.lastIndexOf('.');
|
||||
if (dotPos >= 0) {
|
||||
extension = extension.substring(dotPos + 1);
|
||||
}
|
||||
String fileName = System.currentTimeMillis() + "." + extension;
|
||||
File destFile = new File(stickersDir, fileName);
|
||||
|
||||
try (java.io.FileInputStream in = new java.io.FileInputStream(stickerFile);
|
||||
java.io.FileOutputStream out = new java.io.FileOutputStream(destFile)) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
Toast.makeText(getContext(), R.string.saved, Toast.LENGTH_SHORT).show();
|
||||
|
||||
// Refresh sticker picker to show the newly added sticker
|
||||
if (getActivity() instanceof ConversationActivity) {
|
||||
((ConversationActivity) getActivity()).refreshStickerPicker();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error saving sticker", e);
|
||||
Toast.makeText(getContext(), R.string.error, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
private void handleReplyMessage(final DcMsg message) {
|
||||
if (getActivity() != null) {
|
||||
@@ -596,7 +663,8 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
DcMsg msg = getSelectedMessageRecord(messageRecords);
|
||||
try {
|
||||
if (msg.getSavedMsgId() != 0) {
|
||||
rpc.deleteMessages(rpc.getSelectedAccountId(), Collections.singletonList(msg.getSavedMsgId()));
|
||||
rpc.deleteMessages(
|
||||
rpc.getSelectedAccountId(), Collections.singletonList(msg.getSavedMsgId()));
|
||||
} else {
|
||||
rpc.saveMsgs(rpc.getSelectedAccountId(), Collections.singletonList(msg.getId()));
|
||||
}
|
||||
@@ -615,7 +683,8 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
return;
|
||||
}
|
||||
|
||||
// if chat is a contact request and is accepted/blocked, the DcChat object must be reloaded, otherwise DcChat.canSend() returns wrong values
|
||||
// if chat is a contact request and is accepted/blocked, the DcChat object must be reloaded,
|
||||
// otherwise DcChat.canSend() returns wrong values
|
||||
if (chatModified) {
|
||||
adapter.reloadChat();
|
||||
}
|
||||
@@ -625,9 +694,13 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
int pixelOffset = 0;
|
||||
if (!firstLoad) {
|
||||
oldCount = adapter.getItemCount();
|
||||
oldIndex = ((LinearLayoutManager) list.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
|
||||
oldIndex =
|
||||
((LinearLayoutManager) list.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
|
||||
View firstView = list.getLayoutManager().findViewByPosition(oldIndex);
|
||||
pixelOffset = (firstView == null) ? 0 : list.getBottom() - firstView.getBottom() - list.getPaddingBottom();
|
||||
pixelOffset =
|
||||
(firstView == null)
|
||||
? 0
|
||||
: list.getBottom() - firstView.getBottom() - list.getPaddingBottom();
|
||||
}
|
||||
|
||||
if (getContext() == null) {
|
||||
@@ -648,20 +721,25 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
getListAdapter().pulseHighlightItem(startingPosition);
|
||||
}
|
||||
firstLoad = false;
|
||||
} else if(oldIndex > 0) {
|
||||
} else if (oldIndex > 0) {
|
||||
int newIndex = oldIndex + msgs.length - oldCount;
|
||||
|
||||
if (newIndex < 0) { newIndex = 0; pixelOffset = 0; }
|
||||
else if (newIndex >= msgs.length) { newIndex = msgs.length - 1; pixelOffset = 0; }
|
||||
|
||||
((LinearLayoutManager) list.getLayoutManager()).scrollToPositionWithOffset(newIndex, pixelOffset);
|
||||
if (newIndex < 0) {
|
||||
newIndex = 0;
|
||||
pixelOffset = 0;
|
||||
} else if (newIndex >= msgs.length) {
|
||||
newIndex = msgs.length - 1;
|
||||
pixelOffset = 0;
|
||||
}
|
||||
|
||||
if(!adapter.isActive()){
|
||||
((LinearLayoutManager) list.getLayoutManager())
|
||||
.scrollToPositionWithOffset(newIndex, pixelOffset);
|
||||
}
|
||||
|
||||
if (!adapter.isActive()) {
|
||||
setNoMessageText();
|
||||
noMessageTextView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
noMessageTextView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@@ -671,11 +749,15 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
}
|
||||
|
||||
private void updateLocationButton() {
|
||||
floatingLocationButton.setVisibility(DcHelper.getContext(getContext()).isSendingLocationsToChat((int) chatId)? View.VISIBLE : View.GONE);
|
||||
floatingLocationButton.setVisibility(
|
||||
DcHelper.getContext(getContext()).isSendingLocationsToChat((int) chatId)
|
||||
? View.VISIBLE
|
||||
: View.GONE);
|
||||
}
|
||||
|
||||
private void scrollAndHighlight(final int pos, boolean smooth) {
|
||||
list.post(() -> {
|
||||
list.post(
|
||||
() -> {
|
||||
if (smooth && !AccessibilityUtil.areAnimationsDisabled(getContext())) {
|
||||
list.smoothScrollToPosition(pos);
|
||||
} else {
|
||||
@@ -686,9 +768,9 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
}
|
||||
|
||||
public void scrollToMsgId(final int msgId) {
|
||||
ConversationAdapter adapter = (ConversationAdapter)list.getAdapter();
|
||||
ConversationAdapter adapter = (ConversationAdapter) list.getAdapter();
|
||||
int position = adapter.msgIdToPosition(msgId);
|
||||
if (position!=-1) {
|
||||
if (position != -1) {
|
||||
scrollAndHighlight(position, false);
|
||||
} else {
|
||||
Log.e(TAG, "msgId {} not found for scrolling");
|
||||
@@ -718,6 +800,7 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
public interface ConversationFragmentListener {
|
||||
void handleReplyMessage(DcMsg messageRecord);
|
||||
|
||||
void handleEditMessage(DcMsg messageRecord);
|
||||
}
|
||||
|
||||
@@ -728,7 +811,8 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
private boolean wasAtBottom = true;
|
||||
private boolean wasAtZoomScrollHeight = false;
|
||||
//private long lastPositionId = -1;
|
||||
|
||||
// private long lastPositionId = -1;
|
||||
|
||||
ConversationScrollListener(@NonNull Context context) {
|
||||
this.scrollButtonInAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_scale_in);
|
||||
@@ -742,7 +826,7 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
public void onScrolled(final RecyclerView rv, final int dx, final int dy) {
|
||||
boolean currentlyAtBottom = isAtBottom();
|
||||
boolean currentlyAtZoomScrollHeight = isAtZoomScrollHeight();
|
||||
// int positionId = getHeaderPositionId();
|
||||
// int positionId = getHeaderPositionId();
|
||||
|
||||
if (currentlyAtZoomScrollHeight && !wasAtZoomScrollHeight) {
|
||||
ViewUtil.animateIn(scrollToBottomButton, scrollButtonInAnimation);
|
||||
@@ -750,13 +834,13 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
ViewUtil.animateOut(scrollToBottomButton, scrollButtonOutAnimation, View.INVISIBLE);
|
||||
}
|
||||
|
||||
// if (positionId != lastPositionId) {
|
||||
// bindScrollHeader(conversationDateHeader, positionId);
|
||||
// }
|
||||
// if (positionId != lastPositionId) {
|
||||
// bindScrollHeader(conversationDateHeader, positionId);
|
||||
// }
|
||||
|
||||
wasAtBottom = currentlyAtBottom;
|
||||
wasAtZoomScrollHeight = currentlyAtZoomScrollHeight;
|
||||
// lastPositionId = positionId;
|
||||
// lastPositionId = positionId;
|
||||
|
||||
markseenDebouncer.publish(() -> manageMessageSeenState());
|
||||
|
||||
@@ -765,56 +849,62 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||
// if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
|
||||
// conversationDateHeader.show();
|
||||
// } else if (newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
// conversationDateHeader.hide();
|
||||
// }
|
||||
// if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
|
||||
// conversationDateHeader.show();
|
||||
// } else if (newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
// conversationDateHeader.hide();
|
||||
// }
|
||||
}
|
||||
|
||||
private boolean isAtBottom() {
|
||||
if (list.getChildCount() == 0) return true;
|
||||
|
||||
View bottomView = list.getChildAt(0);
|
||||
int firstVisibleItem = ((LinearLayoutManager) list.getLayoutManager()).findFirstVisibleItemPosition();
|
||||
int firstVisibleItem =
|
||||
((LinearLayoutManager) list.getLayoutManager()).findFirstVisibleItemPosition();
|
||||
boolean isAtBottom = (firstVisibleItem == 0);
|
||||
|
||||
return isAtBottom && bottomView.getBottom() <= list.getHeight();
|
||||
}
|
||||
|
||||
private boolean isAtZoomScrollHeight() {
|
||||
return ((LinearLayoutManager) list.getLayoutManager()).findFirstCompletelyVisibleItemPosition() > 4;
|
||||
return ((LinearLayoutManager) list.getLayoutManager())
|
||||
.findFirstCompletelyVisibleItemPosition()
|
||||
> 4;
|
||||
}
|
||||
|
||||
// private int getHeaderPositionId() {
|
||||
// return ((LinearLayoutManager)list.getLayoutManager()).findLastVisibleItemPosition();
|
||||
// return
|
||||
// ((LinearLayoutManager)list.getLayoutManager()).findLastVisibleItemPosition();
|
||||
// }
|
||||
|
||||
// private void bindScrollHeader(HeaderViewHolder headerViewHolder, int positionId) {
|
||||
// if (((ConversationAdapter)list.getAdapter()).getHeaderId(positionId) != -1) {
|
||||
// ((ConversationAdapter) list.getAdapter()).onBindHeaderViewHolder(headerViewHolder, positionId);
|
||||
// ((ConversationAdapter)
|
||||
// list.getAdapter()).onBindHeaderViewHolder(headerViewHolder, positionId);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private void manageMessageSeenState() {
|
||||
|
||||
LinearLayoutManager layoutManager = (LinearLayoutManager)list.getLayoutManager();
|
||||
LinearLayoutManager layoutManager = (LinearLayoutManager) list.getLayoutManager();
|
||||
|
||||
int firstPos = layoutManager.findFirstVisibleItemPosition();
|
||||
int lastPos = layoutManager.findLastVisibleItemPosition();
|
||||
if(firstPos == RecyclerView.NO_POSITION || lastPos == RecyclerView.NO_POSITION) {
|
||||
if (firstPos == RecyclerView.NO_POSITION || lastPos == RecyclerView.NO_POSITION) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<Integer> ids = new ArrayList<>(lastPos - firstPos + 1);
|
||||
for(int pos = firstPos; pos <= lastPos; pos++) {
|
||||
for (int pos = firstPos; pos <= lastPos; pos++) {
|
||||
DcMsg message = ((ConversationAdapter) list.getAdapter()).getMsg(pos);
|
||||
if (message.getFromId() != DC_CONTACT_ID_SELF) {
|
||||
ids.add(message.getId());
|
||||
}
|
||||
}
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
Util.runOnAnyBackgroundThread(
|
||||
() -> {
|
||||
try {
|
||||
rpc.markseenMsgs(rpc.getSelectedAccountId(), ids);
|
||||
} catch (RpcException e) {
|
||||
@@ -837,36 +927,44 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
hideAddReactionView();
|
||||
Menu menu = actionMode.getMenu();
|
||||
setCorrectMenuVisibility(menu);
|
||||
ConversationAdaptiveActionsToolbar.adjustMenuActions(menu, 10, requireActivity().getWindow().getDecorView().getMeasuredWidth());
|
||||
ConversationAdaptiveActionsToolbar.adjustMenuActions(
|
||||
menu, 10, requireActivity().getWindow().getDecorView().getMeasuredWidth());
|
||||
actionMode.setTitle(String.valueOf(getListAdapter().getSelectedItems().size()));
|
||||
actionMode.setTitleOptionalHint(false); // the title represents important information, also indicating implicitly, more items can be selected
|
||||
actionMode.setTitleOptionalHint(
|
||||
false); // the title represents important information, also indicating implicitly,
|
||||
// more items can be selected
|
||||
}
|
||||
}
|
||||
else if(DozeReminder.isDozeReminderMsg(getContext(), messageRecord)) {
|
||||
} else if (DozeReminder.isDozeReminderMsg(getContext(), messageRecord)) {
|
||||
DozeReminder.dozeReminderTapped(getContext());
|
||||
}
|
||||
else if(StatsSending.isStatsSendingDeviceMsg(getContext(), messageRecord)) {
|
||||
} else if (StatsSending.isStatsSendingDeviceMsg(getContext(), messageRecord)) {
|
||||
StatsSending.statsDeviceMsgTapped(getActivity());
|
||||
}
|
||||
else if(messageRecord.getInfoType() == DcMsg.DC_INFO_WEBXDC_INFO_MESSAGE) {
|
||||
} else if (messageRecord.getInfoType() == DcMsg.DC_INFO_WEBXDC_INFO_MESSAGE) {
|
||||
if (messageRecord.getParent() != null) {
|
||||
// if the parent webxdc message still exists
|
||||
WebxdcActivity.openWebxdcActivity(getContext(), messageRecord.getParent(), messageRecord.getWebxdcHref());
|
||||
WebxdcActivity.openWebxdcActivity(
|
||||
getContext(), messageRecord.getParent(), messageRecord.getWebxdcHref());
|
||||
}
|
||||
}
|
||||
else if (!TextUtils.isEmpty(messageRecord.getPOILocation()) && messageRecord.getType() == DcMsg.DC_MSG_TEXT && !messageRecord.hasHtml()) {
|
||||
WebxdcActivity.openMaps(getContext(), getListAdapter().getChat().getId(), "index.html#"+messageRecord.getPOILocation());
|
||||
}
|
||||
else {
|
||||
} 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);
|
||||
startActivity(intent);
|
||||
} else if (!TextUtils.isEmpty(messageRecord.getPOILocation())
|
||||
&& messageRecord.getType() == DcMsg.DC_MSG_TEXT
|
||||
&& !messageRecord.hasHtml()) {
|
||||
WebxdcActivity.openMaps(
|
||||
getContext(),
|
||||
getListAdapter().getChat().getId(),
|
||||
"index.html#" + messageRecord.getPOILocation());
|
||||
} else {
|
||||
int infoContactId = messageRecord.getInfoContactId();
|
||||
if (infoContactId != 0 && infoContactId != DC_CONTACT_ID_SELF) {
|
||||
Intent intent = new Intent(getContext(), ProfileActivity.class);
|
||||
intent.putExtra(ProfileActivity.CONTACT_ID_EXTRA, infoContactId);
|
||||
startActivity(intent);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
String self_mail = DcHelper.getContext(getContext()).getConfig("configured_mail_user");
|
||||
if (self_mail != null && !self_mail.isEmpty()
|
||||
if (self_mail != null
|
||||
&& !self_mail.isEmpty()
|
||||
&& messageRecord.getText().contains(self_mail)
|
||||
&& getListAdapter().getChat().isDeviceTalk()) {
|
||||
// This is a device message informing the user that the password is wrong
|
||||
@@ -884,8 +982,11 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord);
|
||||
list.getAdapter().notifyDataSetChanged();
|
||||
|
||||
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback);
|
||||
addReactionView.show(messageRecord, view, () -> {
|
||||
actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(actionModeCallback);
|
||||
addReactionView.show(
|
||||
messageRecord,
|
||||
view,
|
||||
() -> {
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
}
|
||||
@@ -895,8 +996,14 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
private void jumpToOriginal(DcMsg original) {
|
||||
if (original == null) {
|
||||
Log.i(TAG, "Clicked on a quote or jump-to-original whose original message was deleted/non-existing.");
|
||||
Toast.makeText(getContext(), R.string.ConversationFragment_quoted_message_not_found, Toast.LENGTH_SHORT).show();
|
||||
Log.i(
|
||||
TAG,
|
||||
"Clicked on a quote or jump-to-original whose original message was deleted/non-existing.");
|
||||
Toast.makeText(
|
||||
getContext(),
|
||||
R.string.ConversationFragment_quoted_message_not_found,
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -932,7 +1039,8 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
public void onShowFullClicked(DcMsg messageRecord) {
|
||||
Intent intent = new Intent(getActivity(), FullMsgActivity.class);
|
||||
intent.putExtra(FullMsgActivity.MSG_ID_EXTRA, messageRecord.getId());
|
||||
intent.putExtra(FullMsgActivity.BLOCK_LOADING_REMOTE, getListAdapter().getChat().isContactRequest());
|
||||
intent.putExtra(
|
||||
FullMsgActivity.BLOCK_LOADING_REMOTE, getListAdapter().getChat().isContactRequest());
|
||||
startActivity(intent);
|
||||
getActivity().overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out);
|
||||
}
|
||||
@@ -947,16 +1055,18 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
ReactionsDetailsFragment dialog = ReactionsDetailsFragment.newInstance(messageRecord.getId());
|
||||
dialog.show(getActivity().getSupportFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == CODE_ADD_EDIT_CONTACT && getContext() != null) {
|
||||
// ApplicationContext.getInstance(getContext().getApplicationContext())
|
||||
// .getJobManager()
|
||||
// .add(new DirectoryRefreshJob(getContext().getApplicationContext(), false));
|
||||
public void onStickerClicked(DcMsg messageRecord) {
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setMessage(R.string.ask_add_sticker_to_collection)
|
||||
.setPositiveButton(
|
||||
R.string.ok,
|
||||
(dialog, which) -> {
|
||||
handleSaveSticker(messageRecord);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -971,7 +1081,8 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
Util.redMenuItem(menu, R.id.menu_context_delete_message);
|
||||
setCorrectMenuVisibility(menu);
|
||||
ConversationAdaptiveActionsToolbar.adjustMenuActions(menu, 10, requireActivity().getWindow().getDecorView().getMeasuredWidth());
|
||||
ConversationAdaptiveActionsToolbar.adjustMenuActions(
|
||||
menu, 10, requireActivity().getWindow().getDecorView().getMeasuredWidth());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -982,7 +1093,7 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
((ConversationAdapter)list.getAdapter()).clearSelection();
|
||||
((ConversationAdapter) list.getAdapter()).clearSelection();
|
||||
list.getAdapter().notifyDataSetChanged();
|
||||
|
||||
actionMode = null;
|
||||
@@ -1001,10 +1112,17 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
actionMode.finish();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_context_delete_message) {
|
||||
handleDeleteMessages((int) chatId, getListAdapter().getSelectedItems(), playbackViewModel::stopByIds, playbackViewModel::stopByIds);
|
||||
handleDeleteMessages(
|
||||
(int) chatId,
|
||||
getListAdapter().getSelectedItems(),
|
||||
playbackViewModel::stopByIds,
|
||||
playbackViewModel::stopByIds);
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_context_share) {
|
||||
DcHelper.openForViewOrShare(getContext(), getSelectedMessageRecord(getListAdapter().getSelectedItems()).getId(), Intent.ACTION_SEND);
|
||||
DcHelper.openForViewOrShare(
|
||||
getContext(),
|
||||
getSelectedMessageRecord(getListAdapter().getSelectedItems()).getId(),
|
||||
Intent.ACTION_SEND);
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_context_details) {
|
||||
handleDisplayDetails(getSelectedMessageRecord(getListAdapter().getSelectedItems()));
|
||||
@@ -1015,7 +1133,8 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
actionMode.finish();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_add_to_home_screen) {
|
||||
WebxdcActivity.addToHomeScreen(getActivity(), getSelectedMessageRecord(getListAdapter().getSelectedItems()).getId());
|
||||
WebxdcActivity.addToHomeScreen(
|
||||
getActivity(), getSelectedMessageRecord(getListAdapter().getSelectedItems()).getId());
|
||||
actionMode.finish();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_context_save_attachment) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.text.Spannable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
@@ -31,16 +32,21 @@ import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.DimenRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import chat.delta.rpc.RpcException;
|
||||
import chat.delta.rpc.types.CallInfo;
|
||||
import chat.delta.rpc.types.CallState;
|
||||
import chat.delta.rpc.types.Reactions;
|
||||
import chat.delta.rpc.types.VcardContact;
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContact;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.thoughtcrime.securesms.calls.CallCoordinator;
|
||||
import org.thoughtcrime.securesms.calls.CallUtil;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.BorderlessImageView;
|
||||
@@ -73,26 +79,14 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import chat.delta.rpc.RpcException;
|
||||
import chat.delta.rpc.types.CallInfo;
|
||||
import chat.delta.rpc.types.CallState;
|
||||
import chat.delta.rpc.types.Reactions;
|
||||
import chat.delta.rpc.types.VcardContact;
|
||||
|
||||
/**
|
||||
* A view that displays an individual conversation item within a conversation
|
||||
* thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter.
|
||||
* A view that displays an individual conversation item within a conversation thread. Used by
|
||||
* ComposeMessageActivity's ListActivity via a ConversationAdapter.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class ConversationItem extends BaseConversationItem
|
||||
{
|
||||
private static final String TAG = ConversationItem.class.getSimpleName();
|
||||
public class ConversationItem extends BaseConversationItem {
|
||||
private static final String TAG = "ConversationItem";
|
||||
|
||||
private static final Rect SWIPE_RECT = new Rect();
|
||||
|
||||
@@ -176,18 +170,20 @@ public class ConversationItem extends BaseConversationItem
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull DcMsg messageRecord,
|
||||
public void bind(
|
||||
@NonNull DcMsg messageRecord,
|
||||
@NonNull DcChat dcChat,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Set<DcMsg> batchSelected,
|
||||
@NonNull Recipient recipients,
|
||||
boolean pulseHighlight,
|
||||
@Nullable AudioPlaybackViewModel playbackViewModel,
|
||||
AudioView.OnActionListener audioPlayPauseListener)
|
||||
{
|
||||
AudioView.OnActionListener audioPlayPauseListener) {
|
||||
bindPartial(messageRecord, dcChat, batchSelected, pulseHighlight, recipients);
|
||||
this.glideRequests = glideRequests;
|
||||
this.showSender = ((dcChat.isMultiUser() || dcChat.isSelfTalk()) && !messageRecord.isOutgoing()) || messageRecord.getOverrideSenderName() != null;
|
||||
this.showSender =
|
||||
((dcChat.isMultiUser() || dcChat.isSelfTalk()) && !messageRecord.isOutgoing())
|
||||
|| messageRecord.getOverrideSenderName() != null;
|
||||
|
||||
if (showSender) {
|
||||
this.dcContact = dcContext.getContact(messageRecord.getFromId());
|
||||
@@ -195,7 +191,8 @@ public class ConversationItem extends BaseConversationItem
|
||||
|
||||
if (dcChat.isSelfTalk() && messageRecord.getOriginalMsgId() != 0) {
|
||||
jumptoView.setVisibility(View.VISIBLE);
|
||||
jumptoView.setOnClickListener(view -> {
|
||||
jumptoView.setOnClickListener(
|
||||
view -> {
|
||||
if (eventListener != null) {
|
||||
eventListener.onJumpToOriginalClicked(messageRecord);
|
||||
}
|
||||
@@ -221,7 +218,6 @@ public class ConversationItem extends BaseConversationItem
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setEventListener(@Nullable EventListener eventListener) {
|
||||
this.eventListener = eventListener;
|
||||
@@ -230,7 +226,8 @@ public class ConversationItem extends BaseConversationItem
|
||||
public boolean disallowSwipe(float downX, float downY) {
|
||||
// If it is possible to reply to a message, it should also be possible to swipe it.
|
||||
// For this to be possible we need a non-null reply icon.
|
||||
// This means that `replyView != null` must always be the same as ConversationFragment.canReplyToMsg(messageRecord).
|
||||
// This means that `replyView != null` must always be the same as
|
||||
// ConversationFragment.canReplyToMsg(messageRecord).
|
||||
if (replyView == null) return true;
|
||||
if (!dcChat.canSend()) return true;
|
||||
|
||||
@@ -275,7 +272,8 @@ public class ConversationItem extends BaseConversationItem
|
||||
}
|
||||
|
||||
private void initializeAttributes() {
|
||||
final int[] attributes = new int[] {
|
||||
final int[] attributes =
|
||||
new int[] {
|
||||
R.attr.conversation_item_incoming_bubble_color,
|
||||
R.attr.conversation_item_outgoing_bubble_color,
|
||||
};
|
||||
@@ -286,8 +284,7 @@ public class ConversationItem extends BaseConversationItem
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind() {
|
||||
}
|
||||
public void unbind() {}
|
||||
|
||||
public DcMsg getMessageRecord() {
|
||||
return messageRecord;
|
||||
@@ -308,8 +305,12 @@ public class ConversationItem extends BaseConversationItem
|
||||
super.setInteractionState(messageRecord, pulseHighlight);
|
||||
|
||||
if (mediaThumbnailStub.resolved()) {
|
||||
mediaThumbnailStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||
mediaThumbnailStub.get().setClickable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||
mediaThumbnailStub
|
||||
.get()
|
||||
.setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||
mediaThumbnailStub
|
||||
.get()
|
||||
.setClickable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||
mediaThumbnailStub.get().setLongClickable(batchSelected.isEmpty());
|
||||
}
|
||||
|
||||
@@ -318,22 +319,30 @@ public class ConversationItem extends BaseConversationItem
|
||||
}
|
||||
|
||||
if (documentViewStub.resolved()) {
|
||||
documentViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||
documentViewStub
|
||||
.get()
|
||||
.setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||
documentViewStub.get().setClickable(batchSelected.isEmpty());
|
||||
}
|
||||
|
||||
if (webxdcViewStub.resolved()) {
|
||||
webxdcViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||
webxdcViewStub
|
||||
.get()
|
||||
.setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||
webxdcViewStub.get().setClickable(batchSelected.isEmpty());
|
||||
}
|
||||
|
||||
if (vcardViewStub.resolved()) {
|
||||
vcardViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||
vcardViewStub
|
||||
.get()
|
||||
.setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||
vcardViewStub.get().setClickable(batchSelected.isEmpty());
|
||||
}
|
||||
|
||||
if (callViewStub.resolved()) {
|
||||
callViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||
callViewStub
|
||||
.get()
|
||||
.setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||
callViewStub.get().setClickable(batchSelected.isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -346,7 +355,8 @@ public class ConversationItem extends BaseConversationItem
|
||||
|
||||
if (audioViewStub.resolved() && audioViewStub.get().getVisibility() == View.VISIBLE) {
|
||||
desc += audioViewStub.get().getDescription() + "\n";
|
||||
} else if (documentViewStub.resolved() && documentViewStub.get().getVisibility() == View.VISIBLE) {
|
||||
} else if (documentViewStub.resolved()
|
||||
&& documentViewStub.get().getVisibility() == View.VISIBLE) {
|
||||
desc += documentViewStub.get().getDescription() + "\n";
|
||||
} else if (webxdcViewStub.resolved() && webxdcViewStub.get().getVisibility() == View.VISIBLE) {
|
||||
desc += webxdcViewStub.get().getDescription() + "\n";
|
||||
@@ -354,7 +364,8 @@ public class ConversationItem extends BaseConversationItem
|
||||
desc += vcardViewStub.get().getDescription() + "\n";
|
||||
} else if (callViewStub.resolved() && callViewStub.get().getVisibility() == View.VISIBLE) {
|
||||
desc += callViewStub.get().getDescription() + "\n";
|
||||
} else if (mediaThumbnailStub.resolved() && mediaThumbnailStub.get().getVisibility() == View.VISIBLE) {
|
||||
} else if (mediaThumbnailStub.resolved()
|
||||
&& mediaThumbnailStub.get().getVisibility() == View.VISIBLE) {
|
||||
desc += mediaThumbnailStub.get().getDescription() + "\n";
|
||||
} else if (stickerStub.resolved() && stickerStub.get().getVisibility() == View.VISIBLE) {
|
||||
desc += stickerStub.get().getDescription() + "\n";
|
||||
@@ -373,7 +384,7 @@ public class ConversationItem extends BaseConversationItem
|
||||
|
||||
private boolean hasAudio(DcMsg messageRecord) {
|
||||
int type = messageRecord.getType();
|
||||
return type==DcMsg.DC_MSG_AUDIO || type==DcMsg.DC_MSG_VOICE;
|
||||
return type == DcMsg.DC_MSG_AUDIO || type == DcMsg.DC_MSG_VOICE;
|
||||
}
|
||||
|
||||
private boolean hasQuote(DcMsg messageRecord) {
|
||||
@@ -382,31 +393,31 @@ public class ConversationItem extends BaseConversationItem
|
||||
|
||||
private boolean hasThumbnail(DcMsg messageRecord) {
|
||||
int type = messageRecord.getType();
|
||||
return type==DcMsg.DC_MSG_GIF || type==DcMsg.DC_MSG_IMAGE || type==DcMsg.DC_MSG_VIDEO;
|
||||
return type == DcMsg.DC_MSG_GIF || type == DcMsg.DC_MSG_IMAGE || type == DcMsg.DC_MSG_VIDEO;
|
||||
}
|
||||
|
||||
private boolean hasSticker(DcMsg dcMsg) {
|
||||
return dcMsg.getType()==DcMsg.DC_MSG_STICKER;
|
||||
return dcMsg.getType() == DcMsg.DC_MSG_STICKER;
|
||||
}
|
||||
|
||||
private boolean hasOnlyThumbnail(DcMsg messageRecord) {
|
||||
return hasThumbnail(messageRecord) &&
|
||||
!hasAudio(messageRecord) &&
|
||||
!hasDocument(messageRecord) &&
|
||||
!hasWebxdc(messageRecord) &&
|
||||
!hasSticker(messageRecord);
|
||||
return hasThumbnail(messageRecord)
|
||||
&& !hasAudio(messageRecord)
|
||||
&& !hasDocument(messageRecord)
|
||||
&& !hasWebxdc(messageRecord)
|
||||
&& !hasSticker(messageRecord);
|
||||
}
|
||||
|
||||
private boolean hasWebxdc(DcMsg dcMsg) {
|
||||
return dcMsg.getType()==DcMsg.DC_MSG_WEBXDC;
|
||||
return dcMsg.getType() == DcMsg.DC_MSG_WEBXDC;
|
||||
}
|
||||
|
||||
private boolean hasVcard(DcMsg dcMsg) {
|
||||
return dcMsg.getType()==DcMsg.DC_MSG_VCARD;
|
||||
return dcMsg.getType() == DcMsg.DC_MSG_VCARD;
|
||||
}
|
||||
|
||||
private boolean hasDocument(DcMsg dcMsg) {
|
||||
return dcMsg.getType()==DcMsg.DC_MSG_FILE;
|
||||
return dcMsg.getType() == DcMsg.DC_MSG_FILE;
|
||||
}
|
||||
|
||||
private void setBodyText(DcMsg messageRecord) {
|
||||
@@ -416,12 +427,12 @@ public class ConversationItem extends BaseConversationItem
|
||||
String subject = messageRecord.getSubject();
|
||||
String text = messageRecord.getText();
|
||||
|
||||
if (!subject.isEmpty() && messageRecord.isOutgoing() && !messageRecord.isSecure()) text = subject + "\n\n" + text;
|
||||
if (!subject.isEmpty() && messageRecord.isOutgoing() && !messageRecord.isSecure())
|
||||
text = subject + "\n\n" + text;
|
||||
|
||||
if (messageRecord.getType() == DcMsg.DC_MSG_CALL || text.isEmpty()) {
|
||||
bodyText.setVisibility(View.GONE);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Spannable spannable = (Spannable) MarkdownUtil.toMarkdown(context, text);
|
||||
if (batchSelected.isEmpty()) {
|
||||
spannable = Linkifier.linkify(spannable);
|
||||
@@ -431,13 +442,15 @@ public class ConversationItem extends BaseConversationItem
|
||||
}
|
||||
|
||||
int downloadState = messageRecord.getDownloadState();
|
||||
if (downloadState == DcMsg.DC_DOWNLOAD_AVAILABLE || downloadState == DcMsg.DC_DOWNLOAD_FAILURE || downloadState == DcMsg.DC_DOWNLOAD_IN_PROGRESS) {
|
||||
if (downloadState == DcMsg.DC_DOWNLOAD_AVAILABLE
|
||||
|| downloadState == DcMsg.DC_DOWNLOAD_FAILURE
|
||||
|| downloadState == DcMsg.DC_DOWNLOAD_IN_PROGRESS) {
|
||||
showFullButton.setVisibility(View.GONE);
|
||||
msgActionButton.setVisibility(View.VISIBLE);
|
||||
if (downloadState==DcMsg.DC_DOWNLOAD_IN_PROGRESS) {
|
||||
if (downloadState == DcMsg.DC_DOWNLOAD_IN_PROGRESS) {
|
||||
msgActionButton.setEnabled(false);
|
||||
msgActionButton.setText(R.string.downloading);
|
||||
} else if (downloadState==DcMsg.DC_DOWNLOAD_FAILURE) {
|
||||
} else if (downloadState == DcMsg.DC_DOWNLOAD_FAILURE) {
|
||||
msgActionButton.setEnabled(true);
|
||||
msgActionButton.setText(R.string.download_failed);
|
||||
} else {
|
||||
@@ -445,7 +458,8 @@ public class ConversationItem extends BaseConversationItem
|
||||
msgActionButton.setText(R.string.download);
|
||||
}
|
||||
|
||||
msgActionButton.setOnClickListener(view -> {
|
||||
msgActionButton.setOnClickListener(
|
||||
view -> {
|
||||
if (eventListener != null && batchSelected.isEmpty()) {
|
||||
eventListener.onDownloadClicked(messageRecord);
|
||||
} else {
|
||||
@@ -457,20 +471,21 @@ public class ConversationItem extends BaseConversationItem
|
||||
msgActionButton.setVisibility(View.VISIBLE);
|
||||
msgActionButton.setEnabled(true);
|
||||
msgActionButton.setText(R.string.start_app);
|
||||
msgActionButton.setOnClickListener(view -> {
|
||||
msgActionButton.setOnClickListener(
|
||||
view -> {
|
||||
if (batchSelected.isEmpty()) {
|
||||
WebxdcActivity.openWebxdcActivity(getContext(), messageRecord);
|
||||
} else {
|
||||
passthroughClickListener.onClick(view);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (messageRecord.hasHtml()) {
|
||||
} else if (messageRecord.hasHtml()) {
|
||||
msgActionButton.setVisibility(View.GONE);
|
||||
showFullButton.setVisibility(View.VISIBLE);
|
||||
showFullButton.setEnabled(true);
|
||||
showFullButton.setText(R.string.show_full_message);
|
||||
showFullButton.setOnClickListener(view -> {
|
||||
showFullButton.setOnClickListener(
|
||||
view -> {
|
||||
if (eventListener != null && batchSelected.isEmpty()) {
|
||||
eventListener.onShowFullClicked(messageRecord);
|
||||
} else {
|
||||
@@ -483,11 +498,11 @@ public class ConversationItem extends BaseConversationItem
|
||||
}
|
||||
}
|
||||
|
||||
private void setMediaAttributes(@NonNull DcMsg messageRecord,
|
||||
private void setMediaAttributes(
|
||||
@NonNull DcMsg messageRecord,
|
||||
boolean showSender,
|
||||
AudioPlaybackViewModel playbackViewModel,
|
||||
AudioView.OnActionListener audioPlayPauseListener)
|
||||
{
|
||||
AudioView.OnActionListener audioPlayPauseListener) {
|
||||
if (hasAudio(messageRecord)) {
|
||||
audioViewStub.get().setVisibility(View.VISIBLE);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
@@ -502,13 +517,18 @@ public class ConversationItem extends BaseConversationItem
|
||||
audioViewStub.get().setAudio(new AudioSlide(context, messageRecord));
|
||||
audioViewStub.get().setOnClickListener(passthroughClickListener);
|
||||
audioViewStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
audioViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
audioViewStub
|
||||
.get()
|
||||
.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(
|
||||
bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(
|
||||
groupSenderHolder,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
footer.setVisibility(VISIBLE);
|
||||
}
|
||||
else if (hasDocument(messageRecord)) {
|
||||
} else if (hasDocument(messageRecord)) {
|
||||
documentViewStub.get().setVisibility(View.VISIBLE);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
@@ -521,13 +541,18 @@ public class ConversationItem extends BaseConversationItem
|
||||
documentViewStub.get().setDocument(new DocumentSlide(context, messageRecord));
|
||||
documentViewStub.get().setDocumentClickListener(new ThumbnailClickListener());
|
||||
documentViewStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
documentViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
documentViewStub
|
||||
.get()
|
||||
.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(
|
||||
bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(
|
||||
groupSenderHolder,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
footer.setVisibility(VISIBLE);
|
||||
}
|
||||
else if (hasWebxdc(messageRecord)) {
|
||||
} else if (hasWebxdc(messageRecord)) {
|
||||
webxdcViewStub.get().setVisibility(View.VISIBLE);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
@@ -539,13 +564,18 @@ public class ConversationItem extends BaseConversationItem
|
||||
webxdcViewStub.get().setWebxdc(messageRecord, context.getString(R.string.webxdc_app));
|
||||
webxdcViewStub.get().setWebxdcClickListener(new ThumbnailClickListener());
|
||||
webxdcViewStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
webxdcViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
webxdcViewStub
|
||||
.get()
|
||||
.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(
|
||||
bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(
|
||||
groupSenderHolder,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
footer.setVisibility(VISIBLE);
|
||||
}
|
||||
else if (hasVcard(messageRecord)) {
|
||||
} else if (hasVcard(messageRecord)) {
|
||||
vcardViewStub.get().setVisibility(View.VISIBLE);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
@@ -558,13 +588,18 @@ public class ConversationItem extends BaseConversationItem
|
||||
vcardViewStub.get().setVcardClickListener(new ThumbnailClickListener());
|
||||
vcardViewStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
|
||||
vcardViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
vcardViewStub
|
||||
.get()
|
||||
.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(
|
||||
bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(
|
||||
groupSenderHolder,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
footer.setVisibility(VISIBLE);
|
||||
}
|
||||
else if (messageRecord.getType() == DcMsg.DC_MSG_CALL) {
|
||||
} else if (messageRecord.getType() == DcMsg.DC_MSG_CALL) {
|
||||
callViewStub.get().setVisibility(View.VISIBLE);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
@@ -574,18 +609,26 @@ public class ConversationItem extends BaseConversationItem
|
||||
if (vcardViewStub.resolved()) vcardViewStub.get().setVisibility(View.GONE);
|
||||
|
||||
try {
|
||||
callViewStub.get().setCallItem(messageRecord.isOutgoing(), rpc.callInfo(dcContext.getAccountId(), messageRecord.getId()));
|
||||
callViewStub
|
||||
.get()
|
||||
.setCallItem(
|
||||
messageRecord.isOutgoing(),
|
||||
rpc.callInfo(dcContext.getAccountId(), messageRecord.getId()));
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "Error in Rpc.callInfo", e);
|
||||
}
|
||||
callViewStub.get().setCallClickListener(new CallClickListener());
|
||||
callViewStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
|
||||
callViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
callViewStub
|
||||
.get()
|
||||
.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
|
||||
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
else if (hasThumbnail(messageRecord)) {
|
||||
ViewUtil.updateLayoutParams(
|
||||
groupSenderHolder,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
} else if (hasThumbnail(messageRecord)) {
|
||||
mediaThumbnailStub.get().setVisibility(View.VISIBLE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||
@@ -596,36 +639,43 @@ public class ConversationItem extends BaseConversationItem
|
||||
|
||||
Slide slide = MediaUtil.getSlideForMsg(context, messageRecord);
|
||||
|
||||
MediaUtil.ThumbnailSize thumbnailSize = new MediaUtil.ThumbnailSize(messageRecord.getWidth(0), messageRecord.getHeight(0));
|
||||
if ((thumbnailSize.width<=0||thumbnailSize.height<=0)) {
|
||||
if(messageRecord.getType()==DcMsg.DC_MSG_VIDEO) {
|
||||
MediaUtil.createVideoThumbnailIfNeeded(context, slide.getUri(), slide.getThumbnailUri(), thumbnailSize);
|
||||
MediaUtil.ThumbnailSize thumbnailSize =
|
||||
new MediaUtil.ThumbnailSize(messageRecord.getWidth(0), messageRecord.getHeight(0));
|
||||
if ((thumbnailSize.width <= 0 || thumbnailSize.height <= 0)) {
|
||||
if (messageRecord.getType() == DcMsg.DC_MSG_VIDEO) {
|
||||
MediaUtil.createVideoThumbnailIfNeeded(
|
||||
context, slide.getUri(), slide.getThumbnailUri(), thumbnailSize);
|
||||
}
|
||||
if (thumbnailSize.width<=0||thumbnailSize.height<=0) {
|
||||
if (thumbnailSize.width <= 0 || thumbnailSize.height <= 0) {
|
||||
thumbnailSize.width = 180;
|
||||
thumbnailSize.height = 180;
|
||||
}
|
||||
messageRecord.lateFilingMediaSize(thumbnailSize.width, thumbnailSize.height, 0);
|
||||
}
|
||||
|
||||
mediaThumbnailStub.get().setImageResource(glideRequests,
|
||||
slide,
|
||||
thumbnailSize.width,
|
||||
thumbnailSize.height);
|
||||
mediaThumbnailStub
|
||||
.get()
|
||||
.setImageResource(glideRequests, slide, thumbnailSize.width, thumbnailSize.height);
|
||||
mediaThumbnailStub.get().setThumbnailClickListener(new ThumbnailClickListener());
|
||||
mediaThumbnailStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
mediaThumbnailStub.get().setOnClickListener(passthroughClickListener);
|
||||
mediaThumbnailStub.get().showShade(TextUtils.isEmpty(messageRecord.getText()));
|
||||
mediaThumbnailStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
mediaThumbnailStub
|
||||
.get()
|
||||
.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
|
||||
setThumbnailOutlineCorners(messageRecord, showSender);
|
||||
|
||||
bodyBubble.getLayoutParams().width = ViewUtil.dpToPx(readDimen(R.dimen.media_bubble_max_width));
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
bodyBubble.getLayoutParams().width =
|
||||
ViewUtil.dpToPx(readDimen(R.dimen.media_bubble_max_width));
|
||||
ViewUtil.updateLayoutParams(
|
||||
bodyText, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(
|
||||
groupSenderHolder,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
footer.setVisibility(VISIBLE);
|
||||
}
|
||||
else if (hasSticker(messageRecord)) {
|
||||
} else if (hasSticker(messageRecord)) {
|
||||
stickerStub.get().setVisibility(View.VISIBLE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||
@@ -640,14 +690,19 @@ public class ConversationItem extends BaseConversationItem
|
||||
stickerStub.get().setThumbnailClickListener(new StickerClickListener());
|
||||
stickerStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
stickerStub.get().setOnClickListener(passthroughClickListener);
|
||||
stickerStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
stickerStub
|
||||
.get()
|
||||
.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(
|
||||
bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(
|
||||
groupSenderHolder,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
footer.setVisibility(VISIBLE);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||
@@ -655,15 +710,17 @@ public class ConversationItem extends BaseConversationItem
|
||||
if (vcardViewStub.resolved()) vcardViewStub.get().setVisibility(View.GONE);
|
||||
if (callViewStub.resolved()) callViewStub.get().setVisibility(View.GONE);
|
||||
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(
|
||||
bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(
|
||||
groupSenderHolder,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
footer.setVisibility(VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setThumbnailOutlineCorners(@NonNull DcMsg current,
|
||||
boolean showSender)
|
||||
{
|
||||
private void setThumbnailOutlineCorners(@NonNull DcMsg current, boolean showSender) {
|
||||
int defaultRadius = readDimen(R.dimen.message_corner_radius);
|
||||
|
||||
int topLeft = defaultRadius;
|
||||
@@ -676,18 +733,16 @@ public class ConversationItem extends BaseConversationItem
|
||||
bottomRight = 0;
|
||||
}
|
||||
|
||||
if (showSender
|
||||
|| current.isForwarded()
|
||||
|| hasQuote(current)) {
|
||||
if (showSender || current.isForwarded() || hasQuote(current)) {
|
||||
topLeft = 0;
|
||||
topRight = 0;
|
||||
}
|
||||
|
||||
if(bottomLeft != 0 && bottomRight !=0) {
|
||||
if((current.isOutgoing() && ViewUtil.isLtr(this)) || (!current.isOutgoing() && ViewUtil.isRtl(this))) {
|
||||
if (bottomLeft != 0 && bottomRight != 0) {
|
||||
if ((current.isOutgoing() && ViewUtil.isLtr(this))
|
||||
|| (!current.isOutgoing() && ViewUtil.isRtl(this))) {
|
||||
bottomRight = 0;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
bottomLeft = 0;
|
||||
}
|
||||
}
|
||||
@@ -698,7 +753,7 @@ public class ConversationItem extends BaseConversationItem
|
||||
private void setContactPhoto() {
|
||||
if (contactPhoto == null) return;
|
||||
|
||||
if (!showSender || dcContact ==null) {
|
||||
if (!showSender || dcContact == null) {
|
||||
contactPhoto.setVisibility(View.GONE);
|
||||
} else {
|
||||
contactPhoto.setAvatar(glideRequests, new Recipient(context, dcContact), true);
|
||||
@@ -722,7 +777,8 @@ public class ConversationItem extends BaseConversationItem
|
||||
}
|
||||
DcMsg msg = current.getQuotedMsg();
|
||||
|
||||
// If you modify these lines you may also want to modify ConversationActivity.handleReplyMessage():
|
||||
// If you modify these lines you may also want to modify
|
||||
// ConversationActivity.handleReplyMessage():
|
||||
Recipient author = null;
|
||||
SlideDeck slideDeck = new SlideDeck();
|
||||
if (msg != null) {
|
||||
@@ -735,7 +791,8 @@ public class ConversationItem extends BaseConversationItem
|
||||
}
|
||||
}
|
||||
|
||||
quoteView.setQuote(GlideApp.with(this),
|
||||
quoteView.setQuote(
|
||||
GlideApp.with(this),
|
||||
msg,
|
||||
author,
|
||||
quoteTxt,
|
||||
@@ -746,7 +803,8 @@ public class ConversationItem extends BaseConversationItem
|
||||
quoteView.setVisibility(View.VISIBLE);
|
||||
quoteView.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
|
||||
quoteView.setOnClickListener(view -> {
|
||||
quoteView.setOnClickListener(
|
||||
view -> {
|
||||
if (eventListener != null && batchSelected.isEmpty()) {
|
||||
eventListener.onQuoteClicked(current);
|
||||
} else {
|
||||
@@ -757,7 +815,8 @@ public class ConversationItem extends BaseConversationItem
|
||||
quoteView.setOnLongClickListener(passthroughClickListener);
|
||||
|
||||
if (mediaThumbnailStub.resolved()) {
|
||||
ViewUtil.setTopMargin(mediaThumbnailStub.get(), readDimen(R.dimen.message_bubble_top_padding));
|
||||
ViewUtil.setTopMargin(
|
||||
mediaThumbnailStub.get(), readDimen(R.dimen.message_bubble_top_padding));
|
||||
} else if (stickerStub.resolved()) {
|
||||
ViewUtil.setTopMargin(stickerStub.get(), readDimen(R.dimen.message_bubble_top_padding));
|
||||
}
|
||||
@@ -789,7 +848,8 @@ public class ConversationItem extends BaseConversationItem
|
||||
reactionsView.clear();
|
||||
} else {
|
||||
reactionsView.setReactions(reactions.reactions);
|
||||
reactionsView.setOnClickListener(view -> {
|
||||
reactionsView.setOnClickListener(
|
||||
view -> {
|
||||
if (eventListener != null && batchSelected.isEmpty()) {
|
||||
eventListener.onReactionClicked(current);
|
||||
} else {
|
||||
@@ -807,8 +867,6 @@ public class ConversationItem extends BaseConversationItem
|
||||
return stickerStub.get().getFooter();
|
||||
} else if (hasOnlyThumbnail(messageRecord) && TextUtils.isEmpty(messageRecord.getText())) {
|
||||
return mediaThumbnailStub.get().getFooter();
|
||||
} else if (messageRecord.getType() == DcMsg.DC_MSG_CALL) {
|
||||
return callViewStub.get().getFooter();
|
||||
} else {
|
||||
return footer;
|
||||
}
|
||||
@@ -819,7 +877,7 @@ public class ConversationItem extends BaseConversationItem
|
||||
}
|
||||
|
||||
private void setGroupMessageStatus() {
|
||||
if (messageRecord.getType()==DcMsg.DC_MSG_STICKER) {
|
||||
if (messageRecord.getType() == DcMsg.DC_MSG_STICKER) {
|
||||
this.groupSender.setVisibility(GONE);
|
||||
return;
|
||||
} else {
|
||||
@@ -827,14 +885,14 @@ public class ConversationItem extends BaseConversationItem
|
||||
}
|
||||
|
||||
if (messageRecord.isForwarded()) {
|
||||
if (showSender && dcContact !=null) {
|
||||
this.groupSender.setText(context.getString(R.string.forwarded_by, messageRecord.getSenderName(dcContact)));
|
||||
if (showSender && dcContact != null) {
|
||||
this.groupSender.setText(
|
||||
context.getString(R.string.forwarded_by, messageRecord.getSenderName(dcContact)));
|
||||
} else {
|
||||
this.groupSender.setText(context.getString(R.string.forwarded_message));
|
||||
}
|
||||
this.groupSender.setTextColor(context.getResources().getColor(R.color.unknown_sender));
|
||||
}
|
||||
else if (showSender && dcContact !=null) {
|
||||
} else if (showSender && dcContact != null) {
|
||||
this.groupSender.setText(messageRecord.getSenderName(dcContact));
|
||||
this.groupSender.setTextColor(Util.rgbToArgbColor(dcContact.getColor()));
|
||||
}
|
||||
@@ -854,24 +912,29 @@ public class ConversationItem extends BaseConversationItem
|
||||
}
|
||||
}
|
||||
|
||||
if(current.isForwarded()) {
|
||||
if (current.isForwarded()) {
|
||||
groupSenderHolderVisibility = VISIBLE;
|
||||
}
|
||||
|
||||
groupSenderHolder.setVisibility(groupSenderHolderVisibility);
|
||||
|
||||
boolean collapse = false;
|
||||
if(groupSenderHolderVisibility==VISIBLE && current.getType()==DcMsg.DC_MSG_TEXT) {
|
||||
if (groupSenderHolderVisibility == VISIBLE && current.getType() == DcMsg.DC_MSG_TEXT) {
|
||||
collapse = true;
|
||||
}
|
||||
|
||||
int spacingTop = collapse? 0 /*2dp border come from the senderHolder*/ : readDimen(context, R.dimen.message_bubble_top_padding);
|
||||
int spacingTop =
|
||||
collapse
|
||||
? 0 /*2dp border come from the senderHolder*/
|
||||
: readDimen(context, R.dimen.message_bubble_top_padding);
|
||||
ViewUtil.setPaddingTop(bodyText, spacingTop);
|
||||
}
|
||||
|
||||
private void setMessageShape(@NonNull DcMsg current) {
|
||||
int background;
|
||||
background = current.isOutgoing() ? R.drawable.message_bubble_background_sent_alone
|
||||
background =
|
||||
current.isOutgoing()
|
||||
? R.drawable.message_bubble_background_sent_alone
|
||||
: R.drawable.message_bubble_background_received_alone;
|
||||
bodyBubble.setBackgroundResource(background);
|
||||
}
|
||||
@@ -891,11 +954,17 @@ public class ConversationItem extends BaseConversationItem
|
||||
private int getAvailableMessageBubbleWidth(@NonNull View forView) {
|
||||
int availableWidth;
|
||||
if (hasAudio(messageRecord)) {
|
||||
availableWidth = audioViewStub.get().getMeasuredWidth() + ViewUtil.getLeftMargin(audioViewStub.get()) + ViewUtil.getRightMargin(audioViewStub.get());
|
||||
availableWidth =
|
||||
audioViewStub.get().getMeasuredWidth()
|
||||
+ ViewUtil.getLeftMargin(audioViewStub.get())
|
||||
+ ViewUtil.getRightMargin(audioViewStub.get());
|
||||
} else if (hasThumbnail(messageRecord)) {
|
||||
availableWidth = mediaThumbnailStub.get().getMeasuredWidth();
|
||||
} else {
|
||||
availableWidth = bodyBubble.getMeasuredWidth() - bodyBubble.getPaddingLeft() - bodyBubble.getPaddingRight();
|
||||
availableWidth =
|
||||
bodyBubble.getMeasuredWidth()
|
||||
- bodyBubble.getPaddingLeft()
|
||||
- bodyBubble.getPaddingRight();
|
||||
}
|
||||
|
||||
availableWidth -= ViewUtil.getLeftMargin(forView) + ViewUtil.getRightMargin(forView);
|
||||
@@ -927,7 +996,9 @@ public class ConversationItem extends BaseConversationItem
|
||||
VcardContact vcardContact = rpc.parseVcard(path).get(0);
|
||||
new AlertDialog.Builder(context)
|
||||
.setMessage(context.getString(R.string.ask_start_chat_with, vcardContact.displayName))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
.setPositiveButton(
|
||||
android.R.string.ok,
|
||||
(dialog, which) -> {
|
||||
try {
|
||||
List<Integer> contactIds = rpc.importVcard(dcContext.getAccountId(), path);
|
||||
if (!contactIds.isEmpty()) {
|
||||
@@ -942,13 +1013,19 @@ public class ConversationItem extends BaseConversationItem
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "failed to import vCard", e);
|
||||
}
|
||||
Toast.makeText(context, context.getResources().getString(R.string.error), Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getResources().getString(R.string.error),
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "failed to parse vCard", e);
|
||||
Toast.makeText(context, context.getResources().getString(R.string.error), Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(
|
||||
context, context.getResources().getString(R.string.error), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
} else if (MediaPreviewActivity.isTypeSupported(slide) && slide.getUri() != null) {
|
||||
Intent intent = new Intent(context, MediaPreviewActivity.class);
|
||||
@@ -969,6 +1046,8 @@ public class ConversationItem extends BaseConversationItem
|
||||
public void onClick(final View v, final Slide slide) {
|
||||
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
|
||||
performClick();
|
||||
} else if (eventListener != null) {
|
||||
eventListener.onStickerClicked(messageRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -978,13 +1057,19 @@ public class ConversationItem extends BaseConversationItem
|
||||
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
|
||||
performClick();
|
||||
} else {
|
||||
int accId = dcContext.getAccountId();
|
||||
int chatId = messageRecord.getChatId();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (!messageRecord.isOutgoing() && callInfo.state instanceof CallState.Alerting) {
|
||||
int callId = messageRecord.getId();
|
||||
CallUtil.openCall(getContext(), accId, chatId, callId, callInfo.sdpOffer, callInfo.hasVideo);
|
||||
CallCoordinator coordinator = CallCoordinator.getInstance(context);
|
||||
coordinator.showIncomingCallScreen(callId);
|
||||
} else {
|
||||
CallUtil.startCall(getContext(), accId, chatId, callInfo.hasVideo);
|
||||
if (callInfo.hasVideo) {
|
||||
CallUtil.startVideoCall(getContext(), chatId);
|
||||
} else {
|
||||
CallUtil.startAudioCall(getContext(), chatId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,10 @@ import android.graphics.Canvas;
|
||||
import android.os.Vibrator;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import org.thoughtcrime.securesms.util.AccessibilityUtil;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
|
||||
@@ -31,9 +28,9 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
private final ConversationItemTouchListener itemTouchListener;
|
||||
private final OnSwipeListener onSwipeListener;
|
||||
|
||||
ConversationItemSwipeCallback(@NonNull SwipeAvailabilityProvider swipeAvailabilityProvider,
|
||||
@NonNull OnSwipeListener onSwipeListener)
|
||||
{
|
||||
ConversationItemSwipeCallback(
|
||||
@NonNull SwipeAvailabilityProvider swipeAvailabilityProvider,
|
||||
@NonNull OnSwipeListener onSwipeListener) {
|
||||
super(0, ItemTouchHelper.END);
|
||||
this.itemTouchListener = new ConversationItemTouchListener(this::updateLatestDownCoordinate);
|
||||
this.swipeAvailabilityProvider = swipeAvailabilityProvider;
|
||||
@@ -48,21 +45,19 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView,
|
||||
public boolean onMove(
|
||||
@NonNull RecyclerView recyclerView,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
@NonNull RecyclerView.ViewHolder target)
|
||||
{
|
||||
@NonNull RecyclerView.ViewHolder target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
||||
}
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {}
|
||||
|
||||
@Override
|
||||
public int getSwipeDirs(@NonNull RecyclerView recyclerView,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder)
|
||||
{
|
||||
public int getSwipeDirs(
|
||||
@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
if (cannotSwipeViewHolder(viewHolder)) return 0;
|
||||
return super.getSwipeDirs(recyclerView, viewHolder);
|
||||
}
|
||||
@@ -81,15 +76,18 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
@NonNull Canvas c,
|
||||
@NonNull RecyclerView recyclerView,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
float dx, float dy, int actionState, boolean isCurrentlyActive)
|
||||
{
|
||||
float dx,
|
||||
float dy,
|
||||
int actionState,
|
||||
boolean isCurrentlyActive) {
|
||||
if (cannotSwipeViewHolder(viewHolder)) return;
|
||||
|
||||
float sign = getSignFromDirection(viewHolder.itemView);
|
||||
boolean isCorrectSwipeDir = sameSign(dx, sign);
|
||||
|
||||
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && isCorrectSwipeDir) {
|
||||
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, Math.abs(dx), sign);
|
||||
ConversationSwipeAnimationHelper.update(
|
||||
(ConversationItem) viewHolder.itemView, Math.abs(dx), sign);
|
||||
handleSwipeFeedback((ConversationItem) viewHolder.itemView, Math.abs(dx));
|
||||
if (canTriggerSwipe) {
|
||||
setTouchListener(recyclerView, viewHolder, Math.abs(dx));
|
||||
@@ -122,15 +120,16 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void setTouchListener(@NonNull RecyclerView recyclerView,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
float dx)
|
||||
{
|
||||
recyclerView.setOnTouchListener(new View.OnTouchListener() {
|
||||
private void setTouchListener(
|
||||
@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dx) {
|
||||
recyclerView.setOnTouchListener(
|
||||
new View.OnTouchListener() {
|
||||
|
||||
// This variable is necessary to make sure that the handleTouchActionUp() and therefore onSwiped() is called only once.
|
||||
// This variable is necessary to make sure that the handleTouchActionUp() and therefore
|
||||
// onSwiped() is called only once.
|
||||
// Otherwise, any subsequent little swipe would invoke onSwiped().
|
||||
// We can't call recyclerView.setOnTouchListener(null) because another ConversationItem might have set its own
|
||||
// We can't call recyclerView.setOnTouchListener(null) because another ConversationItem
|
||||
// might have set its own
|
||||
// on touch listener in the meantime and we don't want to cancel it
|
||||
private boolean listenerCalled = false;
|
||||
|
||||
@@ -143,13 +142,15 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (!listenerCalled) {
|
||||
listenerCalled = true;
|
||||
ConversationItemSwipeCallback.this.handleTouchActionUp(recyclerView, viewHolder, dx);
|
||||
ConversationItemSwipeCallback.this.handleTouchActionUp(
|
||||
recyclerView, viewHolder, dx);
|
||||
}
|
||||
//fallthrough
|
||||
// fallthrough
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
swipeBack = true;
|
||||
shouldTriggerSwipeFeedback = false;
|
||||
// Sometimes the view does not go back correctly, so make sure that after 2s the progress is reset:
|
||||
// Sometimes the view does not go back correctly, so make sure that after 2s the
|
||||
// progress is reset:
|
||||
viewHolder.itemView.postDelayed(() -> resetProgress(viewHolder), 2000);
|
||||
if (AccessibilityUtil.areAnimationsDisabled(viewHolder.itemView.getContext())) {
|
||||
resetProgress(viewHolder);
|
||||
@@ -158,14 +159,11 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void handleTouchActionUp(@NonNull RecyclerView recyclerView,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
float dx)
|
||||
{
|
||||
private void handleTouchActionUp(
|
||||
@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dx) {
|
||||
if (dx > SWIPE_SUCCESS_DX) {
|
||||
canTriggerSwipe = false;
|
||||
onSwiped(viewHolder);
|
||||
@@ -177,17 +175,16 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
}
|
||||
|
||||
private static void resetProgress(RecyclerView.ViewHolder viewHolder) {
|
||||
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView,
|
||||
0f,
|
||||
getSignFromDirection(viewHolder.itemView));
|
||||
ConversationSwipeAnimationHelper.update(
|
||||
(ConversationItem) viewHolder.itemView, 0f, getSignFromDirection(viewHolder.itemView));
|
||||
}
|
||||
|
||||
private boolean cannotSwipeViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
if (!(viewHolder.itemView instanceof ConversationItem)) return true;
|
||||
|
||||
ConversationItem item = ((ConversationItem) viewHolder.itemView);
|
||||
return !swipeAvailabilityProvider.isSwipeAvailable(item.getMessageRecord()) ||
|
||||
item.disallowSwipe(latestDownX, latestDownY);
|
||||
return !swipeAvailabilityProvider.isSwipeAvailable(item.getMessageRecord())
|
||||
|| item.disallowSwipe(latestDownX, latestDownY);
|
||||
}
|
||||
|
||||
private void updateLatestDownCoordinate(float x, float y) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_PROXY_ENABLED;
|
||||
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_PROXY_URL;
|
||||
import static org.thoughtcrime.securesms.util.ShareUtil.acquireRelayMessageContent;
|
||||
import static org.thoughtcrime.securesms.util.ShareUtil.getDirectSharingChatId;
|
||||
import static org.thoughtcrime.securesms.util.ShareUtil.getForwardedMessageAccountId;
|
||||
import static org.thoughtcrime.securesms.util.ShareUtil.getSharedTitle;
|
||||
import static org.thoughtcrime.securesms.util.ShareUtil.isDirectSharing;
|
||||
import static org.thoughtcrime.securesms.util.ShareUtil.isForwarding;
|
||||
import static org.thoughtcrime.securesms.util.ShareUtil.getForwardedMessageAccountId;
|
||||
import static org.thoughtcrime.securesms.util.ShareUtil.isRelayingMessageContent;
|
||||
import static org.thoughtcrime.securesms.util.ShareUtil.resetRelayingMessageContent;
|
||||
|
||||
@@ -45,14 +45,16 @@ import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.appcompat.widget.TooltipCompat;
|
||||
import androidx.core.view.MenuCompat;
|
||||
|
||||
import chat.delta.rpc.types.SecurejoinSource;
|
||||
import chat.delta.rpc.types.SecurejoinUiPath;
|
||||
import com.amulyakhare.textdrawable.TextDrawable;
|
||||
import com.b44t.messenger.DcAccounts;
|
||||
import com.b44t.messenger.DcContact;
|
||||
@@ -60,12 +62,14 @@ import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import org.thoughtcrime.securesms.components.AvatarView;
|
||||
import org.thoughtcrime.securesms.components.SearchToolbar;
|
||||
import org.thoughtcrime.securesms.connect.AccountManager;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.connect.DirectShareUtil;
|
||||
import org.thoughtcrime.securesms.geolocation.LocationStreamingService;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
@@ -77,30 +81,23 @@ import org.thoughtcrime.securesms.search.SearchFragment;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.Prefs;
|
||||
import org.thoughtcrime.securesms.util.ScreenLockUtil;
|
||||
import org.thoughtcrime.securesms.util.ShareUtil;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||
import org.thoughtcrime.securesms.util.ScreenLockUtil;
|
||||
import org.thoughtcrime.securesms.util.SendRelayedMessageUtil;
|
||||
import org.thoughtcrime.securesms.util.ShareUtil;
|
||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import chat.delta.rpc.types.SecurejoinSource;
|
||||
import chat.delta.rpc.types.SecurejoinUiPath;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
public class ConversationListActivity extends PassphraseRequiredActionBarActivity
|
||||
implements ConversationListFragment.ConversationSelectedListener
|
||||
{
|
||||
private static final String TAG = ConversationListActivity.class.getSimpleName();
|
||||
implements ConversationListFragment.ConversationSelectedListener {
|
||||
private static final String TAG = "ConversationListActivity";
|
||||
private static final String OPENPGP4FPR = "openpgp4fpr";
|
||||
private static final String NDK_ARCH_WARNED = "ndk_arch_warned";
|
||||
public static final String CLEAR_NOTIFICATIONS = "clear_notifications";
|
||||
public static final String ACCOUNT_ID_EXTRA = "account_id";
|
||||
public static final String FROM_WELCOME = "from_welcome";
|
||||
public static final String FROM_WELCOME_RAW_QR = "from_welcome_raw_qr";
|
||||
private static final int REQUEST_CODE_CONFIRM_CREDENTIALS_DELETE_PROFILE = ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS+1;
|
||||
|
||||
private ConversationListFragment conversationListFragment;
|
||||
public TextView title;
|
||||
@@ -112,11 +109,23 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
private ViewGroup fragmentContainer;
|
||||
private ViewGroup selfAvatarContainer;
|
||||
|
||||
/** used to store temporarily scanned QR to pass it back to QrCodeHandler when ScreenLockUtil is used */
|
||||
/**
|
||||
* used to store temporarily scanned QR to pass it back to QrCodeHandler when ScreenLockUtil is
|
||||
* used
|
||||
*/
|
||||
private String qrData = null;
|
||||
/** used to store temporarily profile ID to delete after authorization is granted via ScreenLockUtil */
|
||||
|
||||
private ActivityResultLauncher<Intent> relayLockLauncher;
|
||||
private ActivityResultLauncher<Intent> qrScannerLauncher;
|
||||
|
||||
/**
|
||||
* used to store temporarily profile ID to delete after authorization is granted via
|
||||
* ScreenLockUtil
|
||||
*/
|
||||
private int deleteProfileId = 0;
|
||||
|
||||
private ActivityResultLauncher<Intent> deleteProfileLockLauncher;
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
dynamicTheme = new DynamicNoActionBarTheme();
|
||||
@@ -125,6 +134,44 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle, boolean ready) {
|
||||
relayLockLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == RESULT_OK) {
|
||||
// QrCodeHandler requested user authorization before adding a relay
|
||||
// and it was granted, so proceed to add the relay
|
||||
if (qrData != null) {
|
||||
new QrCodeHandler(this).addRelay(qrData);
|
||||
qrData = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
deleteProfileLockLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == RESULT_OK) {
|
||||
if (deleteProfileId != 0) {
|
||||
deleteProfile(deleteProfileId);
|
||||
deleteProfileId = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
qrScannerLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == RESULT_OK) {
|
||||
IntentResult scanResult =
|
||||
IntentIntegrator.parseActivityResult(result.getResultCode(), result.getData());
|
||||
qrData = scanResult.getContents();
|
||||
new QrCodeHandler(this)
|
||||
.handleQrData(
|
||||
qrData, SecurejoinSource.Scan, SecurejoinUiPath.QrIcon, relayLockLauncher);
|
||||
}
|
||||
});
|
||||
|
||||
addDeviceMessages(getIntent().getBooleanExtra(FROM_WELCOME, false));
|
||||
if (getIntent().getIntExtra(ACCOUNT_ID_EXTRA, -1) <= 0) {
|
||||
getIntent().putExtra(ACCOUNT_ID_EXTRA, DcHelper.getContext(this).getAccountId());
|
||||
@@ -147,11 +194,15 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
ViewUtil.applyWindowInsetsAsMargin(searchToolbar, true, true, true, false);
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
conversationListFragment = initFragment(R.id.fragment_container, new ConversationListFragment(), bundle);
|
||||
conversationListFragment =
|
||||
initFragment(R.id.fragment_container, new ConversationListFragment(), bundle);
|
||||
|
||||
initializeSearchListener();
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
getOnBackPressedDispatcher()
|
||||
.addCallback(
|
||||
this,
|
||||
new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (searchToolbar.isVisible()) {
|
||||
@@ -162,8 +213,10 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
int selectedAccId = DcHelper.getContext(activity).getAccountId();
|
||||
int initialAccId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, selectedAccId);
|
||||
if (initialAccId != selectedAccId) {
|
||||
// allowing to go back is dangerous, it could be activity on previously selected account,
|
||||
// instead of figuring out account rollback in onResume in each activity (conversation, gallery, media preview, webxdc, etc.)
|
||||
// allowing to go back is dangerous, it could be activity on previously
|
||||
// selected account,
|
||||
// instead of figuring out account rollback in onResume in each activity
|
||||
// (conversation, gallery, media preview, webxdc, etc.)
|
||||
// just clear the back stack and stay in newly selected account
|
||||
finishAffinity();
|
||||
startActivity(new Intent(activity, ConversationListActivity.class));
|
||||
@@ -182,8 +235,11 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
TooltipCompat.setTooltipText(searchAction, getText(R.string.search_explain));
|
||||
|
||||
TooltipCompat.setTooltipText(selfAvatar, getText(R.string.switch_account));
|
||||
selfAvatar.setOnClickListener(v -> AccountManager.getInstance().showSwitchAccountMenu(this, false));
|
||||
findViewById(R.id.avatar_and_title).setOnClickListener(v -> {
|
||||
selfAvatar.setOnClickListener(
|
||||
v -> AccountManager.getInstance().showSwitchAccountMenu(this, false));
|
||||
findViewById(R.id.avatar_and_title)
|
||||
.setOnClickListener(
|
||||
v -> {
|
||||
if (!isRelayingMessageContent(this)) {
|
||||
AccountManager.getInstance().showSwitchAccountMenu(this, false);
|
||||
}
|
||||
@@ -207,7 +263,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
* If the build script is invoked with a specific architecture (e.g.`ndk-make.sh arm64-v8a`), it
|
||||
* will compile the core only for this arch. This method checks if the arch was correct.
|
||||
*
|
||||
* In order to do this, `ndk-make.sh` writes its argument into the file `ndkArch`.
|
||||
* <p>In order to do this, `ndk-make.sh` writes its argument into the file `ndkArch`.
|
||||
* `getNdkArch()` in `build.gradle` then reads this file and its content is assigned to
|
||||
* `BuildConfig.NDK_ARCH`.
|
||||
*/
|
||||
@@ -220,7 +276,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
String arch;
|
||||
|
||||
// armv8l is 32 bit mode in 64 bit CPU:
|
||||
if (archProperty.startsWith("armv7") || archProperty.startsWith("armv8l")) arch = "armeabi-v7a";
|
||||
if (archProperty.startsWith("armv7") || archProperty.startsWith("armv8l"))
|
||||
arch = "armeabi-v7a";
|
||||
else if (archProperty.equals("aarch64")) arch = "arm64-v8a";
|
||||
else if (archProperty.equals("i686")) arch = "x86";
|
||||
else if (archProperty.equals("x86_64")) arch = "x86_64";
|
||||
@@ -234,15 +291,27 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
|
||||
String message;
|
||||
if (arch.equals("")) {
|
||||
message = "This phone has the unknown architecture " + archProperty + ".\n\n"+
|
||||
"Please open an issue at https://github.com/deltachat/deltachat-android/issues.";
|
||||
message =
|
||||
"This phone has the unknown architecture "
|
||||
+ archProperty
|
||||
+ ".\n\n"
|
||||
+ "Please open an issue at https://github.com/deltachat/deltachat-android/issues.";
|
||||
} else {
|
||||
message = "Apparently you used `ndk-make.sh " + BuildConfig.NDK_ARCH + "`, but this device is " + arch + ".\n\n" +
|
||||
"You can use the app, but changes you made to the Rust code were not applied.\n\n" +
|
||||
"To compile in your changes, you can:\n" +
|
||||
"- Either run `ndk-make.sh " + arch + "` to build only for " + arch + " in debug mode\n" +
|
||||
"- Or run `ndk-make.sh` without argument to build for all architectures in release mode\n\n" +
|
||||
"If something doesn't work, please open an issue at https://github.com/deltachat/deltachat-android/issues!!";
|
||||
message =
|
||||
"Apparently you used `ndk-make.sh "
|
||||
+ BuildConfig.NDK_ARCH
|
||||
+ "`, but this device is "
|
||||
+ arch
|
||||
+ ".\n\n"
|
||||
+ "You can use the app, but changes you made to the Rust code were not applied.\n\n"
|
||||
+ "To compile in your changes, you can:\n"
|
||||
+ "- Either run `ndk-make.sh "
|
||||
+ arch
|
||||
+ "` to build only for "
|
||||
+ arch
|
||||
+ " in debug mode\n"
|
||||
+ "- Or run `ndk-make.sh` without argument to build for all architectures in release mode\n\n"
|
||||
+ "If something doesn't work, please open an issue at https://github.com/deltachat/deltachat-android/issues!!";
|
||||
}
|
||||
Log.e(TAG, message);
|
||||
|
||||
@@ -311,7 +380,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
} else {
|
||||
title.setText(DcHelper.getContext(this).getName());
|
||||
// refreshTitle is called by ConversationListFragment when connectivity changes so update connectivity dot here
|
||||
// refreshTitle is called by ConversationListFragment when connectivity changes so update
|
||||
// connectivity dot here
|
||||
selfAvatar.setConnectivity(DcHelper.getContext(this).getConnectivity());
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
}
|
||||
@@ -347,23 +417,26 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
}
|
||||
}
|
||||
|
||||
if(unreadCount == 0) {
|
||||
if (unreadCount == 0) {
|
||||
unreadIndicator.setVisibility(View.GONE);
|
||||
} else {
|
||||
boolean isDarkTheme = DynamicTheme.isDarkTheme(this);
|
||||
int badgeColor = Color.WHITE;
|
||||
if (isDarkTheme) {
|
||||
final TypedArray attrs = obtainStyledAttributes(new int[] {
|
||||
final TypedArray attrs =
|
||||
obtainStyledAttributes(
|
||||
new int[] {
|
||||
R.attr.conversation_list_item_unreadcount_color,
|
||||
});
|
||||
badgeColor = attrs.getColor(0, Color.BLACK);
|
||||
}
|
||||
String label = unreadCount>99? "99+" : String.valueOf(unreadCount);
|
||||
unreadIndicator.setImageDrawable(TextDrawable.builder()
|
||||
String label = unreadCount > 99 ? "99+" : String.valueOf(unreadCount);
|
||||
unreadIndicator.setImageDrawable(
|
||||
TextDrawable.builder()
|
||||
.beginConfig()
|
||||
.width(ViewUtil.dpToPx(this, 20))
|
||||
.height(ViewUtil.dpToPx(this, 20))
|
||||
.textColor(isDarkTheme? Color.WHITE : Color.BLACK)
|
||||
.textColor(isDarkTheme ? Color.WHITE : Color.BLACK)
|
||||
.bold()
|
||||
.endConfig()
|
||||
.buildRound(label, badgeColor));
|
||||
@@ -377,6 +450,9 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
refreshTitle();
|
||||
invalidateOptionsMenu();
|
||||
DirectShareUtil.triggerRefreshDirectShare(this);
|
||||
if (DcHelper.getContext(this).isSendingLocationsToChat(0)) {
|
||||
LocationStreamingService.ensureRunning(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -386,9 +462,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
|
||||
if (isRelayingMessageContent(this)) {
|
||||
inflater.inflate(R.menu.forwarding_menu, menu);
|
||||
menu.findItem(R.id.menu_export_attachment).setVisible(
|
||||
ShareUtil.isFromWebxdc(this) && ShareUtil.getSharedUris(this).size() == 1
|
||||
);
|
||||
menu.findItem(R.id.menu_export_attachment)
|
||||
.setVisible(ShareUtil.isFromWebxdc(this) && ShareUtil.getSharedUris(this).size() == 1);
|
||||
} else {
|
||||
inflater.inflate(R.menu.text_secure_normal, menu);
|
||||
menu.findItem(R.id.menu_global_map).setVisible(Prefs.isLocationStreamingEnabled(this));
|
||||
@@ -397,7 +472,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
proxyItem.setVisible(false);
|
||||
} else {
|
||||
boolean proxyEnabled = DcHelper.getInt(this, CONFIG_PROXY_ENABLED) == 1;
|
||||
proxyItem.setIcon(proxyEnabled? R.drawable.ic_proxy_enabled_24 : R.drawable.ic_proxy_disabled_24);
|
||||
proxyItem.setIcon(
|
||||
proxyEnabled ? R.drawable.ic_proxy_enabled_24 : R.drawable.ic_proxy_disabled_24);
|
||||
proxyItem.setVisible(true);
|
||||
}
|
||||
MenuCompat.setGroupDividerEnabled(menu, true);
|
||||
@@ -408,12 +484,15 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
}
|
||||
|
||||
private void initializeSearchListener() {
|
||||
searchAction.setOnClickListener(v -> {
|
||||
searchToolbar.display(searchAction.getX() + (searchAction.getWidth() / 2),
|
||||
searchAction.setOnClickListener(
|
||||
v -> {
|
||||
searchToolbar.display(
|
||||
searchAction.getX() + (searchAction.getWidth() / 2),
|
||||
searchAction.getY() + (searchAction.getHeight() / 2));
|
||||
});
|
||||
|
||||
searchToolbar.setListener(new SearchToolbar.SearchListener() {
|
||||
searchToolbar.setListener(
|
||||
new SearchToolbar.SearchListener() {
|
||||
@Override
|
||||
public void onSearchTextChange(String text) {
|
||||
String trimmed = text.trim();
|
||||
@@ -421,15 +500,14 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
if (trimmed.length() > 0) {
|
||||
if (searchFragment == null) {
|
||||
searchFragment = SearchFragment.newInstance();
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(R.id.fragment_container, searchFragment, null)
|
||||
.commit();
|
||||
}
|
||||
searchFragment.updateSearchQuery(trimmed);
|
||||
} else if (searchFragment != null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.remove(searchFragment)
|
||||
.commit();
|
||||
getSupportFragmentManager().beginTransaction().remove(searchFragment).commit();
|
||||
searchFragment = null;
|
||||
}
|
||||
}
|
||||
@@ -437,9 +515,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
@Override
|
||||
public void onSearchClosed() {
|
||||
if (searchFragment != null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.remove(searchFragment)
|
||||
.commit();
|
||||
getSupportFragmentManager().beginTransaction().remove(searchFragment).commit();
|
||||
searchFragment = null;
|
||||
}
|
||||
}
|
||||
@@ -458,7 +534,9 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
startActivity(new Intent(this, ApplicationPreferencesActivity.class));
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_qr) {
|
||||
new IntentIntegrator(this).setCaptureActivity(QrActivity.class).initiateScan();
|
||||
Intent intent =
|
||||
new IntentIntegrator(this).setCaptureActivity(QrActivity.class).createScanIntent();
|
||||
qrScannerLauncher.launch(intent);
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_global_map) {
|
||||
WebxdcActivity.openMaps(this, 0);
|
||||
@@ -483,7 +561,9 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
}
|
||||
|
||||
private void handleSaveAttachment() {
|
||||
SaveAttachmentTask.showWarningDialog(this, (dialogInterface, i) -> {
|
||||
SaveAttachmentTask.showWarningDialog(
|
||||
this,
|
||||
(dialogInterface, i) -> {
|
||||
if (StorageUtil.canWriteToMediaStore(this)) {
|
||||
performSave();
|
||||
return;
|
||||
@@ -504,7 +584,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
Uri uri = uriList.get(0);
|
||||
String mimeType = PersistentBlobProvider.getMimeType(this, uri);
|
||||
String fileName = PersistentBlobProvider.getFileName(this, uri);
|
||||
SaveAttachmentTask.Attachment[] attachments = new SaveAttachmentTask.Attachment[]{
|
||||
SaveAttachmentTask.Attachment[] attachments =
|
||||
new SaveAttachmentTask.Attachment[] {
|
||||
new SaveAttachmentTask.Attachment(uri, mimeType, new Date().getTime(), fileName)
|
||||
};
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(this);
|
||||
@@ -513,8 +594,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
}
|
||||
|
||||
private void handleOpenpgp4fpr() {
|
||||
if (getIntent() != null &&
|
||||
Intent.ACTION_VIEW.equals(getIntent().getAction())) {
|
||||
if (getIntent() != null && Intent.ACTION_VIEW.equals(getIntent().getAction())) {
|
||||
Uri uri = getIntent().getData();
|
||||
if (uri == null) {
|
||||
return;
|
||||
@@ -546,10 +626,18 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
final DcContext dcContext = DcHelper.getContext(this);
|
||||
int fwdAccId = getForwardedMessageAccountId(this);
|
||||
if (fwdAccId == dcContext.getAccountId() && dcContext.getChat(chatId).isSelfTalk()) {
|
||||
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
|
||||
Toast.makeText(this, DynamicTheme.getCheckmarkEmoji(this) + " " + getString(R.string.saved), Toast.LENGTH_SHORT).show();
|
||||
SendRelayedMessageUtil.immediatelyRelay(
|
||||
this,
|
||||
chatId,
|
||||
() -> {
|
||||
Toast.makeText(
|
||||
this,
|
||||
DynamicTheme.getCheckmarkEmoji(this) + " " + getString(R.string.saved),
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
handleResetRelaying();
|
||||
finish();
|
||||
});
|
||||
} else {
|
||||
Intent intent = new Intent(this, ConversationActivity.class);
|
||||
intent.putExtra(ConversationActivity.ACCOUNT_ID_EXTRA, dcContext.getAccountId());
|
||||
@@ -592,25 +680,32 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
|
||||
private void addDeviceMessages(boolean fromWelcome) {
|
||||
// update messages - for new messages, do not reuse or modify strings but create new ones.
|
||||
// it is not needed to keep all past update messages, however, when deleted, also the strings should be deleted.
|
||||
// it is not needed to keep all past update messages, however, when deleted, also the strings
|
||||
// should be deleted.
|
||||
try {
|
||||
DcContext dcContext = DcHelper.getContext(this);
|
||||
final String deviceMsgLabel = "update_2_33_1_android";
|
||||
final String deviceMsgLabel = "update_2_46_0a_android";
|
||||
if (!dcContext.wasDeviceMsgEverAdded(deviceMsgLabel)) {
|
||||
DcMsg msg = null;
|
||||
if (!getIntent().getBooleanExtra(FROM_WELCOME, false)) {
|
||||
msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
|
||||
|
||||
// InputStream inputStream = getResources().getAssets().open("device-messages/green-checkmark.jpg");
|
||||
// InputStream inputStream =
|
||||
// getResources().getAssets().open("device-messages/green-checkmark.jpg");
|
||||
// String outputFile = DcHelper.getBlobdirFile(dcContext, "green-checkmark", ".jpg");
|
||||
// Util.copy(inputStream, new FileOutputStream(outputFile));
|
||||
// msg.setFile(outputFile, "image/jpeg");
|
||||
|
||||
msg.setText(getString(R.string.update_2_33, "https://arcanechat.me/#contribute"));
|
||||
msg.setText(
|
||||
getString(
|
||||
R.string.update_2_46ac,
|
||||
"https://arcanechat.me/#contribute",
|
||||
"https://i.delta.chat/#0A45953086F0C166D3BAF1D4BB2025496E4C2704&x=MVPi07rQBEmHO4FRb3brpwDe&j=n8mkKqu42WAKKUCx1bQOVh23&s=AM1YCd_2hKuiSiTEb-2177On&a=arcanechat%40arcanechat.me&n=ArcaneChat&b=ArcaneChat+Channel"));
|
||||
}
|
||||
dcContext.addDeviceMsg(deviceMsgLabel, msg);
|
||||
|
||||
if (Prefs.getStringPreference(this, Prefs.LAST_DEVICE_MSG_LABEL, "").equals(deviceMsgLabel)) {
|
||||
if (Prefs.getStringPreference(this, Prefs.LAST_DEVICE_MSG_LABEL, "")
|
||||
.equals(deviceMsgLabel)) {
|
||||
int deviceChatId = dcContext.getChatIdByContactId(DcContact.DC_CONTACT_ID_DEVICE);
|
||||
if (deviceChatId != 0) {
|
||||
dcContext.marknoticedChat(deviceChatId);
|
||||
@@ -619,7 +714,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
Prefs.setStringPreference(this, Prefs.LAST_DEVICE_MSG_LABEL, deviceMsgLabel);
|
||||
}
|
||||
|
||||
} catch(Exception e) {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -634,7 +729,12 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
|
||||
public void onDeleteProfile(int profileId) {
|
||||
deleteProfileId = profileId;
|
||||
boolean result = ScreenLockUtil.applyScreenLock(this, getString(R.string.delete_account), getString(R.string.enter_system_secret_to_continue), REQUEST_CODE_CONFIRM_CREDENTIALS_DELETE_PROFILE);
|
||||
boolean result =
|
||||
ScreenLockUtil.applyScreenLock(
|
||||
this,
|
||||
getString(R.string.delete_account),
|
||||
getString(R.string.enter_system_secret_to_continue),
|
||||
deleteProfileLockLauncher);
|
||||
if (!result) {
|
||||
deleteProfile(profileId);
|
||||
}
|
||||
@@ -662,38 +762,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (resultCode != RESULT_OK) return;
|
||||
|
||||
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
|
||||
switch (requestCode) {
|
||||
case IntentIntegrator.REQUEST_CODE:
|
||||
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
|
||||
qrData = scanResult.getContents();
|
||||
qrCodeHandler.handleQrData(qrData, SecurejoinSource.Scan, SecurejoinUiPath.QrIcon);
|
||||
break;
|
||||
case ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS:
|
||||
// QrCodeHandler requested user authorization before adding a relay
|
||||
// and it was granted, so proceed to add the relay
|
||||
if (qrData != null) {
|
||||
qrCodeHandler.addRelay(qrData);
|
||||
qrData = null;
|
||||
}
|
||||
break;
|
||||
case REQUEST_CODE_CONFIRM_CREDENTIALS_DELETE_PROFILE:
|
||||
if (deleteProfileId != 0) {
|
||||
deleteProfile(deleteProfileId);
|
||||
deleteProfileId = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
public void onRequestPermissionsResult(
|
||||
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user