mirror of
https://github.com/ArcaneChat/android.git
synced 2026-07-03 14:05:24 +02:00
Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f0e95a59d1 | |||
| d44ad7de0c | |||
| 3e0a283633 | |||
| ac01395482 | |||
| fc1adc4863 | |||
| d68c32ff26 | |||
| b67e9d795e | |||
| ae7be8a841 | |||
| 1894425ad2 | |||
| 3460e7e405 | |||
| ca40ae7869 | |||
| cb05abb85f | |||
| 8a76f99701 | |||
| fd5a3b9f8e | |||
| 6cab1d1cf4 | |||
| be8009c950 | |||
| f6c1fa2f5c | |||
| 6949be6fd8 | |||
| 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
|
||||
|
||||
@@ -45,10 +45,10 @@ jobs:
|
||||
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
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Delta Chat Android Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
* 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
|
||||
* 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
|
||||
* Update to core 2.44.0
|
||||
|
||||
## v2.43.0
|
||||
2026-02
|
||||
|
||||
|
||||
+1
-1
Submodule jni/deltachat-core-rust updated: 7ad67cfa56...27dbe0bb6c
@@ -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));
|
||||
Intent.makeMainActivity(
|
||||
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'");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,26 +120,22 @@ public class TestUtils {
|
||||
}
|
||||
|
||||
throw new NoMatchingViewException.Builder()
|
||||
.withRootView(view)
|
||||
.withViewMatcher(matcher)
|
||||
.build();
|
||||
.withRootView(view)
|
||||
.withViewMatcher(matcher)
|
||||
.build();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.class.getSimpleName();
|
||||
|
||||
@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,26 +43,34 @@ 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 -> {
|
||||
int contactId = DcHelper.getContext(a).createContact("", "abc@example.org");
|
||||
createdSingleChatId = DcHelper.getContext(a).createChatByContactId(contactId);
|
||||
});
|
||||
activityRule
|
||||
.getScenario()
|
||||
.onActivity(
|
||||
a -> {
|
||||
int contactId = DcHelper.getContext(a).createContact("", "abc@example.org");
|
||||
createdSingleChatId = DcHelper.getContext(a).createChatByContactId(contactId);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
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"
|
||||
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"/>
|
||||
<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"/>
|
||||
</group>
|
||||
<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" />
|
||||
<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" />
|
||||
</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,14 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<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
|
||||
android:name=".connect.KeepAliveService"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:enabled="true" />
|
||||
</application>
|
||||
<application>
|
||||
<service
|
||||
android:name=".connect.KeepAliveService"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:enabled="true" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -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,34 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<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"/>
|
||||
<!-- 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
|
||||
android:name=".connect.KeepAliveService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:enabled="true" />
|
||||
<application>
|
||||
<service
|
||||
android:name=".connect.KeepAliveService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:enabled="true" />
|
||||
|
||||
<service
|
||||
android:name=".notifications.FcmReceiveService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name=".notifications.FcmReceiveService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||
android:authorities="${applicationId}.firebaseinitprovider"
|
||||
tools:node="remove">
|
||||
</provider>
|
||||
</application>
|
||||
<provider
|
||||
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||
android:authorities="${applicationId}.firebaseinitprovider"
|
||||
tools:node="remove">
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
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;
|
||||
@@ -35,36 +31,38 @@ public class FcmReceiveService extends FirebaseMessagingService {
|
||||
return;
|
||||
}
|
||||
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
final String rawToken;
|
||||
Util.runOnAnyBackgroundThread(
|
||||
() -> {
|
||||
final String rawToken;
|
||||
|
||||
try {
|
||||
synchronized (INIT_LOCK) {
|
||||
if (!initialized) {
|
||||
// manual init: read tokens from `./google-services.json`;
|
||||
// automatic init disabled in AndroidManifest.xml to skip FCM code completely.
|
||||
FirebaseApp.initializeApp(context);
|
||||
try {
|
||||
synchronized (INIT_LOCK) {
|
||||
if (!initialized) {
|
||||
// manual init: read tokens from `./google-services.json`;
|
||||
// automatic init disabled in AndroidManifest.xml to skip FCM code completely.
|
||||
FirebaseApp.initializeApp(context);
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
rawToken = Tasks.await(FirebaseMessaging.getInstance().getToken());
|
||||
} catch (Exception e) {
|
||||
// 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;
|
||||
}
|
||||
if (TextUtils.isEmpty(rawToken)) {
|
||||
Log.w(TAG, "got empty FCM token for " + BuildConfig.APPLICATION_ID);
|
||||
triedRegistering = true;
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
rawToken = Tasks.await(FirebaseMessaging.getInstance().getToken());
|
||||
} catch (Exception e) {
|
||||
// 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;
|
||||
}
|
||||
if (TextUtils.isEmpty(rawToken)) {
|
||||
Log.w(TAG, "got empty FCM token for " + BuildConfig.APPLICATION_ID);
|
||||
triedRegistering = true;
|
||||
return;
|
||||
}
|
||||
|
||||
prefixedToken = addPrefix(rawToken);
|
||||
Log.i(TAG, "FCM token: " + prefixedToken);
|
||||
ApplicationContext.getDcAccounts().setPushDeviceToken(prefixedToken);
|
||||
triedRegistering = true;
|
||||
});
|
||||
prefixedToken = addPrefix(rawToken);
|
||||
Log.i(TAG, "FCM token: " + prefixedToken);
|
||||
ApplicationContext.getDcAccounts().setPushDeviceToken(prefixedToken);
|
||||
triedRegistering = true;
|
||||
});
|
||||
}
|
||||
|
||||
// wait a until FCM registration got a token or not.
|
||||
@@ -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 {
|
||||
|
||||
@@ -1369,12 +1369,10 @@ Delta Chat вместо этого использует реализацию Ope
|
||||
<p>Таким образом, серверы могут видеть только:</p>
|
||||
|
||||
<ul>
|
||||
<li>адрес отправителя и получателя</li>
|
||||
<li>а также размер сообщения.</li>
|
||||
<li>Адреса отправителя и получателя, по умолчанию генерируются случайным образом</li>
|
||||
<li>Размер сообщения</li>
|
||||
</ul>
|
||||
|
||||
<p>По умолчанию адреса генерируются случайным образом.</p>
|
||||
|
||||
<p>Все прочие метаданные сообщений, контактов и групп содержатся в части сообщений, защищённой сквозным шифрованием.</p>
|
||||
|
||||
<h3 id="device-seizure">
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -2,64 +2,92 @@ package com.b44t.messenger;
|
||||
|
||||
public class DcAccounts {
|
||||
|
||||
public DcAccounts(String dir, DcEventChannel channel) {
|
||||
accountsCPtr = createAccountsCPtr(dir, channel);
|
||||
if (accountsCPtr == 0) throw new RuntimeException("createAccountsCPtr() returned null pointer");
|
||||
public DcAccounts(String dir, DcEventChannel channel) {
|
||||
accountsCPtr = createAccountsCPtr(dir, channel);
|
||||
if (accountsCPtr == 0) throw new RuntimeException("createAccountsCPtr() returned null pointer");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unref();
|
||||
}
|
||||
|
||||
public void unref() {
|
||||
if (accountsCPtr != 0) {
|
||||
unrefAccountsCPtr();
|
||||
accountsCPtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unref();
|
||||
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 void unref() {
|
||||
if (accountsCPtr != 0) {
|
||||
unrefAccountsCPtr();
|
||||
accountsCPtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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 ();
|
||||
|
||||
public boolean isAllChatmail() {
|
||||
for (int accountId : getAll()) {
|
||||
DcContext dcContext = getAccount(accountId);
|
||||
if (!dcContext.isChatmail()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
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();
|
||||
|
||||
public boolean isAllChatmail() {
|
||||
for (int accountId : getAll()) {
|
||||
DcContext dcContext = getAccount(accountId);
|
||||
if (!dcContext.isChatmail()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,31 +2,35 @@ package com.b44t.messenger;
|
||||
|
||||
public class DcBackupProvider {
|
||||
|
||||
public DcBackupProvider(long backupProviderCPtr) {
|
||||
this.backupProviderCPtr = backupProviderCPtr;
|
||||
public DcBackupProvider(long backupProviderCPtr) {
|
||||
this.backupProviderCPtr = backupProviderCPtr;
|
||||
}
|
||||
|
||||
public boolean isOk() {
|
||||
return backupProviderCPtr != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unref();
|
||||
}
|
||||
|
||||
public void unref() {
|
||||
if (backupProviderCPtr != 0) {
|
||||
unrefBackupProviderCPtr();
|
||||
backupProviderCPtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isOk() {
|
||||
return backupProviderCPtr != 0;
|
||||
}
|
||||
public native String getQr();
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unref();
|
||||
}
|
||||
public native String getQrSvg();
|
||||
|
||||
public void unref() {
|
||||
if (backupProviderCPtr != 0) {
|
||||
unrefBackupProviderCPtr();
|
||||
backupProviderCPtr = 0;
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
// working with raw c-data
|
||||
private long backupProviderCPtr; // CAVE: the name is referenced in the JNI
|
||||
private native void unrefBackupProviderCPtr();
|
||||
private native void unrefBackupProviderCPtr();
|
||||
}
|
||||
|
||||
@@ -1,76 +1,109 @@
|
||||
package com.b44t.messenger;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class DcChat {
|
||||
|
||||
public static final int DC_CHAT_TYPE_UNDEFINED = 0;
|
||||
public static final int DC_CHAT_TYPE_SINGLE = 100;
|
||||
public static final int DC_CHAT_TYPE_GROUP = 120;
|
||||
public static final int DC_CHAT_TYPE_MAILINGLIST = 140;
|
||||
public static final int DC_CHAT_TYPE_OUT_BROADCAST = 160;
|
||||
public static final int DC_CHAT_TYPE_IN_BROADCAST = 165;
|
||||
public static final int DC_CHAT_TYPE_UNDEFINED = 0;
|
||||
public static final int DC_CHAT_TYPE_SINGLE = 100;
|
||||
public static final int DC_CHAT_TYPE_GROUP = 120;
|
||||
public static final int DC_CHAT_TYPE_MAILINGLIST = 140;
|
||||
public static final int DC_CHAT_TYPE_OUT_BROADCAST = 160;
|
||||
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_NO_CHAT = 0;
|
||||
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;
|
||||
private int accountId;
|
||||
|
||||
public DcChat(int accountId, long chatCPtr) {
|
||||
this.accountId = accountId;
|
||||
this.chatCPtr = chatCPtr;
|
||||
public DcChat(int accountId, long chatCPtr) {
|
||||
this.accountId = accountId;
|
||||
this.chatCPtr = chatCPtr;
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
// aliases and higher-level tools
|
||||
|
||||
public boolean isMultiUser() {
|
||||
int type = getType();
|
||||
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;
|
||||
}
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefChatCPtr();
|
||||
chatCPtr = 0;
|
||||
}
|
||||
public boolean isMailingList() {
|
||||
return getType() == DC_CHAT_TYPE_MAILINGLIST;
|
||||
}
|
||||
|
||||
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 boolean isInBroadcast() {
|
||||
return getType() == DC_CHAT_TYPE_IN_BROADCAST;
|
||||
}
|
||||
|
||||
public boolean isOutBroadcast() {
|
||||
return getType() == DC_CHAT_TYPE_OUT_BROADCAST;
|
||||
}
|
||||
|
||||
// aliases and higher-level tools
|
||||
// working with raw c-data
|
||||
|
||||
public boolean isMultiUser() {
|
||||
int type = getType();
|
||||
return type != DC_CHAT_TYPE_SINGLE;
|
||||
}
|
||||
private long chatCPtr; // CAVE: the name is referenced in the JNI
|
||||
|
||||
public boolean isMailingList() {
|
||||
return getType() == DC_CHAT_TYPE_MAILINGLIST;
|
||||
}
|
||||
|
||||
public boolean isInBroadcast() {
|
||||
return getType() == DC_CHAT_TYPE_IN_BROADCAST;
|
||||
}
|
||||
public boolean isOutBroadcast() {
|
||||
return getType() == DC_CHAT_TYPE_OUT_BROADCAST;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,45 +2,64 @@ package com.b44t.messenger;
|
||||
|
||||
public class DcChatlist {
|
||||
|
||||
private int accountId;
|
||||
private int accountId;
|
||||
|
||||
public DcChatlist(int accountId, long chatlistCPtr) {
|
||||
this.accountId = accountId;
|
||||
this.chatlistCPtr = chatlistCPtr;
|
||||
}
|
||||
public DcChatlist(int accountId, long chatlistCPtr) {
|
||||
this.accountId = accountId;
|
||||
this.chatlistCPtr = chatlistCPtr;
|
||||
}
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefChatlistCPtr();
|
||||
chatlistCPtr = 0;
|
||||
}
|
||||
@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 class Item {
|
||||
public DcLot summary;
|
||||
public int msgId;
|
||||
public int chatId;
|
||||
}
|
||||
public native int getCnt();
|
||||
|
||||
public Item getItem(int index) {
|
||||
Item item = new Item();
|
||||
item.summary = getSummary(index, null);
|
||||
item.msgId = getMsgId(index);
|
||||
item.chatId = getChatId(index);
|
||||
return item;
|
||||
}
|
||||
public native int getChatId(int index);
|
||||
|
||||
// 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);
|
||||
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;
|
||||
public int msgId;
|
||||
public int chatId;
|
||||
}
|
||||
|
||||
public Item getItem(int index) {
|
||||
Item item = new Item();
|
||||
item.summary = getSummary(index, null);
|
||||
item.msgId = getMsgId(index);
|
||||
item.chatId = getChatId(index);
|
||||
return item;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -2,67 +2,82 @@ 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;
|
||||
public DcContact(long contactCPtr) {
|
||||
this.contactCPtr = contactCPtr;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefContactCPtr();
|
||||
contactCPtr = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null || !(other instanceof DcContact)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefContactCPtr();
|
||||
contactCPtr = 0;
|
||||
}
|
||||
DcContact that = (DcContact) other;
|
||||
return this.getId() == that.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null || !(other instanceof DcContact)) {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return getAddr();
|
||||
}
|
||||
|
||||
DcContact that = (DcContact) other;
|
||||
return this.getId()==that.getId();
|
||||
}
|
||||
public native int getId();
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.getId();
|
||||
}
|
||||
public native String getName();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getAddr();
|
||||
}
|
||||
public native String getAuthName();
|
||||
|
||||
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 boolean wasSeenRecently();
|
||||
public native boolean isBlocked ();
|
||||
public native boolean isVerified ();
|
||||
public native boolean isKeyContact ();
|
||||
public native int getVerifierId ();
|
||||
public native boolean isBot ();
|
||||
public native String getDisplayName();
|
||||
|
||||
// working with raw c-data
|
||||
private long contactCPtr; // CAVE: the name is referenced in the JNI
|
||||
private native void unrefContactCPtr();
|
||||
public native String getAddr();
|
||||
|
||||
public native String getProfileImage();
|
||||
|
||||
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();
|
||||
|
||||
// working with raw c-data
|
||||
private long contactCPtr; // CAVE: the name is referenced in the JNI
|
||||
|
||||
private native void unrefContactCPtr();
|
||||
}
|
||||
|
||||
@@ -2,288 +2,415 @@ 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";
|
||||
private static final String CONFIG_ACCOUNT_ENABLED = "ui.enabled";
|
||||
private static final String CONFIG_MUTE_MENTIONS_IF_MUTED = "ui.mute_mentions_if_muted";
|
||||
|
||||
// when using DcAccounts, use Rpc.addAccount() instead
|
||||
public DcContext(String osName, String dbfile) {
|
||||
contextCPtr = createContextCPtr(osName, dbfile);
|
||||
// when using DcAccounts, use Rpc.addAccount() instead
|
||||
public DcContext(String osName, String dbfile) {
|
||||
contextCPtr = createContextCPtr(osName, dbfile);
|
||||
}
|
||||
|
||||
public DcContext(long contextCPtr) {
|
||||
this.contextCPtr = contextCPtr;
|
||||
}
|
||||
|
||||
public boolean isOk() {
|
||||
return contextCPtr != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
if (contextCPtr != 0) {
|
||||
unrefContextCPtr();
|
||||
contextCPtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public DcContext(long contextCPtr) {
|
||||
this.contextCPtr = contextCPtr;
|
||||
public native int getAccountId();
|
||||
|
||||
// when using DcAccounts, use DcAccounts.getEventEmitter() instead
|
||||
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();
|
||||
|
||||
// when using DcAccounts, use DcAccounts.startIo() instead
|
||||
public native void startIo();
|
||||
|
||||
// when using DcAccounts, use DcAccounts.stopIo() instead
|
||||
public native void stopIo();
|
||||
|
||||
// when using DcAccounts, use DcAccounts.maybeNetwork() instead
|
||||
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 boolean isOk() {
|
||||
return contextCPtr != 0;
|
||||
}
|
||||
public native String getInfo();
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
if (contextCPtr != 0) {
|
||||
unrefContextCPtr();
|
||||
contextCPtr = 0;
|
||||
}
|
||||
}
|
||||
public native int getConnectivity();
|
||||
|
||||
public native int getAccountId ();
|
||||
public native String getConnectivityHtml();
|
||||
|
||||
// when using DcAccounts, use DcAccounts.getEventEmitter() instead
|
||||
public DcEventEmitter getEventEmitter () { return new DcEventEmitter(getEventEmitterCPtr()); }
|
||||
public native String initiateKeyTransfer();
|
||||
|
||||
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 imex(int what, String dir);
|
||||
|
||||
// when using DcAccounts, use DcAccounts.startIo() instead
|
||||
public native void startIo ();
|
||||
public native String imexHasBackup(String dir);
|
||||
|
||||
// when using DcAccounts, use DcAccounts.stopIo() instead
|
||||
public native void stopIo ();
|
||||
public DcBackupProvider newBackupProvider() {
|
||||
return new DcBackupProvider(newBackupProviderCPtr());
|
||||
}
|
||||
|
||||
// when using DcAccounts, use DcAccounts.maybeNetwork() instead
|
||||
public native void maybeNetwork ();
|
||||
public native boolean receiveBackup(String qr);
|
||||
|
||||
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 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 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 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 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 int initWebxdcIntegration(int chat_id);
|
||||
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 native boolean isSendingLocationsToChat(int chat_id);
|
||||
public DcProvider getProviderFromEmailWithDns (String email) { long cptr = getProviderFromEmailWithDnsCPtr(email); return cptr!=0 ? new DcProvider(cptr) : null; }
|
||||
public native boolean mayBeValidAddr(String addr);
|
||||
|
||||
public boolean isEnabled() {
|
||||
return !"0".equals(getConfig(CONFIG_ACCOUNT_ENABLED));
|
||||
}
|
||||
public native int lookupContactIdByAddr(String addr);
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
setConfigInt(CONFIG_ACCOUNT_ENABLED, enabled? 1 : 0);
|
||||
if (enabled) {
|
||||
startIo();
|
||||
} else {
|
||||
stopIo();
|
||||
}
|
||||
}
|
||||
public native int[] getContacts(int flags, String query);
|
||||
|
||||
public boolean isMentionsEnabled() {
|
||||
return getConfigInt(CONFIG_MUTE_MENTIONS_IF_MUTED) != 1;
|
||||
}
|
||||
public native int[] getBlockedContacts();
|
||||
|
||||
public void setMentionsEnabled(boolean enabled) {
|
||||
setConfigInt(CONFIG_MUTE_MENTIONS_IF_MUTED, enabled? 0 : 1);
|
||||
}
|
||||
public DcContact getContact(int contact_id) {
|
||||
return new DcContact(getContactCPtr(contact_id));
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
String displayname = getConfig("displayname");
|
||||
if (displayname.isEmpty()) {
|
||||
displayname = getContact(DcContact.DC_CONTACT_ID_SELF).getAddr();
|
||||
}
|
||||
return displayname;
|
||||
}
|
||||
public native int createContact(String name, String addr);
|
||||
|
||||
public boolean isChatmail() {
|
||||
return getConfigInt("is_chatmail") == 1;
|
||||
}
|
||||
public native void blockContact(int id, int block);
|
||||
|
||||
public boolean isMuted() {
|
||||
return getConfigInt("is_muted") == 1;
|
||||
}
|
||||
public native String getContactEncrInfo(int contact_id);
|
||||
|
||||
public void setMuted(boolean muted) {
|
||||
setConfigInt("is_muted", muted? 1 : 0);
|
||||
}
|
||||
public native boolean deleteContact(int id);
|
||||
|
||||
public void restartIo() {
|
||||
if (!isEnabled()) return;
|
||||
stopIo();
|
||||
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 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 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 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 int initWebxdcIntegration(int chat_id);
|
||||
|
||||
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 native boolean isSendingLocationsToChat(int chat_id);
|
||||
|
||||
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);
|
||||
if (enabled) {
|
||||
startIo();
|
||||
} else {
|
||||
stopIo();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if at least one chat has location streaming enabled
|
||||
*/
|
||||
public native boolean setLocation (float latitude, float longitude, float accuracy);
|
||||
public boolean isMentionsEnabled() {
|
||||
return getConfigInt(CONFIG_MUTE_MENTIONS_IF_MUTED) != 1;
|
||||
}
|
||||
|
||||
// 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 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);
|
||||
private native long newBackupProviderCPtr();
|
||||
public void setMentionsEnabled(boolean enabled) {
|
||||
setConfigInt(CONFIG_MUTE_MENTIONS_IF_MUTED, enabled ? 0 : 1);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
String displayname = getConfig("displayname");
|
||||
if (displayname.isEmpty()) {
|
||||
displayname = getConfig("addr");
|
||||
}
|
||||
return displayname;
|
||||
}
|
||||
|
||||
public boolean isChatmail() {
|
||||
return getConfigInt("is_chatmail") == 1;
|
||||
}
|
||||
|
||||
public boolean isMuted() {
|
||||
return getConfigInt("is_muted") == 1;
|
||||
}
|
||||
|
||||
public void setMuted(boolean muted) {
|
||||
setConfigInt("is_muted", muted ? 1 : 0);
|
||||
}
|
||||
|
||||
public void restartIo() {
|
||||
if (!isEnabled()) return;
|
||||
stopIo();
|
||||
startIo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if at least one chat has location streaming enabled
|
||||
*/
|
||||
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 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);
|
||||
|
||||
private native long newBackupProviderCPtr();
|
||||
}
|
||||
|
||||
@@ -2,24 +2,31 @@ package com.b44t.messenger;
|
||||
|
||||
public class DcEvent {
|
||||
|
||||
public DcEvent(long eventCPtr) {
|
||||
this.eventCPtr = eventCPtr;
|
||||
}
|
||||
public DcEvent(long eventCPtr) {
|
||||
this.eventCPtr = eventCPtr;
|
||||
}
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefEventCPtr();
|
||||
eventCPtr = 0;
|
||||
}
|
||||
@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 byte[] getData2Blob();
|
||||
public native int getAccountId();
|
||||
public native int getId();
|
||||
|
||||
// working with raw c-data
|
||||
private long eventCPtr; // CAVE: the name is referenced in the JNI
|
||||
private native void unrefEventCPtr();
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -20,8 +20,11 @@ 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 long eventChannelCPtr; // CAVE: the name is referenced in the JNI
|
||||
|
||||
private native long createEventChannelCPtr();
|
||||
|
||||
private native void unrefEventChannelCPtr();
|
||||
|
||||
private native long getEventEmitterCPtr();
|
||||
}
|
||||
|
||||
@@ -2,23 +2,26 @@ package com.b44t.messenger;
|
||||
|
||||
public class DcEventEmitter {
|
||||
|
||||
public DcEventEmitter(long eventEmitterCPtr) {
|
||||
this.eventEmitterCPtr = eventEmitterCPtr;
|
||||
}
|
||||
public DcEventEmitter(long eventEmitterCPtr) {
|
||||
this.eventEmitterCPtr = eventEmitterCPtr;
|
||||
}
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefEventEmitterCPtr();
|
||||
eventEmitterCPtr = 0;
|
||||
}
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefEventEmitterCPtr();
|
||||
eventEmitterCPtr = 0;
|
||||
}
|
||||
|
||||
public DcEvent getNextEvent () {
|
||||
long eventCPtr = getNextEventCPtr();
|
||||
return eventCPtr == 0 ? null : new DcEvent(eventCPtr);
|
||||
}
|
||||
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 void unrefEventEmitterCPtr();
|
||||
// working with raw c-data
|
||||
private long eventEmitterCPtr; // CAVE: the name is referenced in the JNI
|
||||
|
||||
private native long getNextEventCPtr();
|
||||
|
||||
private native void unrefEventEmitterCPtr();
|
||||
}
|
||||
|
||||
@@ -2,20 +2,23 @@ package com.b44t.messenger;
|
||||
|
||||
public class DcJsonrpcInstance {
|
||||
|
||||
public DcJsonrpcInstance(long jsonrpcInstanceCPtr) {
|
||||
this.jsonrpcInstanceCPtr = jsonrpcInstanceCPtr;
|
||||
}
|
||||
public DcJsonrpcInstance(long jsonrpcInstanceCPtr) {
|
||||
this.jsonrpcInstanceCPtr = jsonrpcInstanceCPtr;
|
||||
}
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefJsonrpcInstanceCPtr();
|
||||
jsonrpcInstanceCPtr = 0;
|
||||
}
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefJsonrpcInstanceCPtr();
|
||||
jsonrpcInstanceCPtr = 0;
|
||||
}
|
||||
|
||||
public native void request(String request);
|
||||
public native String getNextResponse();
|
||||
public native void request(String request);
|
||||
|
||||
// working with raw c-data
|
||||
private long jsonrpcInstanceCPtr; // CAVE: the name is referenced in the JNI
|
||||
private native void unrefJsonrpcInstanceCPtr();
|
||||
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;
|
||||
}
|
||||
public DcLot(long lotCPtr) {
|
||||
this.lotCPtr = lotCPtr;
|
||||
}
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefLotCPtr();
|
||||
lotCPtr = 0;
|
||||
}
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefLotCPtr();
|
||||
lotCPtr = 0;
|
||||
}
|
||||
|
||||
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 getText1();
|
||||
|
||||
// working with raw c-data
|
||||
private long lotCPtr; // CAVE: the name is referenced in the JNI
|
||||
private native void unrefLotCPtr();
|
||||
public native int getText1Meaning();
|
||||
|
||||
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,266 +1,332 @@
|
||||
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_AUTOCRYPT_SETUP_MESSAGE = 6;
|
||||
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_NO_ID = 0;
|
||||
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;
|
||||
public static final int DC_VIDEOCHATTYPE_UNKNOWN = 0;
|
||||
public static final int DC_VIDEOCHATTYPE_BASICWEBRTC = 1;
|
||||
|
||||
private static final String TAG = DcMsg.class.getSimpleName();
|
||||
private static final String TAG = DcMsg.class.getSimpleName();
|
||||
|
||||
public DcMsg(DcContext context, int viewtype) {
|
||||
msgCPtr = context.createMsgCPtr(viewtype);
|
||||
public DcMsg(DcContext context, int viewtype) {
|
||||
msgCPtr = context.createMsgCPtr(viewtype);
|
||||
}
|
||||
|
||||
public DcMsg(long msgCPtr) {
|
||||
this.msgCPtr = msgCPtr;
|
||||
}
|
||||
|
||||
public boolean isOk() {
|
||||
return msgCPtr != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefMsgCPtr();
|
||||
msgCPtr = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null || !(other instanceof DcMsg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public DcMsg(long msgCPtr) {
|
||||
this.msgCPtr = msgCPtr;
|
||||
}
|
||||
DcMsg that = (DcMsg) other;
|
||||
return this.getId() == that.getId() && this.getId() != 0;
|
||||
}
|
||||
|
||||
public boolean isOk() {
|
||||
return msgCPtr != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefMsgCPtr();
|
||||
msgCPtr = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null || !(other instanceof DcMsg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DcMsg that = (DcMsg) other;
|
||||
return this.getId()==that.getId() && this.getId()!=0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
int msgId = msg.getId();
|
||||
for (int i = 0; i < msgs.length; i++) {
|
||||
if (msgs[i] == msgId) {
|
||||
startingPosition = msgs.length - 1 - i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return startingPosition;
|
||||
}
|
||||
|
||||
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 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 () {
|
||||
try {
|
||||
String json = getWebxdcInfoJson();
|
||||
if (json != null && !json.isEmpty()) return new JSONObject(json);
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
/** 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;
|
||||
int msgId = msg.getId();
|
||||
for (int i = 0; i < msgs.length; i++) {
|
||||
if (msgs[i] == msgId) {
|
||||
startingPosition = msgs.length - 1 - i;
|
||||
break;
|
||||
}
|
||||
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 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 String getOverrideSenderName();
|
||||
public native boolean isEdited ();
|
||||
return startingPosition;
|
||||
}
|
||||
|
||||
public String getSenderName(DcContact dcContact) {
|
||||
String overrideName = getOverrideSenderName();
|
||||
if (overrideName != null) {
|
||||
return "~" + overrideName;
|
||||
} else {
|
||||
return dcContact.getDisplayName();
|
||||
}
|
||||
}
|
||||
public native int getId();
|
||||
|
||||
public DcMsg getQuotedMsg () {
|
||||
long cPtr = getQuotedMsgCPtr();
|
||||
return cPtr != 0 ? new DcMsg(cPtr) : null;
|
||||
}
|
||||
public native String getText();
|
||||
|
||||
public DcMsg getParent() {
|
||||
long cPtr = getParentCPtr();
|
||||
return cPtr != 0 ? new DcMsg(cPtr) : null;
|
||||
}
|
||||
public native String getSubject();
|
||||
|
||||
public native int getOriginalMsgId ();
|
||||
public native int getSavedMsgId ();
|
||||
public native long getTimestamp();
|
||||
|
||||
public boolean canSave() {
|
||||
// saving info-messages out of context results in confusion, see https://github.com/deltachat/deltachat-ios/issues/2567
|
||||
return !isInfo();
|
||||
}
|
||||
public native long getSortTimestamp();
|
||||
|
||||
public File getFileAsFile() {
|
||||
if(getFile()==null)
|
||||
throw new AssertionError("expected a file to be present.");
|
||||
return new File(getFile());
|
||||
}
|
||||
public native boolean hasDeviatingTimestamp();
|
||||
|
||||
// aliases and higher-level tools
|
||||
public static int[] msgSetToIds(final Set<DcMsg> dcMsgs) {
|
||||
if (dcMsgs == null) {
|
||||
return new int[0];
|
||||
}
|
||||
int[] ids = new int[dcMsgs.size()];
|
||||
int i = 0;
|
||||
for (DcMsg dcMsg : dcMsgs) {
|
||||
ids[i++] = dcMsg.getId();
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
public native boolean hasLocation();
|
||||
|
||||
public boolean isOutgoing() {
|
||||
return getFromId() == DcContact.DC_CONTACT_ID_SELF;
|
||||
}
|
||||
private native int getViewType();
|
||||
|
||||
public String getDisplayBody() {
|
||||
return getText();
|
||||
}
|
||||
public int getType() {
|
||||
return getDownloadState() == DC_DOWNLOAD_DONE ? getViewType() : DC_MSG_TEXT;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return getText();
|
||||
}
|
||||
public native int getInfoType();
|
||||
|
||||
public long getDateReceived() {
|
||||
return getTimestamp();
|
||||
}
|
||||
public native int getInfoContactId();
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
public native int getState();
|
||||
|
||||
public native int getDownloadState();
|
||||
|
||||
// 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 ();
|
||||
};
|
||||
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() {
|
||||
try {
|
||||
String json = getWebxdcInfoJson();
|
||||
if (json != null && !json.isEmpty()) return new JSONObject(json);
|
||||
} 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 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 String getOverrideSenderName();
|
||||
|
||||
public native boolean isEdited();
|
||||
|
||||
public String getSenderName(DcContact dcContact) {
|
||||
String overrideName = getOverrideSenderName();
|
||||
if (overrideName != null) {
|
||||
return "~" + overrideName;
|
||||
} else {
|
||||
return dcContact.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
public DcMsg getQuotedMsg() {
|
||||
long cPtr = getQuotedMsgCPtr();
|
||||
return cPtr != 0 ? new DcMsg(cPtr) : null;
|
||||
}
|
||||
|
||||
public DcMsg getParent() {
|
||||
long cPtr = getParentCPtr();
|
||||
return cPtr != 0 ? new DcMsg(cPtr) : null;
|
||||
}
|
||||
|
||||
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
|
||||
return !isInfo();
|
||||
}
|
||||
|
||||
public File getFileAsFile() {
|
||||
if (getFile() == null) throw new AssertionError("expected a file to be present.");
|
||||
return new File(getFile());
|
||||
}
|
||||
|
||||
// aliases and higher-level tools
|
||||
public static int[] msgSetToIds(final Set<DcMsg> dcMsgs) {
|
||||
if (dcMsgs == null) {
|
||||
return new int[0];
|
||||
}
|
||||
int[] ids = new int[dcMsgs.size()];
|
||||
int i = 0;
|
||||
for (DcMsg dcMsg : dcMsgs) {
|
||||
ids[i++] = dcMsg.getId();
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
public boolean isOutgoing() {
|
||||
return getFromId() == DcContact.DC_CONTACT_ID_SELF;
|
||||
}
|
||||
|
||||
public String getDisplayBody() {
|
||||
return getText();
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return getText();
|
||||
}
|
||||
|
||||
public long getDateReceived() {
|
||||
return getTimestamp();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
public DcProvider(long providerCPtr) {
|
||||
this.providerCPtr = providerCPtr;
|
||||
}
|
||||
|
||||
@Override protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
unrefProviderCPtr();
|
||||
providerCPtr = 0;
|
||||
}
|
||||
@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();
|
||||
|
||||
// working with raw c-data
|
||||
private long providerCPtr; // CAVE: the name is referenced in the JNI
|
||||
private native void unrefProviderCPtr();
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -19,4 +19,4 @@ public class FFITransport extends BaseTransport {
|
||||
protected String getResponse() {
|
||||
return dcJsonrpcInstance.getNextResponse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1292
-1284
File diff suppressed because it is too large
Load Diff
@@ -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,38 +33,37 @@ 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
|
||||
{
|
||||
implements DcEventCenter.DcEventDelegate {
|
||||
private static final String TAG = AllMediaActivity.class.getSimpleName();
|
||||
|
||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||
public static final String CONTACT_ID_EXTRA = "contact_id";
|
||||
public static final String FORCE_GALLERY = "force_gallery";
|
||||
public static final String FORCE_GALLERY = "force_gallery";
|
||||
|
||||
static class TabData {
|
||||
final int title;
|
||||
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;
|
||||
private int contactId;
|
||||
private DcContext dcContext;
|
||||
private int chatId;
|
||||
private int contactId;
|
||||
|
||||
private final ArrayList<TabData> tabs = new ArrayList<>();
|
||||
private Toolbar toolbar;
|
||||
private TabLayout tabLayout;
|
||||
private ViewPager viewPager;
|
||||
private Toolbar toolbar;
|
||||
private TabLayout tabLayout;
|
||||
private ViewPager viewPager;
|
||||
|
||||
private @Nullable MediaController mediaController;
|
||||
private ListenableFuture<MediaController> mediaControllerFuture;
|
||||
@@ -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,47 +123,44 @@ 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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
this.viewPager = ViewUtil.findById(this, R.id.pager);
|
||||
this.toolbar = ViewUtil.findById(this, R.id.toolbar);
|
||||
this.toolbar = ViewUtil.findById(this, R.id.toolbar);
|
||||
this.tabLayout = ViewUtil.findById(this, R.id.tab_layout);
|
||||
}
|
||||
|
||||
private void initializeMediaController() {
|
||||
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()
|
||||
);
|
||||
playbackViewModel.setMediaController(mediaController);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error connecting to audio playback service", e);
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(this));
|
||||
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());
|
||||
playbackViewModel.setMediaController(mediaController);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error connecting to audio playback service", e);
|
||||
}
|
||||
},
|
||||
ContextCompat.getMainExecutor(this));
|
||||
}
|
||||
|
||||
private void addActivityContext(Bundle extras, String activityClassName) {
|
||||
@@ -176,13 +173,13 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
SessionCommand updateContextCommand =
|
||||
new SessionCommand("UPDATE_ACTIVITY_CONTEXT", Bundle.EMPTY);
|
||||
new SessionCommand("UPDATE_ACTIVITY_CONTEXT", Bundle.EMPTY);
|
||||
|
||||
mediaController.sendCustomCommand(updateContextCommand, commandArgs);
|
||||
}
|
||||
|
||||
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,31 +22,27 @@ 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;
|
||||
private final ItemClickListener itemClickListener;
|
||||
private final Set<DcMsg> selected;
|
||||
private final Context context;
|
||||
private final ItemClickListener itemClickListener;
|
||||
private final Set<DcMsg> selected;
|
||||
|
||||
private BucketedThreadMedia media;
|
||||
private AudioPlaybackViewModel playbackViewModel;
|
||||
private BucketedThreadMedia media;
|
||||
private AudioPlaybackViewModel playbackViewModel;
|
||||
|
||||
private static class ViewHolder extends StickyHeaderGridAdapter.ItemViewHolder {
|
||||
private final DocumentView documentView;
|
||||
private final AudioView audioView;
|
||||
private final WebxdcView webxdcView;
|
||||
private final TextView date;
|
||||
private final AudioView audioView;
|
||||
private final WebxdcView webxdcView;
|
||||
private final TextView date;
|
||||
|
||||
public ViewHolder(View v) {
|
||||
super(v);
|
||||
documentView = v.findViewById(R.id.document_view);
|
||||
audioView = v.findViewById(R.id.audio_view);
|
||||
webxdcView = v.findViewById(R.id.webxdc_view);
|
||||
date = v.findViewById(R.id.date);
|
||||
documentView = v.findViewById(R.id.document_view);
|
||||
audioView = v.findViewById(R.id.audio_view);
|
||||
webxdcView = v.findViewById(R.id.webxdc_view);
|
||||
date = v.findViewById(R.id.date);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,14 +55,12 @@ class AllMediaDocumentsAdapter extends StickyHeaderGridAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
AllMediaDocumentsAdapter(@NonNull Context context,
|
||||
BucketedThreadMedia media,
|
||||
ItemClickListener clickListener)
|
||||
{
|
||||
this.context = context;
|
||||
this.media = media;
|
||||
AllMediaDocumentsAdapter(
|
||||
@NonNull Context context, BucketedThreadMedia media, ItemClickListener clickListener) {
|
||||
this.context = context;
|
||||
this.media = media;
|
||||
this.itemClickListener = clickListener;
|
||||
this.selected = new HashSet<>();
|
||||
this.selected = new HashSet<>();
|
||||
}
|
||||
|
||||
public void setMedia(BucketedThreadMedia media) {
|
||||
@@ -78,25 +72,30 @@ 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);
|
||||
DcMsg dcMsg = media.get(section, offset);
|
||||
Slide slide = MediaUtil.getSlideForMsg(context, dcMsg);
|
||||
ViewHolder viewHolder = ((ViewHolder) itemViewHolder);
|
||||
DcMsg dcMsg = media.get(section, offset);
|
||||
Slide slide = MediaUtil.getSlideForMsg(context, dcMsg);
|
||||
|
||||
if (slide != null && slide.hasAudio()) {
|
||||
viewHolder.documentView.setVisibility(View.GONE);
|
||||
@@ -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";
|
||||
@@ -50,7 +44,7 @@ public class AllMediaDocumentsFragment
|
||||
private int viewtype1;
|
||||
private int viewtype2;
|
||||
|
||||
protected int chatId;
|
||||
protected int chatId;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
@@ -64,21 +58,23 @@ 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);
|
||||
this.noMedia = ViewUtil.findById(view, R.id.no_documents);
|
||||
this.gridManager = new StickyHeaderGridLayoutManager(1);
|
||||
this.noMedia = ViewUtil.findById(view, R.id.no_documents);
|
||||
this.gridManager = new StickyHeaderGridLayoutManager(1);
|
||||
|
||||
// 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,38 +5,34 @@ 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;
|
||||
private final GlideRequests glideRequests;
|
||||
private final ItemClickListener itemClickListener;
|
||||
private final Set<DcMsg> selected;
|
||||
private final Context context;
|
||||
private final GlideRequests glideRequests;
|
||||
private final ItemClickListener itemClickListener;
|
||||
private final Set<DcMsg> selected;
|
||||
|
||||
private BucketedThreadMedia media;
|
||||
private BucketedThreadMedia media;
|
||||
|
||||
private static class ViewHolder extends StickyHeaderGridAdapter.ItemViewHolder {
|
||||
final ThumbnailView imageView;
|
||||
final View selectedIndicator;
|
||||
final View selectedIndicator;
|
||||
|
||||
ViewHolder(View v) {
|
||||
super(v);
|
||||
imageView = v.findViewById(R.id.image);
|
||||
imageView = v.findViewById(R.id.image);
|
||||
selectedIndicator = v.findViewById(R.id.selected_indicator);
|
||||
}
|
||||
}
|
||||
@@ -50,16 +46,16 @@ class AllMediaGalleryAdapter extends StickyHeaderGridAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
AllMediaGalleryAdapter(@NonNull Context context,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
BucketedThreadMedia media,
|
||||
ItemClickListener clickListener)
|
||||
{
|
||||
this.context = context;
|
||||
this.glideRequests = glideRequests;
|
||||
this.media = media;
|
||||
AllMediaGalleryAdapter(
|
||||
@NonNull Context context,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
BucketedThreadMedia media,
|
||||
ItemClickListener clickListener) {
|
||||
this.context = context;
|
||||
this.glideRequests = glideRequests;
|
||||
this.media = media;
|
||||
this.itemClickListener = clickListener;
|
||||
this.selected = new HashSet<>();
|
||||
this.selected = new HashSet<>();
|
||||
}
|
||||
|
||||
public void setMedia(BucketedThreadMedia media) {
|
||||
@@ -67,36 +63,42 @@ 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;
|
||||
Slide slide = MediaUtil.getSlideForMsg(context, mediaRecord);
|
||||
DcMsg mediaRecord = media.get(section, offset);
|
||||
ThumbnailView thumbnailView = ((ViewHolder) viewHolder).imageView;
|
||||
View selectedIndicator = ((ViewHolder) viewHolder).selectedIndicator;
|
||||
Slide slide = MediaUtil.getSlideForMsg(context, mediaRecord);
|
||||
|
||||
if (slide != null) {
|
||||
thumbnailView.setImageResource(glideRequests, slide);
|
||||
}
|
||||
|
||||
thumbnailView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord));
|
||||
thumbnailView.setOnLongClickListener(view -> {
|
||||
itemClickListener.onMediaLongClicked(mediaRecord);
|
||||
return true;
|
||||
});
|
||||
thumbnailView.setOnLongClickListener(
|
||||
view -> {
|
||||
itemClickListener.onMediaLongClicked(mediaRecord);
|
||||
return true;
|
||||
});
|
||||
|
||||
selectedIndicator.setVisibility(selected.contains(mediaRecord) ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -46,7 +40,7 @@ public class AllMediaGalleryFragment
|
||||
private StickyHeaderGridLayoutManager gridManager;
|
||||
private final ActionModeCallback actionModeCallback = new ActionModeCallback();
|
||||
|
||||
private int chatId;
|
||||
private int chatId;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
@@ -58,20 +52,23 @@ 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);
|
||||
this.noMedia = ViewUtil.findById(view, R.id.no_images);
|
||||
this.gridManager = new StickyHeaderGridLayoutManager(getCols());
|
||||
this.noMedia = ViewUtil.findById(view, R.id.no_images);
|
||||
this.gridManager = new StickyHeaderGridLayoutManager(getCols());
|
||||
|
||||
// add padding to avoid content hidden behind system bars
|
||||
ViewUtil.applyWindowInsets(recyclerView, true, false, true, true);
|
||||
|
||||
this.recyclerView.setAdapter(new AllMediaGalleryAdapter(getContext(),
|
||||
GlideApp.with(this),
|
||||
new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()),
|
||||
this));
|
||||
this.recyclerView.setAdapter(
|
||||
new AllMediaGalleryAdapter(
|
||||
getContext(),
|
||||
GlideApp.with(this),
|
||||
new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()),
|
||||
this));
|
||||
this.recyclerView.setLayoutManager(gridManager);
|
||||
this.recyclerView.setHasFixedSize(true);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,24 +54,22 @@ 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
|
||||
{
|
||||
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_APPEARANCE = "preference_category_appearance";
|
||||
private static final String PREFERENCE_CATEGORY_CHATS = "preference_category_chats";
|
||||
private static final String PREFERENCE_CATEGORY_PRIVACY = "preference_category_privacy";
|
||||
private static final String PREFERENCE_CATEGORY_MULTIDEVICE = "preference_category_multidevice";
|
||||
private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
|
||||
private static final String PREFERENCE_CATEGORY_CONNECTIVITY = "preference_category_connectivity";
|
||||
private static final String PREFERENCE_CATEGORY_DONATE = "preference_category_donate";
|
||||
private static final String PREFERENCE_CATEGORY_HELP = "preference_category_help";
|
||||
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_APPEARANCE = "preference_category_appearance";
|
||||
private static final String PREFERENCE_CATEGORY_CHATS = "preference_category_chats";
|
||||
private static final String PREFERENCE_CATEGORY_PRIVACY = "preference_category_privacy";
|
||||
private static final String PREFERENCE_CATEGORY_MULTIDEVICE = "preference_category_multidevice";
|
||||
private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
|
||||
private static final String PREFERENCE_CATEGORY_CONNECTIVITY = "preference_category_connectivity";
|
||||
private static final String PREFERENCE_CATEGORY_DONATE = "preference_category_donate";
|
||||
private static final String PREFERENCE_CATEGORY_HELP = "preference_category_help";
|
||||
|
||||
public static final int REQUEST_CODE_SET_BACKGROUND = 11;
|
||||
public static final int REQUEST_CODE_SET_BACKGROUND = 11;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle, boolean ready) {
|
||||
@@ -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,34 +110,51 @@ 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));
|
||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE));
|
||||
this.findPreference(PREFERENCE_CATEGORY_CHATS)
|
||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_CHATS));
|
||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_CHATS));
|
||||
this.findPreference(PREFERENCE_CATEGORY_PRIVACY)
|
||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_PRIVACY));
|
||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_PRIVACY));
|
||||
this.findPreference(PREFERENCE_CATEGORY_MULTIDEVICE)
|
||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_MULTIDEVICE));
|
||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_MULTIDEVICE));
|
||||
this.findPreference(PREFERENCE_CATEGORY_ADVANCED)
|
||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
|
||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
|
||||
|
||||
this.findPreference(PREFERENCE_CATEGORY_DONATE)
|
||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DONATE));
|
||||
@@ -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()));
|
||||
}
|
||||
@@ -219,63 +228,76 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
||||
Fragment fragment = null;
|
||||
|
||||
switch (category) {
|
||||
case PREFERENCE_CATEGORY_NOTIFICATIONS:
|
||||
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())))
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_CONNECTIVITY:
|
||||
startActivity(new Intent(getActivity(), ConnectivityActivity.class));
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_APPEARANCE:
|
||||
fragment = new AppearancePreferenceFragment();
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_CHATS:
|
||||
fragment = new ChatsPreferenceFragment();
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_PRIVACY:
|
||||
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)) {
|
||||
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())
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
;
|
||||
}
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_ADVANCED:
|
||||
fragment = new AdvancedPreferenceFragment();
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_DONATE:
|
||||
IntentUtils.showInBrowser(requireActivity(), "https://arcanechat.me/#contribute");
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_HELP:
|
||||
startActivity(new Intent(getActivity(), LocalHelpActivity.class));
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
case PREFERENCE_CATEGORY_NOTIFICATIONS:
|
||||
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())))
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_CONNECTIVITY:
|
||||
startActivity(new Intent(getActivity(), ConnectivityActivity.class));
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_APPEARANCE:
|
||||
fragment = new AppearancePreferenceFragment();
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_CHATS:
|
||||
fragment = new ChatsPreferenceFragment();
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_PRIVACY:
|
||||
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),
|
||||
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())
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
;
|
||||
}
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_ADVANCED:
|
||||
fragment = new AdvancedPreferenceFragment();
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_DONATE:
|
||||
IntentUtils.showInBrowser(requireActivity(), "https://arcanechat.me/#contribute");
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_HELP:
|
||||
startActivity(new Intent(getActivity(), LocalHelpActivity.class));
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
if (fragment != null) {
|
||||
Bundle args = new Bundle();
|
||||
fragment.setArguments(args);
|
||||
|
||||
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
|
||||
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
|
||||
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
||||
fragmentTransaction.replace(R.id.fragment, fragment);
|
||||
fragmentTransaction.addToBackStack(null);
|
||||
@@ -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,14 +13,11 @@ 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();
|
||||
@@ -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) {
|
||||
ViewConfiguration config = ViewConfiguration.get(this);
|
||||
Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
|
||||
if (menuKeyField != null) {
|
||||
menuKeyField.setAccessible(true);
|
||||
menuKeyField.setBoolean(config, false);
|
||||
}
|
||||
@@ -97,21 +92,18 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
|
||||
} 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(
|
||||
!visible); // if search is shown, other items are hidden - and the other way round
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,9 +111,10 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
fragment.setArguments(args);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(target, fragment)
|
||||
.commitAllowingStateLoss();
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(target, fragment)
|
||||
.commitAllowingStateLoss();
|
||||
return fragment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,42 +6,37 @@ 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;
|
||||
protected DcChat dcChat;
|
||||
protected TextView bodyText;
|
||||
protected DcMsg messageRecord;
|
||||
protected DcChat dcChat;
|
||||
protected TextView bodyText;
|
||||
|
||||
protected final Context context;
|
||||
protected final DcContext dcContext;
|
||||
protected final Context context;
|
||||
protected final DcContext dcContext;
|
||||
protected final Rpc rpc;
|
||||
protected Recipient conversationRecipient;
|
||||
protected Recipient conversationRecipient;
|
||||
|
||||
protected @NonNull Set<DcMsg> batchSelected = new HashSet<>();
|
||||
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,32 +45,32 @@ public abstract class BaseConversationItem extends LinearLayout
|
||||
this.rpc = DcHelper.getRpc(context);
|
||||
}
|
||||
|
||||
protected void bindPartial(@NonNull DcMsg messageRecord,
|
||||
@NonNull DcChat dcChat,
|
||||
@NonNull Set<DcMsg> batchSelected,
|
||||
boolean pulseHighlight,
|
||||
@NonNull Recipient conversationRecipient)
|
||||
{
|
||||
this.messageRecord = messageRecord;
|
||||
this.dcChat = dcChat;
|
||||
this.batchSelected = batchSelected;
|
||||
this.conversationRecipient = conversationRecipient;
|
||||
protected void bindPartial(
|
||||
@NonNull DcMsg messageRecord,
|
||||
@NonNull DcChat dcChat,
|
||||
@NonNull Set<DcMsg> batchSelected,
|
||||
boolean pulseHighlight,
|
||||
@NonNull Recipient conversationRecipient) {
|
||||
this.messageRecord = messageRecord;
|
||||
this.dcChat = dcChat;
|
||||
this.batchSelected = batchSelected;
|
||||
this.conversationRecipient = conversationRecipient;
|
||||
setInteractionState(messageRecord, pulseHighlight);
|
||||
}
|
||||
|
||||
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)) {
|
||||
final TypedArray attrs = context.obtainStyledAttributes(attributes);
|
||||
final TypedArray attrs = context.obtainStyledAttributes(attributes);
|
||||
ViewUtil.setBackground(this, attrs.getDrawable(0));
|
||||
attrs.recycle();
|
||||
setSelected(true);
|
||||
} else if (pulseHighlight) {
|
||||
final TypedArray attrs = context.obtainStyledAttributes(attributes);
|
||||
final TypedArray attrs = context.obtainStyledAttributes(attributes);
|
||||
ViewUtil.setBackground(this, attrs.getDrawable(1));
|
||||
attrs.recycle();
|
||||
setSelected(true);
|
||||
@@ -92,15 +87,16 @@ public abstract class BaseConversationItem extends LinearLayout
|
||||
|
||||
protected boolean shouldInterceptClicks(DcMsg messageRecord) {
|
||||
return batchSelected.isEmpty()
|
||||
&& (messageRecord.isFailed()
|
||||
|| messageRecord.getInfoType() == DcMsg.DC_INFO_CHAT_E2EE
|
||||
|| messageRecord.getInfoType() == DcMsg.DC_INFO_PROTECTION_ENABLED
|
||||
|| messageRecord.getInfoType() == DcMsg.DC_INFO_INVALID_UNENCRYPTED_MAIL);
|
||||
&& (messageRecord.isFailed()
|
||||
|| messageRecord.getInfoType() == DcMsg.DC_INFO_CHAT_E2EE
|
||||
|| messageRecord.getInfoType() == DcMsg.DC_INFO_PROTECTION_ENABLED
|
||||
|| messageRecord.getInfoType() == DcMsg.DC_INFO_INVALID_UNENCRYPTED_MAIL);
|
||||
}
|
||||
|
||||
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,14 +1,14 @@
|
||||
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> {
|
||||
protected final Set<Long> batchSet = Collections.synchronizedSet(new HashSet<Long>());
|
||||
protected boolean batchMode = false;
|
||||
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;
|
||||
|
||||
public abstract void selectAllThreads();
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ 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 +26,36 @@ import androidx.core.content.pm.ShortcutInfoCompat;
|
||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
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 {
|
||||
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 +70,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,36 +89,54 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
Intent intent = new Intent(getActivity(), NewConversationActivity.class);
|
||||
if (isRelayingMessageContent(getActivity())) {
|
||||
if (isActionMode) {
|
||||
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());
|
||||
} else if (!uris.isEmpty()) {
|
||||
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()));
|
||||
}
|
||||
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());
|
||||
} else if (!uris.isEmpty()) {
|
||||
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()));
|
||||
}
|
||||
|
||||
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)
|
||||
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()]));
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
getActivity().finish();
|
||||
})
|
||||
.setPositiveButton(
|
||||
R.string.menu_send,
|
||||
(dialog, which) -> {
|
||||
SendRelayedMessageUtil.immediatelyRelay(
|
||||
getActivity(), selectedChats.toArray(new Long[selectedChats.size()]));
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
getActivity().finish();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
acquireRelayMessageContent(getActivity(), intent);
|
||||
fab.setOnClickListener(v -> requireActivity().startActivity(intent));
|
||||
@@ -132,8 +150,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 +162,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;
|
||||
}
|
||||
@@ -153,12 +171,14 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
}
|
||||
|
||||
private void handlePinAllSelected() {
|
||||
final DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
final Set<Long> selectedConversations = new HashSet<Long>(getListAdapter().getBatchSelections());
|
||||
final DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
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();
|
||||
@@ -167,23 +187,26 @@ 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 DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
final Set<Long> selectedConversations =
|
||||
new HashSet<Long>(getListAdapter().getBatchSelections());
|
||||
if (areSomeSelectedChatsUnmuted()) {
|
||||
MuteDialog.show(getActivity(), duration -> {
|
||||
for (long chatId : selectedConversations) {
|
||||
dcContext.setChatMuteDuration((int)chatId, duration);
|
||||
}
|
||||
MuteDialog.show(
|
||||
getActivity(),
|
||||
duration -> {
|
||||
for (long chatId : selectedConversations) {
|
||||
dcContext.setChatMuteDuration((int) chatId, duration);
|
||||
}
|
||||
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
}
|
||||
});
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// unmute
|
||||
for (long chatId : selectedConversations) {
|
||||
dcContext.setChatMuteDuration((int)chatId, 0);
|
||||
dcContext.setChatMuteDuration((int) chatId, 0);
|
||||
}
|
||||
|
||||
if (actionMode != null) {
|
||||
@@ -194,10 +217,11 @@ 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 DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
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();
|
||||
@@ -207,22 +231,21 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private void handleArchiveAllSelected() {
|
||||
final DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
final Set<Long> selectedConversations = new HashSet<Long>(getListAdapter().getBatchSelections());
|
||||
final boolean archive = offerToArchive();
|
||||
final DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
final Set<Long> selectedConversations =
|
||||
new HashSet<Long>(getListAdapter().getBatchSelections());
|
||||
final boolean archive = offerToArchive();
|
||||
|
||||
int snackBarTitleId;
|
||||
|
||||
if (archive) snackBarTitleId = R.plurals.chat_archived;
|
||||
else snackBarTitleId = R.plurals.chat_unarchived;
|
||||
else snackBarTitleId = R.plurals.chat_unarchived;
|
||||
|
||||
int count = selectedConversations.size();
|
||||
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 +260,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,51 +287,73 @@ 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;
|
||||
|
||||
if (!selectedChats.isEmpty()) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
private ProgressDialog dialog;
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
dialog =
|
||||
ProgressDialog.show(
|
||||
getActivity(),
|
||||
"",
|
||||
requireActivity().getString(R.string.one_moment),
|
||||
true,
|
||||
false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
dialog = ProgressDialog.show(getActivity(),
|
||||
"",
|
||||
requireActivity().getString(R.string.one_moment),
|
||||
true, false);
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
int accountId = dcContext.getAccountId();
|
||||
for (long chatId : selectedChats) {
|
||||
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);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
dialog.dismiss();
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
}
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
int accountId = dcContext.getAccountId();
|
||||
for (long chatId : selectedChats) {
|
||||
DcHelper.getNotificationCenter(requireContext()).removeNotifications(accountId, (int) chatId);
|
||||
dcContext.deleteChat((int) chatId);
|
||||
DirectShareUtil.clearShortcut(requireContext(), (int) chatId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
dialog.dismiss();
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
}
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
alert.setNegativeButton(android.R.string.cancel, null);
|
||||
AlertDialog dialog = alert.show();
|
||||
@@ -331,22 +378,30 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
intent.putExtra(ShareActivity.EXTRA_CHAT_ID, chat.getId());
|
||||
|
||||
Recipient recipient = new Recipient(activity, chat);
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
Bitmap avatar = DirectShareUtil.getIconForShortcut(activity, recipient);
|
||||
ShortcutInfoCompat shortcutInfoCompat = new ShortcutInfoCompat.Builder(activity, "chat-" + dcContext.getAccountId() + "-" + chat.getId())
|
||||
.setShortLabel(chat.getName())
|
||||
.setIcon(IconCompat.createWithAdaptiveBitmap(avatar))
|
||||
.setIntent(intent)
|
||||
.build();
|
||||
Util.runOnMain(() -> {
|
||||
if (!ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfoCompat, null)) {
|
||||
Toast.makeText(activity, "ErrAddToHomescreen: requestPinShortcut() failed", Toast.LENGTH_LONG).show();
|
||||
} else if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
Util.runOnAnyBackgroundThread(
|
||||
() -> {
|
||||
Bitmap avatar = DirectShareUtil.getIconForShortcut(activity, recipient);
|
||||
ShortcutInfoCompat shortcutInfoCompat =
|
||||
new ShortcutInfoCompat.Builder(
|
||||
activity, "chat-" + dcContext.getAccountId() + "-" + chat.getId())
|
||||
.setShortLabel(chat.getName())
|
||||
.setIcon(IconCompat.createWithAdaptiveBitmap(avatar))
|
||||
.setIntent(intent)
|
||||
.build();
|
||||
Util.runOnMain(
|
||||
() -> {
|
||||
if (!ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfoCompat, null)) {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
"ErrAddToHomescreen: requestPinShortcut() failed",
|
||||
Toast.LENGTH_LONG)
|
||||
.show();
|
||||
} else if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void updateActionModeItems(Menu menu) {
|
||||
@@ -356,11 +411,11 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
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);
|
||||
archiveItem.setTitle(R.string.menu_archive_chat);
|
||||
archiveItem.setIcon(R.drawable.ic_archive_white_24dp);
|
||||
archiveItem.setTitle(R.string.menu_archive_chat);
|
||||
} else {
|
||||
archiveItem.setIcon(R.drawable.ic_unarchive_white_24dp);
|
||||
archiveItem.setTitle(R.string.menu_unarchive_chat);
|
||||
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()) {
|
||||
@@ -383,7 +438,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) {
|
||||
@@ -451,6 +507,7 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
|
||||
public interface ConversationSelectedListener {
|
||||
void onCreateConversation(int chatId);
|
||||
|
||||
void onSwitchToArchive();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,26 +2,24 @@ 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,
|
||||
@NonNull DcChat dcChat,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Set<DcMsg> batchSelected,
|
||||
@NonNull Recipient recipients,
|
||||
boolean pulseHighlight,
|
||||
@Nullable AudioPlaybackViewModel playbackViewModel,
|
||||
AudioView.OnActionListener audioPlayPauseListener);
|
||||
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);
|
||||
|
||||
DcMsg getMessageRecord();
|
||||
|
||||
@@ -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,
|
||||
int msgId,
|
||||
@NonNull DcLot dcSummary,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Set<Long> selectedThreads, boolean batchMode);
|
||||
public void bind(
|
||||
@NonNull ThreadRecord thread,
|
||||
int msgId,
|
||||
@NonNull DcLot dcSummary,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@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
|
||||
implements LoaderManager.LoaderCallbacks<DcContactsLoader.Ret>,
|
||||
DcEventCenter.DcEventDelegate, ContactSelectionListAdapter.ItemClickListener {
|
||||
|
||||
public static class BlockedAndShareContactsFragment extends Fragment
|
||||
implements LoaderManager.LoaderCallbacks<DcContactsLoader.Ret>,
|
||||
DcEventCenter.DcEventDelegate,
|
||||
ContactSelectionListAdapter.ItemClickListener {
|
||||
|
||||
private RecyclerView recyclerView;
|
||||
private TextView emptyStateView;
|
||||
@@ -57,7 +55,7 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false);
|
||||
recyclerView = ViewUtil.findById(view, R.id.recycler_view);
|
||||
recyclerView = ViewUtil.findById(view, R.id.recycler_view);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
|
||||
// add padding to avoid content hidden behind system bars
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -128,10 +123,12 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
public void onItemClick(ContactSelectionListItem item, boolean handleActionMode) {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.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();
|
||||
.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();
|
||||
}
|
||||
|
||||
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,8 +32,10 @@ public class ConnectivityActivity extends WebViewActivity implements DcEventCent
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
final String connectivityHtml = DcHelper.getContext(this).getConnectivityHtml()
|
||||
.replace("</style>", " html { color-scheme: dark light; }</style>");
|
||||
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,11 +26,9 @@ 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
|
||||
{
|
||||
implements ContactSelectionListFragment.OnContactSelectedListener {
|
||||
private static final String TAG = ContactSelectionActivity.class.getSimpleName();
|
||||
|
||||
protected ContactSelectionListFragment contactsFragment;
|
||||
@@ -61,7 +58,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
|
||||
this.toolbar = ViewUtil.findById(this, R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
assert getSupportActionBar() != null;
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||
getSupportActionBar().setIcon(null);
|
||||
@@ -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,48 +60,52 @@ 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
|
||||
{
|
||||
public class ContactSelectionListFragment extends Fragment
|
||||
implements LoaderManager.LoaderCallbacks<DcContactsLoader.Ret>, DcEventCenter.DcEventDelegate {
|
||||
private static final String TAG = ContactSelectionListFragment.class.getSimpleName();
|
||||
|
||||
public static final String MULTI_SELECT = "multi_select";
|
||||
public static final String MULTI_SELECT = "multi_select";
|
||||
public static final String SELECT_UNENCRYPTED_EXTRA = "select_unencrypted_extra";
|
||||
public static final String ALLOW_CREATION = "allow_creation";
|
||||
public static final String PRESELECTED_CONTACTS = "preselected_contacts";
|
||||
public static final int CONTACT_ADDR_RESULT_CODE = 61123;
|
||||
|
||||
private DcContext dcContext;
|
||||
|
||||
private Set<Integer> selectedContacts;
|
||||
private Set<Integer> deselectedContacts;
|
||||
private Set<Integer> selectedContacts;
|
||||
private Set<Integer> deselectedContacts;
|
||||
private OnContactSelectedListener onContactSelectedListener;
|
||||
private String cursorFilter;
|
||||
private RecyclerView recyclerView;
|
||||
private TextView emptyView;
|
||||
private ActionMode actionMode;
|
||||
private ActionMode.Callback actionModeCallback;
|
||||
private String cursorFilter;
|
||||
private RecyclerView recyclerView;
|
||||
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,53 +121,59 @@ 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);
|
||||
emptyView = ViewUtil.findById(view, android.R.id.empty);
|
||||
recyclerView = ViewUtil.findById(view, R.id.recycler_view);
|
||||
emptyView = ViewUtil.findById(view, android.R.id.empty);
|
||||
|
||||
// add padding to avoid content hidden behind system bars
|
||||
ViewUtil.applyWindowInsets(recyclerView, true, false, true, true);
|
||||
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
actionModeCallback = new ActionMode.Callback() {
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
|
||||
MenuInflater inflater = getActivity().getMenuInflater();
|
||||
inflater.inflate(R.menu.contact_list, menu);
|
||||
menu.findItem(R.id.menu_delete_selected).setVisible(!isMulti());
|
||||
updateActionModeState(actionMode);
|
||||
return true;
|
||||
}
|
||||
actionModeCallback =
|
||||
new ActionMode.Callback() {
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
|
||||
MenuInflater inflater = getActivity().getMenuInflater();
|
||||
inflater.inflate(R.menu.contact_list, menu);
|
||||
menu.findItem(R.id.menu_delete_selected).setVisible(!isMulti());
|
||||
updateActionModeState(actionMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
|
||||
int itemId = menuItem.getItemId();
|
||||
if (itemId == R.id.menu_select_all) {
|
||||
handleSelectAll();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_view_profile) {
|
||||
handleViewProfile();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_delete_selected) {
|
||||
handleDeleteSelected();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
|
||||
int itemId = menuItem.getItemId();
|
||||
if (itemId == R.id.menu_select_all) {
|
||||
handleSelectAll();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_view_profile) {
|
||||
handleViewProfile();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_delete_selected) {
|
||||
handleDeleteSelected();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode actionMode) {
|
||||
ContactSelectionListFragment.this.actionMode = null;
|
||||
getContactSelectionListAdapter().resetActionModeSelection();
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode actionMode) {
|
||||
ContactSelectionListFragment.this.actionMode = null;
|
||||
getContactSelectionListAdapter().resetActionModeSelection();
|
||||
}
|
||||
};
|
||||
|
||||
DcHelper.getEventCenter(requireActivity())
|
||||
.addObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
|
||||
initializeCursor();
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -192,22 +205,28 @@ public class ContactSelectionListFragment extends Fragment
|
||||
}
|
||||
|
||||
private void handleDeleteSelected() {
|
||||
AlertDialog dialog = new AlertDialog.Builder(getActivity())
|
||||
.setMessage(R.string.ask_delete_contacts)
|
||||
.setPositiveButton(R.string.delete, (d, i) -> {
|
||||
ContactSelectionListAdapter adapter = getContactSelectionListAdapter();
|
||||
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();
|
||||
adapter.resetActionModeSelection();
|
||||
actionMode.finish();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
AlertDialog dialog =
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setMessage(R.string.ask_delete_contacts)
|
||||
.setPositiveButton(
|
||||
R.string.delete,
|
||||
(d, i) -> {
|
||||
ContactSelectionListAdapter adapter = getContactSelectionListAdapter();
|
||||
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();
|
||||
adapter.resetActionModeSelection();
|
||||
actionMode.finish();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
Util.redPositiveButton(dialog);
|
||||
}
|
||||
|
||||
@@ -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,15 +326,14 @@ 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);
|
||||
}
|
||||
return;
|
||||
}
|
||||
int contactId = contact.getSpecialId();
|
||||
int contactId = contact.getSpecialId();
|
||||
if (!isMulti() || !selectedContacts.contains(contactId)) {
|
||||
if (contactId == DcContact.DC_CONTACT_ID_NEW_CLASSIC_CONTACT) {
|
||||
Intent intent = new Intent(getContext(), NewContactActivity.class);
|
||||
@@ -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,38 +367,26 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
|
||||
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
|
||||
this.onContactSelectedListener = onContactSelectedListener;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,15 +531,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
menu.findItem(R.id.menu_ephemeral_messages).setVisible(false);
|
||||
}
|
||||
|
||||
if (isMultiUser()) {
|
||||
if (dcChat.isInBroadcast() && !dcChat.isContactRequest()) {
|
||||
menu.findItem(R.id.menu_leave).setTitle(R.string.menu_leave_channel).setVisible(true);
|
||||
} else if (dcChat.isEncrypted()
|
||||
&& dcChat.canSend()
|
||||
&& !dcChat.isOutBroadcast()
|
||||
&& !dcChat.isMailingList()) {
|
||||
menu.findItem(R.id.menu_leave).setVisible(true);
|
||||
if (dcChat.shallLeaveBeforeDelete(DcHelper.getContext(context))) {
|
||||
if (dcChat.isInBroadcast()) {
|
||||
menu.findItem(R.id.menu_leave).setTitle(R.string.menu_leave_channel);
|
||||
}
|
||||
menu.findItem(R.id.menu_leave).setVisible(true);
|
||||
menu.findItem(R.id.menu_delete_chat).setVisible(false);
|
||||
}
|
||||
|
||||
if (isArchived()) {
|
||||
@@ -732,9 +729,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
DcHelper.getContext(context).removeContactFromChat(chatId, DcContact.DC_CONTACT_ID_SELF);
|
||||
Toast.makeText(this, getString(R.string.done), Toast.LENGTH_SHORT).show();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setNegativeButton(R.string.menu_leave_and_delete, (d, which) -> {
|
||||
DcHelper.getContext(context).removeContactFromChat(chatId, DcContact.DC_CONTACT_ID_SELF);
|
||||
DcHelper.getContext(context).deleteChat(chatId);
|
||||
DirectShareUtil.clearShortcut(this, chatId);
|
||||
finish();
|
||||
})
|
||||
.setNeutralButton(R.string.cancel, null)
|
||||
.show();
|
||||
Util.redPositiveButton(dialog);
|
||||
Util.redButton(dialog, AlertDialog.BUTTON_NEGATIVE);
|
||||
}
|
||||
|
||||
private void handleArchiveChat() {
|
||||
@@ -753,7 +757,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private void handleDeleteChat() {
|
||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
.setMessage(getResources().getString(R.string.ask_delete_named_chat, dcChat.getName()))
|
||||
.setPositiveButton(R.string.delete, (d, which) -> {
|
||||
.setPositiveButton(R.string.delete_for_me, (d, which) -> {
|
||||
DcHelper.getContext(context).deleteChat(chatId);
|
||||
DirectShareUtil.clearShortcut(this, chatId);
|
||||
finish();
|
||||
@@ -1178,6 +1182,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
playbackViewModel.stopNonMessageAudioPlayback();
|
||||
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
final int currentChatId = dcChat.getId();
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
DcMsg msg = null;
|
||||
int recompress = 0;
|
||||
@@ -1187,9 +1192,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
if (action == ACTION_SEND_OUT) {
|
||||
dcContext.sendEditRequest(msgId, body);
|
||||
} else {
|
||||
dcContext.setDraft(chatId, null);
|
||||
dcContext.setDraft(currentChatId, null);
|
||||
}
|
||||
future.set(chatId);
|
||||
future.set(currentChatId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1200,7 +1205,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
try {
|
||||
if (slideDeck.getWebxdctDraftId() != 0) {
|
||||
msg = dcContext.getDraft(chatId);
|
||||
msg = dcContext.getDraft(currentChatId);
|
||||
} else {
|
||||
List<Attachment> attachments = slideDeck.asAttachments();
|
||||
for (Attachment attachment : attachments) {
|
||||
@@ -1249,7 +1254,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
// for WEBXDC, drafts are just sent out as is.
|
||||
// for preparations and other cases, cleanup draft soon.
|
||||
if (msg == null || msg.getType() != DcMsg.DC_MSG_WEBXDC) {
|
||||
dcContext.setDraft(dcChat.getId(), null);
|
||||
dcContext.setDraft(currentChatId, null);
|
||||
}
|
||||
|
||||
if(msg!=null) {
|
||||
@@ -1265,7 +1270,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
false
|
||||
);
|
||||
});
|
||||
doSend = VideoRecoder.prepareVideo(ConversationActivity.this, dcChat.getId(), msg);
|
||||
doSend = VideoRecoder.prepareVideo(ConversationActivity.this, currentChatId, msg);
|
||||
Util.runOnMain(() -> {
|
||||
try {
|
||||
if (progressDialog != null) progressDialog.dismiss();
|
||||
@@ -1276,48 +1281,34 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
if (doSend) {
|
||||
if (dcContext.sendMsg(dcChat.getId(), msg) == 0) {
|
||||
if (dcContext.sendMsg(currentChatId, msg) == 0) {
|
||||
String lastError = dcContext.getLastError();
|
||||
if (!"".equals(lastError)) {
|
||||
Util.runOnMain(() -> Toast.makeText(ConversationActivity.this, lastError, Toast.LENGTH_LONG).show());
|
||||
}
|
||||
future.set(chatId);
|
||||
future.set(currentChatId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Util.runOnMain(() -> sendComplete(dcChat.getId()));
|
||||
Util.runOnMain(() -> sendComplete());
|
||||
}
|
||||
} else {
|
||||
dcContext.setDraft(dcChat.getId(), msg);
|
||||
dcContext.setDraft(currentChatId, msg);
|
||||
}
|
||||
future.set(chatId);
|
||||
future.set(currentChatId);
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
|
||||
protected void sendComplete(int chatId) {
|
||||
boolean refreshFragment = (chatId != this.chatId);
|
||||
this.chatId = chatId;
|
||||
|
||||
protected void sendComplete() {
|
||||
if (fragment == null || !fragment.isVisible() || isFinishing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
fragment.setLastSeen(-1);
|
||||
|
||||
if (refreshFragment) {
|
||||
fragment.reload(recipient, chatId);
|
||||
try {
|
||||
int accId = rpc.getSelectedAccountId();
|
||||
DcHelper.getNotificationCenter(this).updateVisibleChat(accId, chatId);
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "rpc.getSelectedAccountId() failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
fragment.scrollToBottom();
|
||||
attachmentManager.cleanup();
|
||||
}
|
||||
@@ -1456,6 +1447,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
isEditing = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStickerPicked(Uri stickerUri) {
|
||||
if (isEditing) return;
|
||||
sendSticker(stickerUri, "image/webp");
|
||||
}
|
||||
|
||||
public void refreshStickerPicker() {
|
||||
if (emojiPicker != null) {
|
||||
emojiPicker.refreshStickerPicker();
|
||||
}
|
||||
}
|
||||
|
||||
// media selected by the system keyboard
|
||||
@Override
|
||||
public void onMediaSelected(@NonNull Uri uri, String contentType) {
|
||||
|
||||
@@ -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,61 +49,50 @@ 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;
|
||||
private static final int MESSAGE_TYPE_INFO = 2;
|
||||
private static final int MESSAGE_TYPE_AUDIO_OUTGOING = 3;
|
||||
private static final int MESSAGE_TYPE_AUDIO_INCOMING = 4;
|
||||
private static final int MESSAGE_TYPE_OUTGOING = 0;
|
||||
private static final int MESSAGE_TYPE_INCOMING = 1;
|
||||
private static final int MESSAGE_TYPE_INFO = 2;
|
||||
private static final int MESSAGE_TYPE_AUDIO_OUTGOING = 3;
|
||||
private static final int MESSAGE_TYPE_AUDIO_INCOMING = 4;
|
||||
private static final int MESSAGE_TYPE_THUMBNAIL_OUTGOING = 5;
|
||||
private static final int MESSAGE_TYPE_THUMBNAIL_INCOMING = 6;
|
||||
private static final int MESSAGE_TYPE_DOCUMENT_OUTGOING = 7;
|
||||
private static final int MESSAGE_TYPE_DOCUMENT_INCOMING = 8;
|
||||
private static final int MESSAGE_TYPE_STICKER_INCOMING = 9;
|
||||
private static final int MESSAGE_TYPE_STICKER_OUTGOING = 10;
|
||||
private static final int MESSAGE_TYPE_DOCUMENT_OUTGOING = 7;
|
||||
private static final int MESSAGE_TYPE_DOCUMENT_INCOMING = 8;
|
||||
private static final int MESSAGE_TYPE_STICKER_INCOMING = 9;
|
||||
private static final int MESSAGE_TYPE_STICKER_OUTGOING = 10;
|
||||
|
||||
private final Set<DcMsg> batchSelected = Collections.synchronizedSet(new HashSet<DcMsg>());
|
||||
|
||||
private final @Nullable ItemClickListener clickListener;
|
||||
private final @NonNull GlideRequests glideRequests;
|
||||
private final @NonNull Recipient recipient;
|
||||
private final @NonNull LayoutInflater inflater;
|
||||
private final @NonNull Context context;
|
||||
private final @NonNull Calendar calendar;
|
||||
private final @NonNull GlideRequests glideRequests;
|
||||
private final @NonNull Recipient recipient;
|
||||
private final @NonNull LayoutInflater inflater;
|
||||
private final @NonNull Context context;
|
||||
private final @NonNull Calendar calendar;
|
||||
|
||||
private final DcContext dcContext;
|
||||
private @NonNull DcChat dcChat;
|
||||
private @NonNull int[] dcMsgList = new int[0];
|
||||
private int positionToPulseHighlight = -1;
|
||||
private int positionCurrentlyPulseHighlighting = -1;
|
||||
private long pulseHighlightingSince = -1;
|
||||
private int lastSeenPosition = -1;
|
||||
private long lastSeen = -1;
|
||||
private final DcContext dcContext;
|
||||
private @NonNull DcChat dcChat;
|
||||
private @NonNull int[] dcMsgList = new int[0];
|
||||
private int positionToPulseHighlight = -1;
|
||||
private int positionCurrentlyPulseHighlighting = -1;
|
||||
private long pulseHighlightingSince = -1;
|
||||
private int lastSeenPosition = -1;
|
||||
private long lastSeen = -1;
|
||||
private AudioPlaybackViewModel playbackViewModel;
|
||||
private AudioView.OnActionListener audioPlayPauseListener;
|
||||
|
||||
@@ -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,17 +202,18 @@ 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,
|
||||
@NonNull DcChat dcChat,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@Nullable ItemClickListener clickListener,
|
||||
@NonNull Recipient recipient) {
|
||||
public ConversationAdapter(
|
||||
@NonNull Context context,
|
||||
@NonNull DcChat dcChat,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@Nullable ItemClickListener clickListener,
|
||||
@NonNull Recipient recipient) {
|
||||
this.dcChat = dcChat;
|
||||
this.glideRequests = glideRequests;
|
||||
this.clickListener = clickListener;
|
||||
@@ -231,14 +221,14 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
this.context = context;
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.calendar = Calendar.getInstance();
|
||||
this.dcContext = DcHelper.getContext(context);
|
||||
this.dcContext = DcHelper.getContext(context);
|
||||
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
@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,14 +238,25 @@ 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
|
||||
public void onViewRecycled(@NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
if (viewHolder.itemView instanceof ConversationItem) {
|
||||
if (viewHolder.itemView instanceof ConversationItem) {
|
||||
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, 0, 1);
|
||||
}
|
||||
}
|
||||
@@ -263,17 +264,19 @@ 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 -> {
|
||||
if (clickListener != null) {
|
||||
clickListener.onItemClick(itemView.getMessageRecord());
|
||||
}
|
||||
});
|
||||
itemView.setOnLongClickListener(view -> {
|
||||
if (clickListener != null) {
|
||||
clickListener.onItemLongClick(itemView.getMessageRecord(), view);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
itemView.setOnClickListener(
|
||||
view -> {
|
||||
if (clickListener != null) {
|
||||
clickListener.onItemClick(itemView.getMessageRecord());
|
||||
}
|
||||
});
|
||||
itemView.setOnLongClickListener(
|
||||
view -> {
|
||||
if (clickListener != null) {
|
||||
clickListener.onItemLongClick(itemView.getMessageRecord(), view);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
itemView.setEventListener(clickListener);
|
||||
return new ViewHolder(itemView);
|
||||
}
|
||||
@@ -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,16 +343,16 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
public long getSortTimestamp(int position) {
|
||||
if (!isActive()) return 0;
|
||||
if (!isActive()) return 0;
|
||||
if (position >= getItemCount()) return 0;
|
||||
if (position < 0) return 0;
|
||||
if (position < 0) return 0;
|
||||
|
||||
DcMsg msg = getMsg(position);
|
||||
return msg.getSortTimestamp();
|
||||
@@ -361,7 +366,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
@Override
|
||||
public long getHeaderId(int position) {
|
||||
if (position >= getItemCount()) return -1;
|
||||
if (position < 0) return -1;
|
||||
if (position < 0) return -1;
|
||||
|
||||
calendar.setTime(new Date(getSortTimestamp(position)));
|
||||
return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR));
|
||||
@@ -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;
|
||||
@@ -400,12 +404,11 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
}
|
||||
|
||||
private int findLastSeenPosition(long lastSeen) {
|
||||
if (lastSeen <= 0) return -1;
|
||||
if (!isActive()) return -1;
|
||||
if (lastSeen <= 0) return -1;
|
||||
if (!isActive()) return -1;
|
||||
|
||||
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 {
|
||||
@@ -429,32 +437,45 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
|
||||
LastSeenHeader(ConversationAdapter adapter) {
|
||||
super(adapter, false, false);
|
||||
this.adapter = adapter;
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasHeader(RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) {
|
||||
return adapter.isActive() && position == adapter.getLastSeenPosition();
|
||||
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 widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
|
||||
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;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -807,8 +807,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;
|
||||
}
|
||||
@@ -969,6 +967,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,40 +6,37 @@ 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;
|
||||
|
||||
class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
|
||||
private static final float SWIPE_SUCCESS_DX = ConversationSwipeAnimationHelper.TRIGGER_DX;
|
||||
private static final long SWIPE_SUCCESS_VIBE_TIME_MS = 10;
|
||||
private static final float SWIPE_SUCCESS_DX = ConversationSwipeAnimationHelper.TRIGGER_DX;
|
||||
private static final long SWIPE_SUCCESS_VIBE_TIME_MS = 10;
|
||||
|
||||
private boolean swipeBack;
|
||||
private boolean shouldTriggerSwipeFeedback;
|
||||
private boolean canTriggerSwipe;
|
||||
private float latestDownX;
|
||||
private float latestDownY;
|
||||
private float latestDownX;
|
||||
private float latestDownY;
|
||||
|
||||
private final SwipeAvailabilityProvider swipeAvailabilityProvider;
|
||||
private final SwipeAvailabilityProvider swipeAvailabilityProvider;
|
||||
private final ConversationItemTouchListener itemTouchListener;
|
||||
private final OnSwipeListener onSwipeListener;
|
||||
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;
|
||||
this.onSwipeListener = onSwipeListener;
|
||||
this.itemTouchListener = new ConversationItemTouchListener(this::updateLatestDownCoordinate);
|
||||
this.swipeAvailabilityProvider = swipeAvailabilityProvider;
|
||||
this.onSwipeListener = onSwipeListener;
|
||||
this.shouldTriggerSwipeFeedback = true;
|
||||
this.canTriggerSwipe = true;
|
||||
this.canTriggerSwipe = true;
|
||||
}
|
||||
|
||||
void attachToRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
@@ -48,21 +45,19 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
@NonNull RecyclerView.ViewHolder target)
|
||||
{
|
||||
public boolean onMove(
|
||||
@NonNull RecyclerView recyclerView,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
@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);
|
||||
}
|
||||
@@ -78,18 +73,21 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
|
||||
@Override
|
||||
public void onChildDraw(
|
||||
@NonNull Canvas c,
|
||||
@NonNull RecyclerView recyclerView,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
float dx, float dy, int actionState, boolean isCurrentlyActive)
|
||||
{
|
||||
@NonNull Canvas c,
|
||||
@NonNull RecyclerView recyclerView,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
float dx,
|
||||
float dy,
|
||||
int actionState,
|
||||
boolean isCurrentlyActive) {
|
||||
if (cannotSwipeViewHolder(viewHolder)) return;
|
||||
|
||||
float sign = getSignFromDirection(viewHolder.itemView);
|
||||
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));
|
||||
@@ -100,7 +98,7 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
|
||||
if (dx == 0) {
|
||||
shouldTriggerSwipeFeedback = true;
|
||||
canTriggerSwipe = true;
|
||||
canTriggerSwipe = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,50 +120,50 @@ 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.
|
||||
// Otherwise, any subsequent little swipe would invoke onSwiped().
|
||||
// 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;
|
||||
// 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
|
||||
// on touch listener in the meantime and we don't want to cancel it
|
||||
private boolean listenerCalled = false;
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
shouldTriggerSwipeFeedback = true;
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (!listenerCalled) {
|
||||
listenerCalled = true;
|
||||
ConversationItemSwipeCallback.this.handleTouchActionUp(recyclerView, viewHolder, dx);
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
shouldTriggerSwipeFeedback = true;
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (!listenerCalled) {
|
||||
listenerCalled = true;
|
||||
ConversationItemSwipeCallback.this.handleTouchActionUp(
|
||||
recyclerView, viewHolder, dx);
|
||||
}
|
||||
// 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:
|
||||
viewHolder.itemView.postDelayed(() -> resetProgress(viewHolder), 2000);
|
||||
if (AccessibilityUtil.areAnimationsDisabled(viewHolder.itemView.getContext())) {
|
||||
resetProgress(viewHolder);
|
||||
}
|
||||
break;
|
||||
}
|
||||
//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:
|
||||
viewHolder.itemView.postDelayed(() -> resetProgress(viewHolder), 2000);
|
||||
if (AccessibilityUtil.areAnimationsDisabled(viewHolder.itemView.getContext())) {
|
||||
resetProgress(viewHolder);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
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,7 +62,8 @@ 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;
|
||||
@@ -77,46 +80,51 @@ 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
|
||||
{
|
||||
implements ConversationListFragment.ConversationSelectedListener {
|
||||
private static final String TAG = ConversationListActivity.class.getSimpleName();
|
||||
private static final String OPENPGP4FPR = "openpgp4fpr";
|
||||
private static final String NDK_ARCH_WARNED = "ndk_arch_warned";
|
||||
public static final String CLEAR_NOTIFICATIONS = "clear_notifications";
|
||||
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;
|
||||
public static final String FROM_WELCOME = "from_welcome";
|
||||
public static final String FROM_WELCOME_RAW_QR = "from_welcome_raw_qr";
|
||||
|
||||
private ConversationListFragment conversationListFragment;
|
||||
public TextView title;
|
||||
private AvatarView selfAvatar;
|
||||
private ImageView unreadIndicator;
|
||||
private SearchFragment searchFragment;
|
||||
private SearchToolbar searchToolbar;
|
||||
private ImageView searchAction;
|
||||
private ViewGroup fragmentContainer;
|
||||
private ViewGroup selfAvatarContainer;
|
||||
public TextView title;
|
||||
private AvatarView selfAvatar;
|
||||
private ImageView unreadIndicator;
|
||||
private SearchFragment searchFragment;
|
||||
private SearchToolbar searchToolbar;
|
||||
private ImageView searchAction;
|
||||
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 +133,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());
|
||||
@@ -135,59 +181,68 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
selfAvatar = findViewById(R.id.self_avatar);
|
||||
selfAvatarContainer = findViewById(R.id.self_avatar_container);
|
||||
unreadIndicator = findViewById(R.id.unread_indicator);
|
||||
title = findViewById(R.id.toolbar_title);
|
||||
searchToolbar = findViewById(R.id.search_toolbar);
|
||||
searchAction = findViewById(R.id.search_action);
|
||||
fragmentContainer = findViewById(R.id.fragment_container);
|
||||
selfAvatar = findViewById(R.id.self_avatar);
|
||||
selfAvatarContainer = findViewById(R.id.self_avatar_container);
|
||||
unreadIndicator = findViewById(R.id.unread_indicator);
|
||||
title = findViewById(R.id.toolbar_title);
|
||||
searchToolbar = findViewById(R.id.search_toolbar);
|
||||
searchAction = findViewById(R.id.search_action);
|
||||
fragmentContainer = findViewById(R.id.fragment_container);
|
||||
|
||||
// add margin to avoid content hidden behind system bars
|
||||
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) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (searchToolbar.isVisible()) {
|
||||
searchToolbar.collapse();
|
||||
} else {
|
||||
Activity activity = ConversationListActivity.this;
|
||||
if (isRelayingMessageContent(activity)) {
|
||||
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.)
|
||||
// just clear the back stack and stay in newly selected account
|
||||
finishAffinity();
|
||||
startActivity(new Intent(activity, ConversationListActivity.class));
|
||||
return;
|
||||
} else {
|
||||
handleResetRelaying();
|
||||
}
|
||||
}
|
||||
getOnBackPressedDispatcher()
|
||||
.addCallback(
|
||||
this,
|
||||
new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (searchToolbar.isVisible()) {
|
||||
searchToolbar.collapse();
|
||||
} else {
|
||||
Activity activity = ConversationListActivity.this;
|
||||
if (isRelayingMessageContent(activity)) {
|
||||
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.)
|
||||
// just clear the back stack and stay in newly selected account
|
||||
finishAffinity();
|
||||
startActivity(new Intent(activity, ConversationListActivity.class));
|
||||
return;
|
||||
} else {
|
||||
handleResetRelaying();
|
||||
}
|
||||
}
|
||||
|
||||
setEnabled(false);
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
}
|
||||
}
|
||||
});
|
||||
setEnabled(false);
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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 -> {
|
||||
if (!isRelayingMessageContent(this)) {
|
||||
AccountManager.getInstance().showSwitchAccountMenu(this, false);
|
||||
}
|
||||
});
|
||||
selfAvatar.setOnClickListener(
|
||||
v -> AccountManager.getInstance().showSwitchAccountMenu(this, false));
|
||||
findViewById(R.id.avatar_and_title)
|
||||
.setOnClickListener(
|
||||
v -> {
|
||||
if (!isRelayingMessageContent(this)) {
|
||||
AccountManager.getInstance().showSwitchAccountMenu(this, false);
|
||||
}
|
||||
});
|
||||
|
||||
refresh();
|
||||
|
||||
@@ -207,7 +262,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 +275,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,23 +290,35 @@ 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);
|
||||
|
||||
if (!Prefs.getBooleanPreference(this, NDK_ARCH_WARNED, false)) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
Prefs.setBooleanPreference(this, NDK_ARCH_WARNED, true);
|
||||
}
|
||||
}
|
||||
@@ -311,7 +379,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 +416,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[] {
|
||||
R.attr.conversation_list_item_unreadcount_color,
|
||||
});
|
||||
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));
|
||||
@@ -386,9 +458,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 +468,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,42 +480,42 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
}
|
||||
|
||||
private void initializeSearchListener() {
|
||||
searchAction.setOnClickListener(v -> {
|
||||
searchToolbar.display(searchAction.getX() + (searchAction.getWidth() / 2),
|
||||
searchAction.getY() + (searchAction.getHeight() / 2));
|
||||
});
|
||||
searchAction.setOnClickListener(
|
||||
v -> {
|
||||
searchToolbar.display(
|
||||
searchAction.getX() + (searchAction.getWidth() / 2),
|
||||
searchAction.getY() + (searchAction.getHeight() / 2));
|
||||
});
|
||||
|
||||
searchToolbar.setListener(new SearchToolbar.SearchListener() {
|
||||
@Override
|
||||
public void onSearchTextChange(String text) {
|
||||
String trimmed = text.trim();
|
||||
searchToolbar.setListener(
|
||||
new SearchToolbar.SearchListener() {
|
||||
@Override
|
||||
public void onSearchTextChange(String text) {
|
||||
String trimmed = text.trim();
|
||||
|
||||
if (trimmed.length() > 0) {
|
||||
if (searchFragment == null) {
|
||||
searchFragment = SearchFragment.newInstance();
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.fragment_container, searchFragment, null)
|
||||
.commit();
|
||||
if (trimmed.length() > 0) {
|
||||
if (searchFragment == null) {
|
||||
searchFragment = SearchFragment.newInstance();
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(R.id.fragment_container, searchFragment, null)
|
||||
.commit();
|
||||
}
|
||||
searchFragment.updateSearchQuery(trimmed);
|
||||
} else if (searchFragment != null) {
|
||||
getSupportFragmentManager().beginTransaction().remove(searchFragment).commit();
|
||||
searchFragment = null;
|
||||
}
|
||||
}
|
||||
searchFragment.updateSearchQuery(trimmed);
|
||||
} else if (searchFragment != null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.remove(searchFragment)
|
||||
.commit();
|
||||
searchFragment = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchClosed() {
|
||||
if (searchFragment != null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.remove(searchFragment)
|
||||
.commit();
|
||||
searchFragment = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onSearchClosed() {
|
||||
if (searchFragment != null) {
|
||||
getSupportFragmentManager().beginTransaction().remove(searchFragment).commit();
|
||||
searchFragment = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -458,7 +530,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,38 +557,40 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
}
|
||||
|
||||
private void handleSaveAttachment() {
|
||||
SaveAttachmentTask.showWarningDialog(this, (dialogInterface, i) -> {
|
||||
if (StorageUtil.canWriteToMediaStore(this)) {
|
||||
performSave();
|
||||
return;
|
||||
}
|
||||
SaveAttachmentTask.showWarningDialog(
|
||||
this,
|
||||
(dialogInterface, i) -> {
|
||||
if (StorageUtil.canWriteToMediaStore(this)) {
|
||||
performSave();
|
||||
return;
|
||||
}
|
||||
|
||||
Permissions.with(this)
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.alwaysGrantOnSdk30()
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(getString(R.string.perm_explain_access_to_storage_denied))
|
||||
.onAllGranted(this::performSave)
|
||||
.execute();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void performSave() {
|
||||
ArrayList<Uri> uriList = ShareUtil.getSharedUris(this);
|
||||
ArrayList<Uri> uriList = ShareUtil.getSharedUris(this);
|
||||
Uri uri = uriList.get(0);
|
||||
String mimeType = PersistentBlobProvider.getMimeType(this, uri);
|
||||
String fileName = PersistentBlobProvider.getFileName(this, uri);
|
||||
SaveAttachmentTask.Attachment[] attachments = new SaveAttachmentTask.Attachment[]{
|
||||
new SaveAttachmentTask.Attachment(uri, mimeType, new Date().getTime(), fileName)
|
||||
};
|
||||
SaveAttachmentTask.Attachment[] attachments =
|
||||
new SaveAttachmentTask.Attachment[] {
|
||||
new SaveAttachmentTask.Attachment(uri, mimeType, new Date().getTime(), fileName)
|
||||
};
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(this);
|
||||
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachments);
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -547,7 +623,11 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
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();
|
||||
Toast.makeText(
|
||||
this,
|
||||
DynamicTheme.getCheckmarkEmoji(this) + " " + getString(R.string.saved),
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
handleResetRelaying();
|
||||
finish();
|
||||
} else {
|
||||
@@ -592,7 +672,8 @@ 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";
|
||||
@@ -601,7 +682,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
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");
|
||||
@@ -610,7 +692,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
}
|
||||
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 +702,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 +717,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 +750,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,48 +20,44 @@ import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcChatlist;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcLot;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* A CursorAdapter for building a list of conversation threads.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
class ConversationListAdapter extends BaseConversationListAdapter<ConversationListAdapter.ViewHolder> {
|
||||
class ConversationListAdapter
|
||||
extends BaseConversationListAdapter<ConversationListAdapter.ViewHolder> {
|
||||
|
||||
private static final int MESSAGE_TYPE_SWITCH_ARCHIVE = 1;
|
||||
private static final int MESSAGE_TYPE_THREAD = 2;
|
||||
private static final int MESSAGE_TYPE_INBOX_ZERO = 3;
|
||||
private static final int MESSAGE_TYPE_THREAD = 2;
|
||||
private static final int MESSAGE_TYPE_INBOX_ZERO = 3;
|
||||
|
||||
private final WeakReference<Context> context;
|
||||
private @NonNull DcContext dcContext;
|
||||
private @NonNull DcChatlist dcChatlist;
|
||||
private final @NonNull GlideRequests glideRequests;
|
||||
private final @NonNull LayoutInflater inflater;
|
||||
private final @Nullable ItemClickListener clickListener;
|
||||
private final WeakReference<Context> context;
|
||||
private @NonNull DcContext dcContext;
|
||||
private @NonNull DcChatlist dcChatlist;
|
||||
private final @NonNull GlideRequests glideRequests;
|
||||
private final @NonNull LayoutInflater inflater;
|
||||
private final @Nullable ItemClickListener clickListener;
|
||||
|
||||
protected static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public <V extends View & BindableConversationListItem> ViewHolder(final @NonNull V itemView)
|
||||
{
|
||||
public <V extends View & BindableConversationListItem> ViewHolder(final @NonNull V itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public BindableConversationListItem getItem() {
|
||||
return (BindableConversationListItem)itemView;
|
||||
return (BindableConversationListItem) itemView;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,17 +71,17 @@ class ConversationListAdapter extends BaseConversationListAdapter<ConversationLi
|
||||
return dcChatlist.getChatId(i);
|
||||
}
|
||||
|
||||
ConversationListAdapter(@NonNull Context context,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@Nullable ItemClickListener clickListener)
|
||||
{
|
||||
ConversationListAdapter(
|
||||
@NonNull Context context,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@Nullable ItemClickListener clickListener) {
|
||||
super();
|
||||
this.context = new WeakReference<>(context);
|
||||
this.glideRequests = glideRequests;
|
||||
this.dcContext = DcHelper.getContext(context);
|
||||
this.dcChatlist = new DcChatlist(0, 0);
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.clickListener = clickListener;
|
||||
this.context = new WeakReference<>(context);
|
||||
this.glideRequests = glideRequests;
|
||||
this.dcContext = DcHelper.getContext(context);
|
||||
this.dcChatlist = new DcChatlist(0, 0);
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.clickListener = clickListener;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
@@ -93,29 +89,37 @@ class ConversationListAdapter extends BaseConversationListAdapter<ConversationLi
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
if (viewType == MESSAGE_TYPE_SWITCH_ARCHIVE) {
|
||||
final ConversationListItem item = (ConversationListItem)inflater.inflate(R.layout.conversation_list_item_view, parent, false);
|
||||
final ConversationListItem item =
|
||||
(ConversationListItem)
|
||||
inflater.inflate(R.layout.conversation_list_item_view, parent, false);
|
||||
item.getLayoutParams().height = ViewUtil.dpToPx(54);
|
||||
item.findViewById(R.id.subject).setVisibility(View.GONE);
|
||||
item.findViewById(R.id.date).setVisibility(View.GONE);
|
||||
item.setOnClickListener(v -> {
|
||||
if (clickListener != null) clickListener.onSwitchToArchive();
|
||||
});
|
||||
item.setOnClickListener(
|
||||
v -> {
|
||||
if (clickListener != null) clickListener.onSwitchToArchive();
|
||||
});
|
||||
|
||||
return new ViewHolder(item);
|
||||
} else if (viewType == MESSAGE_TYPE_INBOX_ZERO) {
|
||||
return new ViewHolder((ConversationListItemInboxZero)inflater.inflate(R.layout.conversation_list_item_inbox_zero, parent, false));
|
||||
return new ViewHolder(
|
||||
(ConversationListItemInboxZero)
|
||||
inflater.inflate(R.layout.conversation_list_item_inbox_zero, parent, false));
|
||||
} else {
|
||||
final ConversationListItem item = (ConversationListItem)inflater.inflate(R.layout.conversation_list_item_view,
|
||||
parent, false);
|
||||
final ConversationListItem item =
|
||||
(ConversationListItem)
|
||||
inflater.inflate(R.layout.conversation_list_item_view, parent, false);
|
||||
|
||||
item.setOnClickListener(view -> {
|
||||
if (clickListener != null) clickListener.onItemClick(item);
|
||||
});
|
||||
item.setOnClickListener(
|
||||
view -> {
|
||||
if (clickListener != null) clickListener.onItemClick(item);
|
||||
});
|
||||
|
||||
item.setOnLongClickListener(view -> {
|
||||
if (clickListener != null) clickListener.onItemLongClick(item);
|
||||
return true;
|
||||
});
|
||||
item.setOnLongClickListener(
|
||||
view -> {
|
||||
if (clickListener != null) clickListener.onItemLongClick(item);
|
||||
return true;
|
||||
});
|
||||
|
||||
return new ViewHolder(item);
|
||||
}
|
||||
@@ -130,7 +134,15 @@ class ConversationListAdapter extends BaseConversationListAdapter<ConversationLi
|
||||
|
||||
DcChat chat = dcContext.getChat(dcChatlist.getChatId(i));
|
||||
DcLot summary = dcChatlist.getSummary(i, chat);
|
||||
viewHolder.getItem().bind(DcHelper.getThreadRecord(context, summary, chat), dcChatlist.getMsgId(i), summary, glideRequests, batchSet, batchMode);
|
||||
viewHolder
|
||||
.getItem()
|
||||
.bind(
|
||||
DcHelper.getThreadRecord(context, summary, chat),
|
||||
dcChatlist.getMsgId(i),
|
||||
summary,
|
||||
glideRequests,
|
||||
batchSet,
|
||||
batchMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,7 +151,7 @@ class ConversationListAdapter extends BaseConversationListAdapter<ConversationLi
|
||||
|
||||
if (chatId == DcChat.DC_CHAT_ID_ARCHIVED_LINK) {
|
||||
return MESSAGE_TYPE_SWITCH_ARCHIVE;
|
||||
} else if(chatId == DcChat.DC_CHAT_ID_ALLDONE_HINT) {
|
||||
} else if (chatId == DcChat.DC_CHAT_ID_ALLDONE_HINT) {
|
||||
return MESSAGE_TYPE_INBOX_ZERO;
|
||||
} else {
|
||||
return MESSAGE_TYPE_THREAD;
|
||||
@@ -159,7 +171,9 @@ class ConversationListAdapter extends BaseConversationListAdapter<ConversationLi
|
||||
|
||||
interface ItemClickListener {
|
||||
void onItemClick(ConversationListItem item);
|
||||
|
||||
void onItemLongClick(ConversationListItem item);
|
||||
|
||||
void onSwitchToArchive();
|
||||
}
|
||||
|
||||
@@ -169,7 +183,7 @@ class ConversationListAdapter extends BaseConversationListAdapter<ConversationLi
|
||||
return;
|
||||
}
|
||||
if (chatlist == null) {
|
||||
dcChatlist = new DcChatlist(0, 0);
|
||||
dcChatlist = new DcChatlist(0, 0);
|
||||
} else {
|
||||
dcChatlist = chatlist;
|
||||
dcContext = DcHelper.getContext(context);
|
||||
|
||||
@@ -11,22 +11,19 @@ import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
|
||||
import com.b44t.messenger.DcChat;
|
||||
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
|
||||
public class ConversationListArchiveActivity extends PassphraseRequiredActionBarActivity
|
||||
implements ConversationListFragment.ConversationSelectedListener
|
||||
{
|
||||
implements ConversationListFragment.ConversationSelectedListener {
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle, boolean ready) {
|
||||
setContentView(R.layout.activity_conversation_list_archive);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
if (isRelayingMessageContent(this)) {
|
||||
getSupportActionBar().setTitle(isSharing(this) ? R.string.chat_share_with_title : R.string.forward_to);
|
||||
getSupportActionBar()
|
||||
.setTitle(isSharing(this) ? R.string.chat_share_with_title : R.string.forward_to);
|
||||
getSupportActionBar().setSubtitle(R.string.chat_archived_label);
|
||||
} else {
|
||||
getSupportActionBar().setTitle(R.string.chat_archived_label);
|
||||
@@ -36,20 +33,25 @@ public class ConversationListArchiveActivity extends PassphraseRequiredActionBar
|
||||
bundle.putBoolean(ConversationListFragment.ARCHIVE, true);
|
||||
initFragment(R.id.fragment, new ConversationListFragment(), bundle);
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (!isRelayingMessageContent(ConversationListArchiveActivity.this)) {
|
||||
// Load the ConversationListActivity in case it's not existent for some reason
|
||||
Intent intent = new Intent(ConversationListArchiveActivity.this, ConversationListActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
}
|
||||
getOnBackPressedDispatcher()
|
||||
.addCallback(
|
||||
this,
|
||||
new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (!isRelayingMessageContent(ConversationListArchiveActivity.this)) {
|
||||
// Load the ConversationListActivity in case it's not existent for some reason
|
||||
Intent intent =
|
||||
new Intent(
|
||||
ConversationListArchiveActivity.this, ConversationListActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
setEnabled(false);
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
}
|
||||
});
|
||||
setEnabled(false);
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,16 +29,15 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.b44t.messenger.DcChatlist;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import org.thoughtcrime.securesms.ConversationListAdapter.ItemClickListener;
|
||||
import org.thoughtcrime.securesms.components.recyclerview.DeleteItemAnimator;
|
||||
import org.thoughtcrime.securesms.components.reminder.DozeReminder;
|
||||
@@ -52,25 +51,21 @@ import org.thoughtcrime.securesms.util.ShareUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
|
||||
public class ConversationListFragment extends BaseConversationListFragment
|
||||
implements ItemClickListener, DcEventCenter.DcEventDelegate {
|
||||
implements ItemClickListener, DcEventCenter.DcEventDelegate {
|
||||
public static final String ARCHIVE = "archive";
|
||||
public static final String RELOAD_LIST = "reload_list";
|
||||
|
||||
private static final String TAG = ConversationListFragment.class.getSimpleName();
|
||||
|
||||
private RecyclerView list;
|
||||
private View emptyState;
|
||||
private TextView emptySearch;
|
||||
private final String queryFilter = "";
|
||||
private boolean archive;
|
||||
private Timer reloadTimer;
|
||||
private boolean chatlistJustLoaded;
|
||||
private boolean reloadTimerInstantly;
|
||||
private RecyclerView list;
|
||||
private View emptyState;
|
||||
private TextView emptySearch;
|
||||
private final String queryFilter = "";
|
||||
private boolean archive;
|
||||
private Timer reloadTimer;
|
||||
private boolean chatlistJustLoaded;
|
||||
private boolean reloadTimerInstantly;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
@@ -103,10 +98,10 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
final View view = inflater.inflate(R.layout.conversation_list_fragment, container, false);
|
||||
|
||||
list = ViewUtil.findById(view, R.id.list);
|
||||
fab = ViewUtil.findById(view, R.id.fab);
|
||||
emptyState = ViewUtil.findById(view, R.id.empty_state);
|
||||
emptySearch = ViewUtil.findById(view, R.id.empty_search);
|
||||
list = ViewUtil.findById(view, R.id.list);
|
||||
fab = ViewUtil.findById(view, R.id.fab);
|
||||
emptyState = ViewUtil.findById(view, R.id.empty_state);
|
||||
emptySearch = ViewUtil.findById(view, R.id.empty_search);
|
||||
|
||||
// add padding to avoid content hidden behind system bars
|
||||
ViewUtil.applyWindowInsets(list, true, archive, true, true);
|
||||
@@ -145,22 +140,25 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
|
||||
updateReminders();
|
||||
|
||||
if (requireActivity().getIntent().getIntExtra(RELOAD_LIST, 0) == 1
|
||||
&& !chatlistJustLoaded) {
|
||||
if (requireActivity().getIntent().getIntExtra(RELOAD_LIST, 0) == 1 && !chatlistJustLoaded) {
|
||||
loadChatlist();
|
||||
reloadTimerInstantly = false;
|
||||
}
|
||||
chatlistJustLoaded = false;
|
||||
|
||||
reloadTimer = new Timer();
|
||||
reloadTimer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Util.runOnMain(() -> {
|
||||
list.getAdapter().notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
}, reloadTimerInstantly? 0 : 60 * 1000, 60 * 1000);
|
||||
reloadTimer.scheduleAtFixedRate(
|
||||
new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Util.runOnMain(
|
||||
() -> {
|
||||
list.getAdapter().notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
},
|
||||
reloadTimerInstantly ? 0 : 60 * 1000,
|
||||
60 * 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -184,7 +182,8 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
|
||||
@SuppressLint({"StaticFieldLeak", "NewApi"})
|
||||
private void updateReminders() {
|
||||
// by the time onPostExecute() is asynchronously run, getActivity() might return null, so get the activity here:
|
||||
// by the time onPostExecute() is asynchronously run, getActivity() might return null, so get
|
||||
// the activity here:
|
||||
Activity activity = requireActivity();
|
||||
new AsyncTask<Context, Void, Void>() {
|
||||
@Override
|
||||
@@ -204,22 +203,29 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (!Prefs.getBooleanPreference(activity, Prefs.ASKED_FOR_NOTIFICATION_PERMISSION, false)) {
|
||||
if (!Prefs.getBooleanPreference(
|
||||
activity, Prefs.ASKED_FOR_NOTIFICATION_PERMISSION, false)) {
|
||||
Prefs.setBooleanPreference(activity, Prefs.ASKED_FOR_NOTIFICATION_PERMISSION, true);
|
||||
Permissions.with(activity)
|
||||
.request(Manifest.permission.POST_NOTIFICATIONS)
|
||||
.ifNecessary()
|
||||
.onAllGranted(() -> {
|
||||
DozeReminder.maybeAskDirectly(activity);
|
||||
})
|
||||
.onAnyDenied(() -> {
|
||||
final DcContext dcContext = DcHelper.getContext(activity);
|
||||
DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
|
||||
msg.setText("\uD83D\uDC49 "+activity.getString(R.string.notifications_disabled)+" \uD83D\uDC48\n\n"
|
||||
+activity.getString(R.string.perm_explain_access_to_notifications_denied));
|
||||
dcContext.addDeviceMsg("android.notifications-disabled", msg);
|
||||
})
|
||||
.execute();
|
||||
.request(Manifest.permission.POST_NOTIFICATIONS)
|
||||
.ifNecessary()
|
||||
.onAllGranted(
|
||||
() -> {
|
||||
DozeReminder.maybeAskDirectly(activity);
|
||||
})
|
||||
.onAnyDenied(
|
||||
() -> {
|
||||
final DcContext dcContext = DcHelper.getContext(activity);
|
||||
DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
|
||||
msg.setText(
|
||||
"\uD83D\uDC49 "
|
||||
+ activity.getString(R.string.notifications_disabled)
|
||||
+ " \uD83D\uDC48\n\n"
|
||||
+ activity.getString(
|
||||
R.string.perm_explain_access_to_notifications_denied));
|
||||
dcContext.addDeviceMsg("android.notifications-disabled", msg);
|
||||
})
|
||||
.execute();
|
||||
} else {
|
||||
DozeReminder.maybeAskDirectly(activity);
|
||||
}
|
||||
@@ -233,6 +239,7 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
private final Object loadChatlistLock = new Object();
|
||||
private boolean inLoadChatlist;
|
||||
private boolean needsAnotherLoad;
|
||||
|
||||
public void loadChatlistAsync() {
|
||||
synchronized (loadChatlistLock) {
|
||||
needsAnotherLoad = true;
|
||||
@@ -243,21 +250,22 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
inLoadChatlist = true;
|
||||
}
|
||||
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
while(true) {
|
||||
synchronized (loadChatlistLock) {
|
||||
if (!needsAnotherLoad) {
|
||||
inLoadChatlist = false;
|
||||
return;
|
||||
}
|
||||
needsAnotherLoad = false;
|
||||
}
|
||||
Util.runOnAnyBackgroundThread(
|
||||
() -> {
|
||||
while (true) {
|
||||
synchronized (loadChatlistLock) {
|
||||
if (!needsAnotherLoad) {
|
||||
inLoadChatlist = false;
|
||||
return;
|
||||
}
|
||||
needsAnotherLoad = false;
|
||||
}
|
||||
|
||||
Log.i(TAG, "executing debounced chatlist loading");
|
||||
loadChatlist();
|
||||
Util.sleep(100);
|
||||
}
|
||||
});
|
||||
Log.i(TAG, "executing debounced chatlist loading");
|
||||
loadChatlist();
|
||||
Util.sleep(100);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadChatlist() {
|
||||
@@ -272,32 +280,36 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
|
||||
Context context = getContext();
|
||||
if (context == null) {
|
||||
// can't load chat list at this time, see: https://github.com/deltachat/deltachat-android/issues/2012
|
||||
// can't load chat list at this time, see:
|
||||
// https://github.com/deltachat/deltachat-android/issues/2012
|
||||
Log.w(TAG, "Ignoring call to loadChatlist()");
|
||||
return;
|
||||
}
|
||||
DcChatlist chatlist = DcHelper.getContext(context).getChatlist(listflags, queryFilter.isEmpty() ? null : queryFilter, 0);
|
||||
DcChatlist chatlist =
|
||||
DcHelper.getContext(context)
|
||||
.getChatlist(listflags, queryFilter.isEmpty() ? null : queryFilter, 0);
|
||||
|
||||
Util.runOnMain(() -> {
|
||||
if (chatlist.getCnt() <= 0 && TextUtils.isEmpty(queryFilter)) {
|
||||
list.setVisibility(View.INVISIBLE);
|
||||
emptyState.setVisibility(View.VISIBLE);
|
||||
emptySearch.setVisibility(View.INVISIBLE);
|
||||
fab.startPulse(3 * 1000);
|
||||
} else if (chatlist.getCnt() <= 0 && !TextUtils.isEmpty(queryFilter)) {
|
||||
list.setVisibility(View.INVISIBLE);
|
||||
emptyState.setVisibility(View.GONE);
|
||||
emptySearch.setVisibility(View.VISIBLE);
|
||||
emptySearch.setText(getString(R.string.search_no_result_for_x, queryFilter));
|
||||
} else {
|
||||
list.setVisibility(View.VISIBLE);
|
||||
emptyState.setVisibility(View.GONE);
|
||||
emptySearch.setVisibility(View.INVISIBLE);
|
||||
fab.stopPulse();
|
||||
}
|
||||
Util.runOnMain(
|
||||
() -> {
|
||||
if (chatlist.getCnt() <= 0 && TextUtils.isEmpty(queryFilter)) {
|
||||
list.setVisibility(View.INVISIBLE);
|
||||
emptyState.setVisibility(View.VISIBLE);
|
||||
emptySearch.setVisibility(View.INVISIBLE);
|
||||
fab.startPulse(3 * 1000);
|
||||
} else if (chatlist.getCnt() <= 0 && !TextUtils.isEmpty(queryFilter)) {
|
||||
list.setVisibility(View.INVISIBLE);
|
||||
emptyState.setVisibility(View.GONE);
|
||||
emptySearch.setVisibility(View.VISIBLE);
|
||||
emptySearch.setText(getString(R.string.search_no_result_for_x, queryFilter));
|
||||
} else {
|
||||
list.setVisibility(View.VISIBLE);
|
||||
emptyState.setVisibility(View.GONE);
|
||||
emptySearch.setVisibility(View.INVISIBLE);
|
||||
fab.stopPulse();
|
||||
}
|
||||
|
||||
((ConversationListAdapter)list.getAdapter()).changeData(chatlist);
|
||||
});
|
||||
((ConversationListAdapter) list.getAdapter()).changeData(chatlist);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -307,12 +319,12 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
|
||||
@Override
|
||||
protected void setFabVisibility(boolean isActionMode) {
|
||||
fab.setVisibility((isActionMode || !archive)? View.VISIBLE : View.GONE);
|
||||
fab.setVisibility((isActionMode || !archive) ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(ConversationListItem item) {
|
||||
onItemClick(item.getChatId());
|
||||
onItemClick(item.getChatId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -322,14 +334,15 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
|
||||
@Override
|
||||
public void onSwitchToArchive() {
|
||||
((ConversationSelectedListener)requireActivity()).onSwitchToArchive();
|
||||
((ConversationSelectedListener) requireActivity()).onSwitchToArchive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleEvent(@NonNull DcEvent event) {
|
||||
final int accId = event.getAccountId();
|
||||
if (event.getId() == DcContext.DC_EVENT_CHAT_DELETED) {
|
||||
DcHelper.getNotificationCenter(requireActivity()).removeNotifications(accId, event.getData1Int());
|
||||
DcHelper.getNotificationCenter(requireActivity())
|
||||
.removeNotifications(accId, event.getData1Int());
|
||||
} else if (accId != DcHelper.getContext(requireActivity()).getAccountId()) {
|
||||
Activity activity = getActivity();
|
||||
if (activity instanceof ConversationListActivity) {
|
||||
@@ -352,7 +365,4 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
loadChatlistAsync();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -30,17 +30,16 @@ import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.amulyakhare.textdrawable.TextDrawable;
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContact;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcLot;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import org.thoughtcrime.securesms.components.AvatarView;
|
||||
import org.thoughtcrime.securesms.components.DeliveryStatusView;
|
||||
import org.thoughtcrime.securesms.components.FromTextView;
|
||||
@@ -53,25 +52,22 @@ import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
public class ConversationListItem extends RelativeLayout
|
||||
implements BindableConversationListItem, Unbindable
|
||||
{
|
||||
private final static Typeface BOLD_TYPEFACE = Typeface.create("sans-serif-medium", Typeface.NORMAL);
|
||||
private final static Typeface LIGHT_TYPEFACE = Typeface.create("sans-serif", Typeface.NORMAL);
|
||||
implements BindableConversationListItem, Unbindable {
|
||||
private static final Typeface BOLD_TYPEFACE =
|
||||
Typeface.create("sans-serif-medium", Typeface.NORMAL);
|
||||
private static final Typeface LIGHT_TYPEFACE = Typeface.create("sans-serif", Typeface.NORMAL);
|
||||
|
||||
private Set<Long> selectedThreads;
|
||||
private long chatId;
|
||||
private int msgId;
|
||||
private TextView subjectView;
|
||||
private FromTextView fromView;
|
||||
private TextView dateView;
|
||||
private TextView archivedBadgeView;
|
||||
private TextView requestBadgeView;
|
||||
private Set<Long> selectedThreads;
|
||||
private long chatId;
|
||||
private int msgId;
|
||||
private TextView subjectView;
|
||||
private FromTextView fromView;
|
||||
private TextView dateView;
|
||||
private TextView archivedBadgeView;
|
||||
private TextView requestBadgeView;
|
||||
private DeliveryStatusView deliveryStatusIndicator;
|
||||
private ImageView unreadIndicator;
|
||||
private ImageView unreadIndicator;
|
||||
|
||||
private AvatarView avatar;
|
||||
|
||||
@@ -86,69 +82,72 @@ public class ConversationListItem extends RelativeLayout
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
this.subjectView = findViewById(R.id.subject);
|
||||
this.fromView = findViewById(R.id.from_text);
|
||||
this.dateView = findViewById(R.id.date);
|
||||
this.subjectView = findViewById(R.id.subject);
|
||||
this.fromView = findViewById(R.id.from_text);
|
||||
this.dateView = findViewById(R.id.date);
|
||||
this.deliveryStatusIndicator = new DeliveryStatusView(findViewById(R.id.delivery_indicator));
|
||||
this.avatar = findViewById(R.id.avatar);
|
||||
this.archivedBadgeView = findViewById(R.id.archived_badge);
|
||||
this.requestBadgeView = findViewById(R.id.request_badge);
|
||||
this.unreadIndicator = findViewById(R.id.unread_indicator);
|
||||
this.avatar = findViewById(R.id.avatar);
|
||||
this.archivedBadgeView = findViewById(R.id.archived_badge);
|
||||
this.requestBadgeView = findViewById(R.id.request_badge);
|
||||
this.unreadIndicator = findViewById(R.id.unread_indicator);
|
||||
|
||||
ViewUtil.setTextViewGravityStart(this.fromView, getContext());
|
||||
ViewUtil.setTextViewGravityStart(this.subjectView, getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull ThreadRecord thread,
|
||||
int msgId,
|
||||
@NonNull DcLot dcSummary,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Set<Long> selectedThreads,
|
||||
boolean batchMode)
|
||||
{
|
||||
public void bind(
|
||||
@NonNull ThreadRecord thread,
|
||||
int msgId,
|
||||
@NonNull DcLot dcSummary,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Set<Long> selectedThreads,
|
||||
boolean batchMode) {
|
||||
bind(thread, msgId, dcSummary, glideRequests, selectedThreads, batchMode, null);
|
||||
}
|
||||
|
||||
public void bind(@NonNull ThreadRecord thread,
|
||||
int msgId,
|
||||
@NonNull DcLot dcSummary,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Set<Long> selectedThreads,
|
||||
boolean batchMode,
|
||||
@Nullable String highlightSubstring)
|
||||
{
|
||||
this.selectedThreads = selectedThreads;
|
||||
Recipient recipient = thread.getRecipient();
|
||||
this.chatId = thread.getThreadId();
|
||||
this.msgId = msgId;
|
||||
public void bind(
|
||||
@NonNull ThreadRecord thread,
|
||||
int msgId,
|
||||
@NonNull DcLot dcSummary,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Set<Long> selectedThreads,
|
||||
boolean batchMode,
|
||||
@Nullable String highlightSubstring) {
|
||||
this.selectedThreads = selectedThreads;
|
||||
Recipient recipient = thread.getRecipient();
|
||||
this.chatId = thread.getThreadId();
|
||||
this.msgId = msgId;
|
||||
|
||||
int state = dcSummary.getState();
|
||||
int state = dcSummary.getState();
|
||||
|
||||
if (highlightSubstring != null) {
|
||||
this.fromView.setText(getHighlightedSpan(recipient.getName(), highlightSubstring));
|
||||
} else {
|
||||
this.fromView.setText(recipient, state!=DcMsg.DC_STATE_IN_FRESH);
|
||||
this.fromView.setText(recipient, state != DcMsg.DC_STATE_IN_FRESH);
|
||||
}
|
||||
|
||||
subjectView.setVisibility(chatId == DcChat.DC_CHAT_ID_ARCHIVED_LINK? GONE : VISIBLE);
|
||||
subjectView.setVisibility(chatId == DcChat.DC_CHAT_ID_ARCHIVED_LINK ? GONE : VISIBLE);
|
||||
this.subjectView.setText(thread.getDisplayBody());
|
||||
this.subjectView.setTypeface(state==DcMsg.DC_STATE_IN_FRESH ? BOLD_TYPEFACE : LIGHT_TYPEFACE);
|
||||
this.subjectView.setTextColor(state==DcMsg.DC_STATE_IN_FRESH ? ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_unread_color)
|
||||
: ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_subject_color));
|
||||
this.subjectView.setTypeface(state == DcMsg.DC_STATE_IN_FRESH ? BOLD_TYPEFACE : LIGHT_TYPEFACE);
|
||||
this.subjectView.setTextColor(
|
||||
state == DcMsg.DC_STATE_IN_FRESH
|
||||
? ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_unread_color)
|
||||
: ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_subject_color));
|
||||
|
||||
if (thread.getDate() > 0) {
|
||||
CharSequence date = DateUtils.getBriefRelativeTimeSpanString(getContext(), thread.getDate());
|
||||
dateView.setText(date);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
dateView.setText("");
|
||||
}
|
||||
|
||||
dateView.setCompoundDrawablesWithIntrinsicBounds(
|
||||
thread.isSendingLocations()? R.drawable.ic_location_chatlist : 0, 0,
|
||||
thread.getVisibility()==DcChat.DC_CHAT_VISIBILITY_PINNED? R.drawable.ic_pinned_chatlist : 0, 0
|
||||
);
|
||||
thread.isSendingLocations() ? R.drawable.ic_location_chatlist : 0, 0,
|
||||
thread.getVisibility() == DcChat.DC_CHAT_VISIBILITY_PINNED
|
||||
? R.drawable.ic_pinned_chatlist
|
||||
: 0,
|
||||
0);
|
||||
|
||||
setStatusIcons(thread, state);
|
||||
setBatchState(batchMode);
|
||||
@@ -159,22 +158,22 @@ public class ConversationListItem extends RelativeLayout
|
||||
DcContact contact = recipient.getDcContact();
|
||||
avatar.setSeenRecently(contact != null && contact.wasSeenRecently());
|
||||
|
||||
DcChat dcChat = DcHelper.getContext(getContext()).getChat((int)chatId);
|
||||
DcChat dcChat = DcHelper.getContext(getContext()).getChat((int) chatId);
|
||||
boolean isProtected = dcChat.isDeviceTalk() || dcChat.isSelfTalk();
|
||||
|
||||
fromView.setCompoundDrawablesWithIntrinsicBounds(
|
||||
thread.isMuted()? R.drawable.ic_volume_off_grey600_18dp : 0,
|
||||
thread.isMuted() ? R.drawable.ic_volume_off_grey600_18dp : 0,
|
||||
0,
|
||||
isProtected? R.drawable.ic_verified : 0,
|
||||
isProtected ? R.drawable.ic_verified : 0,
|
||||
0);
|
||||
}
|
||||
|
||||
public void bind(@NonNull DcContact contact,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@Nullable String highlightSubstring)
|
||||
{
|
||||
public void bind(
|
||||
@NonNull DcContact contact,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@Nullable String highlightSubstring) {
|
||||
this.selectedThreads = Collections.emptySet();
|
||||
Recipient recipient = new Recipient(getContext(), contact);
|
||||
Recipient recipient = new Recipient(getContext(), contact);
|
||||
|
||||
fromView.setText(getHighlightedSpan(contact.getDisplayName(), highlightSubstring));
|
||||
fromView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
@@ -191,14 +190,14 @@ public class ConversationListItem extends RelativeLayout
|
||||
avatar.setSeenRecently(contact.wasSeenRecently());
|
||||
}
|
||||
|
||||
public void bind(@NonNull DcMsg messageResult,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@Nullable String highlightSubstring)
|
||||
{
|
||||
public void bind(
|
||||
@NonNull DcMsg messageResult,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@Nullable String highlightSubstring) {
|
||||
DcContext dcContext = DcHelper.getContext(getContext());
|
||||
DcContact sender = dcContext.getContact(messageResult.getFromId());
|
||||
this.selectedThreads = Collections.emptySet();
|
||||
Recipient recipient = new Recipient(getContext(), sender);
|
||||
Recipient recipient = new Recipient(getContext(), sender);
|
||||
|
||||
fromView.setText(recipient, true);
|
||||
fromView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
@@ -206,10 +205,10 @@ public class ConversationListItem extends RelativeLayout
|
||||
subjectView.setText(getHighlightedSpan(messageResult.getSummarytext(512), highlightSubstring));
|
||||
|
||||
long timestamp = messageResult.getTimestamp();
|
||||
if(timestamp>0) {
|
||||
dateView.setText(DateUtils.getBriefRelativeTimeSpanString(getContext(), messageResult.getTimestamp()));
|
||||
}
|
||||
else {
|
||||
if (timestamp > 0) {
|
||||
dateView.setText(
|
||||
DateUtils.getBriefRelativeTimeSpanString(getContext(), messageResult.getTimestamp()));
|
||||
} else {
|
||||
dateView.setText("");
|
||||
}
|
||||
dateView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
@@ -224,8 +223,7 @@ public class ConversationListItem extends RelativeLayout
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind() {
|
||||
}
|
||||
public void unbind() {}
|
||||
|
||||
private void setBatchState(boolean batch) {
|
||||
setSelected(batch && selectedThreads.contains(chatId));
|
||||
@@ -240,19 +238,15 @@ public class ConversationListItem extends RelativeLayout
|
||||
}
|
||||
|
||||
private void setStatusIcons(ThreadRecord thread, int state) {
|
||||
if (thread.getVisibility() == DcChat.DC_CHAT_VISIBILITY_ARCHIVED)
|
||||
{
|
||||
if (thread.getVisibility() == DcChat.DC_CHAT_VISIBILITY_ARCHIVED) {
|
||||
archivedBadgeView.setVisibility(View.VISIBLE);
|
||||
requestBadgeView.setVisibility(thread.isContactRequest() ? View.VISIBLE : View.GONE);
|
||||
deliveryStatusIndicator.setNone();
|
||||
}
|
||||
else if (thread.isContactRequest()) {
|
||||
} else if (thread.isContactRequest()) {
|
||||
requestBadgeView.setVisibility(View.VISIBLE);
|
||||
archivedBadgeView.setVisibility(View.GONE);
|
||||
deliveryStatusIndicator.setNone();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
requestBadgeView.setVisibility(View.GONE);
|
||||
archivedBadgeView.setVisibility(View.GONE);
|
||||
|
||||
@@ -274,43 +268,50 @@ public class ConversationListItem extends RelativeLayout
|
||||
}
|
||||
|
||||
int unreadCount = thread.getUnreadCount();
|
||||
if(unreadCount==0 || thread.isContactRequest()) {
|
||||
if (unreadCount == 0 || thread.isContactRequest()) {
|
||||
unreadIndicator.setVisibility(View.GONE);
|
||||
} else {
|
||||
final int color;
|
||||
if(thread.isMuted() || chatId == DcChat.DC_CHAT_ID_ARCHIVED_LINK){
|
||||
color = getResources().getColor(ThemeUtil.isDarkTheme(getContext()) ? R.color.unread_count_muted_dark : R.color.unread_count_muted);
|
||||
if (thread.isMuted() || chatId == DcChat.DC_CHAT_ID_ARCHIVED_LINK) {
|
||||
color =
|
||||
getResources()
|
||||
.getColor(
|
||||
ThemeUtil.isDarkTheme(getContext())
|
||||
? R.color.unread_count_muted_dark
|
||||
: R.color.unread_count_muted);
|
||||
} else {
|
||||
final TypedArray attrs = getContext().obtainStyledAttributes(new int[] {
|
||||
R.attr.conversation_list_item_unreadcount_color,
|
||||
});
|
||||
final TypedArray attrs =
|
||||
getContext()
|
||||
.obtainStyledAttributes(
|
||||
new int[] {
|
||||
R.attr.conversation_list_item_unreadcount_color,
|
||||
});
|
||||
color = attrs.getColor(0, Color.BLACK);
|
||||
}
|
||||
unreadIndicator.setImageDrawable(TextDrawable.builder()
|
||||
.beginConfig()
|
||||
.width(ViewUtil.dpToPx(getContext(), 24))
|
||||
.height(ViewUtil.dpToPx(getContext(), 24))
|
||||
.textColor(Color.WHITE)
|
||||
.bold()
|
||||
.endConfig()
|
||||
.buildRound(String.valueOf(unreadCount), color));
|
||||
unreadIndicator.setImageDrawable(
|
||||
TextDrawable.builder()
|
||||
.beginConfig()
|
||||
.width(ViewUtil.dpToPx(getContext(), 24))
|
||||
.height(ViewUtil.dpToPx(getContext(), 24))
|
||||
.textColor(Color.WHITE)
|
||||
.bold()
|
||||
.endConfig()
|
||||
.buildRound(String.valueOf(unreadCount), color));
|
||||
unreadIndicator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setBgColor(ThreadRecord thread) {
|
||||
int bg = R.attr.conversation_list_item_background;
|
||||
if (thread!=null && thread.getVisibility()==DcChat.DC_CHAT_VISIBILITY_PINNED) {
|
||||
bg = R.attr.pinned_list_item_background;
|
||||
if (thread != null && thread.getVisibility() == DcChat.DC_CHAT_VISIBILITY_PINNED) {
|
||||
bg = R.attr.pinned_list_item_background;
|
||||
}
|
||||
try (TypedArray ta = getContext().obtainStyledAttributes(new int[]{bg})) {
|
||||
try (TypedArray ta = getContext().obtainStyledAttributes(new int[] {bg})) {
|
||||
ViewUtil.setBackground(this, ta.getDrawable(0));
|
||||
}
|
||||
}
|
||||
|
||||
private Spanned getHighlightedSpan(@Nullable String value,
|
||||
@Nullable String highlight)
|
||||
{
|
||||
private Spanned getHighlightedSpan(@Nullable String value, @Nullable String highlight) {
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
return new SpannableString("");
|
||||
}
|
||||
@@ -321,11 +322,11 @@ public class ConversationListItem extends RelativeLayout
|
||||
return new SpannableString(value);
|
||||
}
|
||||
|
||||
String normalizedValue = value.toLowerCase(Util.getLocale());
|
||||
String normalizedTest = highlight.toLowerCase(Util.getLocale());
|
||||
String normalizedValue = value.toLowerCase(Util.getLocale());
|
||||
String normalizedTest = highlight.toLowerCase(Util.getLocale());
|
||||
|
||||
Spannable spanned = new SpannableString(value);
|
||||
int searchStartIndex = 0;
|
||||
Spannable spanned = new SpannableString(value);
|
||||
int searchStartIndex = 0;
|
||||
|
||||
for (String token : normalizedTest.split(" ")) {
|
||||
if (token.trim().isEmpty()) continue;
|
||||
@@ -337,7 +338,8 @@ public class ConversationListItem extends RelativeLayout
|
||||
|
||||
if (start >= 0) {
|
||||
int end = Math.min(start + token.length(), spanned.length());
|
||||
spanned.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
spanned.setSpan(
|
||||
new StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
searchStartIndex = end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
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 class ConversationListItemInboxZero extends LinearLayout implements BindableConversationListItem{
|
||||
public class ConversationListItemInboxZero extends LinearLayout
|
||||
implements BindableConversationListItem {
|
||||
public ConversationListItemInboxZero(Context context) {
|
||||
super(context);
|
||||
}
|
||||
@@ -24,21 +20,25 @@ public class ConversationListItemInboxZero extends LinearLayout implements Binda
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ConversationListItemInboxZero(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
public ConversationListItemInboxZero(
|
||||
Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public ConversationListItemInboxZero(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
public ConversationListItemInboxZero(
|
||||
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind() {
|
||||
|
||||
}
|
||||
public void unbind() {}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull ThreadRecord thread, int msgId, @NonNull DcLot dcSummary, @NonNull GlideRequests glideRequests, @NonNull Set<Long> selectedThreads, boolean batchMode) {
|
||||
|
||||
}
|
||||
public void bind(
|
||||
@NonNull ThreadRecord thread,
|
||||
int msgId,
|
||||
@NonNull DcLot dcSummary,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Set<Long> selectedThreads,
|
||||
boolean batchMode) {}
|
||||
}
|
||||
|
||||
@@ -4,27 +4,24 @@ import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* "Relaying" means "Forwarding or Sharing".
|
||||
*
|
||||
* When forwarding or sharing, we show the ConversationListActivity to the user.
|
||||
* However, ConversationListActivity has `launchMode="singleTask"`, which means that this will
|
||||
* destroy the existing ConversationListActivity.
|
||||
* <p>When forwarding or sharing, we show the ConversationListActivity to the user. However,
|
||||
* ConversationListActivity has `launchMode="singleTask"`, which means that this will destroy the
|
||||
* existing ConversationListActivity.
|
||||
*
|
||||
* In API 20-29, `startActivityForResult()` could be used instead of `startActivity()`
|
||||
* to override this behavior and get two instances of ConversationListActivity.
|
||||
* <p>In API 20-29, `startActivityForResult()` could be used instead of `startActivity()` to
|
||||
* override this behavior and get two instances of ConversationListActivity.
|
||||
*
|
||||
* As this is not possible anymore starting with API 30, we needed another solution, and created
|
||||
* <p>As this is not possible anymore starting with API 30, we needed another solution, and created
|
||||
* this activity here.
|
||||
*
|
||||
* See https://github.com/deltachat/deltachat-android/issues/1704.
|
||||
* <p>See https://github.com/deltachat/deltachat-android/issues/1704.
|
||||
*/
|
||||
|
||||
public class ConversationListRelayingActivity extends ConversationListActivity {
|
||||
static WeakReference<ConversationListRelayingActivity> INSTANCE = null;
|
||||
|
||||
@@ -42,7 +39,8 @@ public class ConversationListRelayingActivity extends ConversationListActivity {
|
||||
|
||||
// =================== Static Methods ===================
|
||||
public static void start(Fragment fragment, Intent intent) {
|
||||
intent.setComponent(new ComponentName(fragment.getContext(), ConversationListRelayingActivity.class));
|
||||
intent.setComponent(
|
||||
new ComponentName(fragment.getContext(), ConversationListRelayingActivity.class));
|
||||
fragment.startActivity(intent);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,30 +4,32 @@ import android.animation.ValueAnimator;
|
||||
import android.content.res.Resources;
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
final class ConversationSwipeAnimationHelper {
|
||||
|
||||
static final float TRIGGER_DX = dpToPx(64);
|
||||
static final float MAX_DX = dpToPx(96);
|
||||
static final float MAX_DX = dpToPx(96);
|
||||
|
||||
private static final float REPLY_SCALE_OVERSHOOT = 1.8f;
|
||||
private static final float REPLY_SCALE_MAX = 1.2f;
|
||||
private static final float REPLY_SCALE_MIN = 1f;
|
||||
private static final long REPLY_SCALE_OVERSHOOT_DURATION = 200;
|
||||
private static final float REPLY_SCALE_OVERSHOOT = 1.8f;
|
||||
private static final float REPLY_SCALE_MAX = 1.2f;
|
||||
private static final float REPLY_SCALE_MIN = 1f;
|
||||
private static final long REPLY_SCALE_OVERSHOOT_DURATION = 200;
|
||||
|
||||
private static final Interpolator BUBBLE_INTERPOLATOR = new BubblePositionInterpolator(0f, TRIGGER_DX, MAX_DX);
|
||||
private static final Interpolator REPLY_ALPHA_INTERPOLATOR = new ClampingLinearInterpolator(0f, 1f, 1f);
|
||||
private static final Interpolator REPLY_TRANSITION_INTERPOLATOR = new ClampingLinearInterpolator(0f, dpToPx(10));
|
||||
private static final Interpolator AVATAR_INTERPOLATOR = new ClampingLinearInterpolator(0f, dpToPx(8));
|
||||
private static final Interpolator REPLY_SCALE_INTERPOLATOR = new ClampingLinearInterpolator(REPLY_SCALE_MIN, REPLY_SCALE_MAX);
|
||||
private static final Interpolator BUBBLE_INTERPOLATOR =
|
||||
new BubblePositionInterpolator(0f, TRIGGER_DX, MAX_DX);
|
||||
private static final Interpolator REPLY_ALPHA_INTERPOLATOR =
|
||||
new ClampingLinearInterpolator(0f, 1f, 1f);
|
||||
private static final Interpolator REPLY_TRANSITION_INTERPOLATOR =
|
||||
new ClampingLinearInterpolator(0f, dpToPx(10));
|
||||
private static final Interpolator AVATAR_INTERPOLATOR =
|
||||
new ClampingLinearInterpolator(0f, dpToPx(8));
|
||||
private static final Interpolator REPLY_SCALE_INTERPOLATOR =
|
||||
new ClampingLinearInterpolator(REPLY_SCALE_MIN, REPLY_SCALE_MAX);
|
||||
|
||||
private ConversationSwipeAnimationHelper() {
|
||||
}
|
||||
private ConversationSwipeAnimationHelper() {}
|
||||
|
||||
public static void update(@NonNull ConversationItem conversationItem, float dx, float sign) {
|
||||
float progress = dx / TRIGGER_DX;
|
||||
@@ -46,11 +48,13 @@ final class ConversationSwipeAnimationHelper {
|
||||
bodyBubble.setTranslationX(BUBBLE_INTERPOLATOR.getInterpolation(dx) * sign);
|
||||
}
|
||||
|
||||
private static void updateReactionsTransition(@NonNull View reactionsContainer, float dx, float sign) {
|
||||
private static void updateReactionsTransition(
|
||||
@NonNull View reactionsContainer, float dx, float sign) {
|
||||
reactionsContainer.setTranslationX(BUBBLE_INTERPOLATOR.getInterpolation(dx) * sign);
|
||||
}
|
||||
|
||||
private static void updateReplyIconTransition(@NonNull View replyIcon, float dx, float progress, float sign) {
|
||||
private static void updateReplyIconTransition(
|
||||
@NonNull View replyIcon, float dx, float progress, float sign) {
|
||||
if (progress > 0.05f) {
|
||||
replyIcon.setAlpha(REPLY_ALPHA_INTERPOLATOR.getInterpolation(progress));
|
||||
} else replyIcon.setAlpha(0f);
|
||||
@@ -64,21 +68,21 @@ final class ConversationSwipeAnimationHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateContactPhotoHolderTransition(@Nullable View contactPhotoHolder,
|
||||
float progress,
|
||||
float sign)
|
||||
{
|
||||
private static void updateContactPhotoHolderTransition(
|
||||
@Nullable View contactPhotoHolder, float progress, float sign) {
|
||||
if (contactPhotoHolder == null) return;
|
||||
contactPhotoHolder.setTranslationX(AVATAR_INTERPOLATOR.getInterpolation(progress) * sign);
|
||||
}
|
||||
|
||||
private static void triggerReplyIcon(@NonNull View replyIcon) {
|
||||
ValueAnimator animator = ValueAnimator.ofFloat(REPLY_SCALE_MAX, REPLY_SCALE_OVERSHOOT, REPLY_SCALE_MAX);
|
||||
ValueAnimator animator =
|
||||
ValueAnimator.ofFloat(REPLY_SCALE_MAX, REPLY_SCALE_OVERSHOOT, REPLY_SCALE_MAX);
|
||||
animator.setDuration(REPLY_SCALE_OVERSHOOT_DURATION);
|
||||
animator.addUpdateListener(animation -> {
|
||||
replyIcon.setScaleX((float) animation.getAnimatedValue());
|
||||
replyIcon.setScaleY((float) animation.getAnimatedValue());
|
||||
});
|
||||
animator.addUpdateListener(
|
||||
animation -> {
|
||||
replyIcon.setScaleX((float) animation.getAnimatedValue());
|
||||
replyIcon.setScaleY((float) animation.getAnimatedValue());
|
||||
});
|
||||
animator.start();
|
||||
}
|
||||
|
||||
@@ -93,9 +97,9 @@ final class ConversationSwipeAnimationHelper {
|
||||
private final float end;
|
||||
|
||||
private BubblePositionInterpolator(float start, float middle, float end) {
|
||||
this.start = start;
|
||||
this.start = start;
|
||||
this.middle = middle;
|
||||
this.end = end;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,11 +109,11 @@ final class ConversationSwipeAnimationHelper {
|
||||
} else if (input < middle) {
|
||||
return input;
|
||||
} else {
|
||||
float segmentLength = end - middle;
|
||||
float segmentTraveled = input - middle;
|
||||
float segmentLength = end - middle;
|
||||
float segmentTraveled = input - middle;
|
||||
float segmentCompletion = segmentTraveled / segmentLength;
|
||||
float scaleDownFactor = middle / (input * 2);
|
||||
float output = middle + (segmentLength * segmentCompletion * scaleDownFactor);
|
||||
float scaleDownFactor = middle / (input * 2);
|
||||
float output = middle + (segmentLength * segmentCompletion * scaleDownFactor);
|
||||
|
||||
return Math.min(output, end);
|
||||
}
|
||||
@@ -128,10 +132,10 @@ final class ConversationSwipeAnimationHelper {
|
||||
}
|
||||
|
||||
ClampingLinearInterpolator(float start, float end, float scale) {
|
||||
slope = (end - start) * scale;
|
||||
slope = (end - start) * scale;
|
||||
yIntercept = start;
|
||||
max = Math.max(start, end);
|
||||
min = Math.min(start, end);
|
||||
max = Math.max(start, end);
|
||||
min = Math.min(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,5 +143,4 @@ final class ConversationSwipeAnimationHelper {
|
||||
return Util.clamp(slope * input + yIntercept, min, max);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
@@ -8,14 +7,11 @@ import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContact;
|
||||
import com.b44t.messenger.DcContext;
|
||||
|
||||
import org.thoughtcrime.securesms.components.AvatarView;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
@@ -24,16 +20,14 @@ import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class ConversationTitleView extends RelativeLayout {
|
||||
|
||||
private View content;
|
||||
private ImageView back;
|
||||
private AvatarView avatar;
|
||||
private TextView title;
|
||||
private TextView subtitle;
|
||||
private ImageView ephemeralIcon;
|
||||
private View content;
|
||||
private ImageView back;
|
||||
private AvatarView avatar;
|
||||
private TextView title;
|
||||
private TextView subtitle;
|
||||
private ImageView ephemeralIcon;
|
||||
|
||||
public ConversationTitleView(Context context) {
|
||||
this(context, null);
|
||||
@@ -41,18 +35,17 @@ public class ConversationTitleView extends RelativeLayout {
|
||||
|
||||
public ConversationTitleView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
this.back = ViewUtil.findById(this, R.id.up_button);
|
||||
this.content = ViewUtil.findById(this, R.id.content);
|
||||
this.title = ViewUtil.findById(this, R.id.title);
|
||||
this.subtitle = ViewUtil.findById(this, R.id.subtitle);
|
||||
this.avatar = ViewUtil.findById(this, R.id.avatar);
|
||||
this.back = ViewUtil.findById(this, R.id.up_button);
|
||||
this.content = ViewUtil.findById(this, R.id.content);
|
||||
this.title = ViewUtil.findById(this, R.id.title);
|
||||
this.subtitle = ViewUtil.findById(this, R.id.subtitle);
|
||||
this.avatar = ViewUtil.findById(this, R.id.avatar);
|
||||
this.ephemeralIcon = ViewUtil.findById(this, R.id.ephemeral_icon);
|
||||
|
||||
ViewUtil.setTextViewGravityStart(this.title, getContext());
|
||||
@@ -75,21 +68,25 @@ public class ConversationTitleView extends RelativeLayout {
|
||||
} else if (dcChat.isInBroadcast()) {
|
||||
subtitleStr = context.getString(R.string.channel);
|
||||
} else if (dcChat.isOutBroadcast()) {
|
||||
subtitleStr = context.getResources().getQuantityString(R.plurals.n_recipients, chatContacts.length, chatContacts.length);
|
||||
} else if( dcChat.isMultiUser() ) {
|
||||
subtitleStr =
|
||||
context
|
||||
.getResources()
|
||||
.getQuantityString(R.plurals.n_recipients, chatContacts.length, chatContacts.length);
|
||||
} else if (dcChat.isMultiUser()) {
|
||||
if (chatContacts.length > 1 || Util.contains(chatContacts, DcContact.DC_CONTACT_ID_SELF)) {
|
||||
subtitleStr = context.getResources().getQuantityString(R.plurals.n_members, chatContacts.length, chatContacts.length);
|
||||
subtitleStr =
|
||||
context
|
||||
.getResources()
|
||||
.getQuantityString(R.plurals.n_members, chatContacts.length, chatContacts.length);
|
||||
} else {
|
||||
subtitleStr = "…";
|
||||
}
|
||||
} else if( chatContacts.length>=1 ) {
|
||||
if( dcChat.isSelfTalk() ) {
|
||||
} else if (chatContacts.length >= 1) {
|
||||
if (dcChat.isSelfTalk()) {
|
||||
subtitleStr = context.getString(R.string.chat_self_talk_subtitle);
|
||||
}
|
||||
else if( dcChat.isDeviceTalk() ) {
|
||||
} else if (dcChat.isDeviceTalk()) {
|
||||
subtitleStr = context.getString(R.string.device_talk_subtitle);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
DcContact dcContact = dcContext.getContact(chatContacts[0]);
|
||||
isOnline = dcContact.wasSeenRecently();
|
||||
if (!dcChat.isEncrypted()) {
|
||||
@@ -101,7 +98,9 @@ public class ConversationTitleView extends RelativeLayout {
|
||||
} else {
|
||||
long timestamp = dcContact.getLastSeen();
|
||||
if (timestamp >= 0) {
|
||||
subtitleStr = context.getString(R.string.last_seen_at, DateUtils.getExtendedTimeSpanString(context, timestamp));
|
||||
subtitleStr =
|
||||
context.getString(
|
||||
R.string.last_seen_at, DateUtils.getExtendedTimeSpanString(context, timestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,8 +108,8 @@ public class ConversationTitleView extends RelativeLayout {
|
||||
|
||||
avatar.setAvatar(glideRequests, new Recipient(getContext(), dcChat), false);
|
||||
avatar.setSeenRecently(isOnline);
|
||||
int imgLeft = dcChat.isMuted()? R.drawable.ic_volume_off_white_18dp : 0;
|
||||
int imgRight = dcChat.isSelfTalk() || dcChat.isDeviceTalk()? R.drawable.ic_verified : 0;
|
||||
int imgLeft = dcChat.isMuted() ? R.drawable.ic_volume_off_white_18dp : 0;
|
||||
int imgRight = dcChat.isSelfTalk() || dcChat.isDeviceTalk() ? R.drawable.ic_verified : 0;
|
||||
title.setCompoundDrawablesWithIntrinsicBounds(imgLeft, 0, imgRight, 0);
|
||||
if (!TextUtils.isEmpty(subtitleStr)) {
|
||||
subtitle.setText(subtitleStr);
|
||||
@@ -119,7 +118,7 @@ public class ConversationTitleView extends RelativeLayout {
|
||||
subtitle.setVisibility(View.GONE);
|
||||
}
|
||||
boolean isEphemeral = dcContext.getChatEphemeralTimer(chatId) != 0;
|
||||
ephemeralIcon.setVisibility(isEphemeral? View.VISIBLE : View.GONE);
|
||||
ephemeralIcon.setVisibility(isEphemeral ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
public void setTitle(@NonNull GlideRequests glideRequests, @NonNull DcContact contact) {
|
||||
|
||||
@@ -5,14 +5,13 @@ import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Set;
|
||||
import org.json.JSONObject;
|
||||
import org.thoughtcrime.securesms.components.DeliveryStatusView;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||
@@ -21,14 +20,10 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Set;
|
||||
|
||||
public class ConversationUpdateItem extends BaseConversationItem
|
||||
{
|
||||
private DeliveryStatusView deliveryStatusView;
|
||||
private AppCompatImageView appIcon;
|
||||
private int textColor;
|
||||
public class ConversationUpdateItem extends BaseConversationItem {
|
||||
private DeliveryStatusView deliveryStatusView;
|
||||
private AppCompatImageView appIcon;
|
||||
private int textColor;
|
||||
|
||||
public ConversationUpdateItem(Context context) {
|
||||
this(context, null);
|
||||
@@ -44,10 +39,9 @@ public class ConversationUpdateItem extends BaseConversationItem
|
||||
|
||||
initializeAttributes();
|
||||
|
||||
bodyText = findViewById(R.id.conversation_update_body);
|
||||
bodyText = findViewById(R.id.conversation_update_body);
|
||||
deliveryStatusView = new DeliveryStatusView(findViewById(R.id.delivery_indicator));
|
||||
appIcon = findViewById(R.id.app_icon);
|
||||
|
||||
appIcon = findViewById(R.id.app_icon);
|
||||
|
||||
bodyText.setOnLongClickListener(passthroughClickListener);
|
||||
bodyText.setOnClickListener(passthroughClickListener);
|
||||
@@ -58,24 +52,25 @@ public class ConversationUpdateItem extends BaseConversationItem
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull DcMsg messageRecord,
|
||||
@NonNull DcChat dcChat,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Set<DcMsg> batchSelected,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
boolean pulseUpdate,
|
||||
@Nullable AudioPlaybackViewModel playbackViewModel,
|
||||
AudioView.OnActionListener audioPlayPauseListener)
|
||||
{
|
||||
public void bind(
|
||||
@NonNull DcMsg messageRecord,
|
||||
@NonNull DcChat dcChat,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Set<DcMsg> batchSelected,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
boolean pulseUpdate,
|
||||
@Nullable AudioPlaybackViewModel playbackViewModel,
|
||||
AudioView.OnActionListener audioPlayPauseListener) {
|
||||
bindPartial(messageRecord, dcChat, batchSelected, pulseUpdate, conversationRecipient);
|
||||
setGenericInfoRecord(messageRecord);
|
||||
}
|
||||
|
||||
private void initializeAttributes() {
|
||||
final int[] attributes = new int[] {
|
||||
R.attr.conversation_item_update_text_color,
|
||||
};
|
||||
final TypedArray attrs = context.obtainStyledAttributes(attributes);
|
||||
final int[] attributes =
|
||||
new int[] {
|
||||
R.attr.conversation_item_update_text_color,
|
||||
};
|
||||
final TypedArray attrs = context.obtainStyledAttributes(attributes);
|
||||
|
||||
textColor = attrs.getColor(0, Color.WHITE);
|
||||
attrs.recycle();
|
||||
@@ -118,14 +113,13 @@ public class ConversationUpdateItem extends BaseConversationItem
|
||||
bodyText.setText(messageRecord.getDisplayBody());
|
||||
bodyText.setVisibility(VISIBLE);
|
||||
|
||||
if (messageRecord.isFailed()) deliveryStatusView.setFailed();
|
||||
else if (!messageRecord.isOutgoing()) deliveryStatusView.setNone();
|
||||
else if (messageRecord.isPreparing()) deliveryStatusView.setPreparing();
|
||||
else if (messageRecord.isPending()) deliveryStatusView.setPending();
|
||||
else deliveryStatusView.setNone();
|
||||
if (messageRecord.isFailed()) deliveryStatusView.setFailed();
|
||||
else if (!messageRecord.isOutgoing()) deliveryStatusView.setNone();
|
||||
else if (messageRecord.isPreparing()) deliveryStatusView.setPreparing();
|
||||
else if (messageRecord.isPending()) deliveryStatusView.setPending();
|
||||
else deliveryStatusView.setNone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind() {
|
||||
}
|
||||
public void unbind() {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
@@ -15,22 +14,19 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.request.target.SimpleTarget;
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import org.thoughtcrime.securesms.components.AvatarSelector;
|
||||
import org.thoughtcrime.securesms.components.InputAwareLayout;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
@@ -43,11 +39,6 @@ import org.thoughtcrime.securesms.scribbles.ScribbleActivity;
|
||||
import org.thoughtcrime.securesms.util.Prefs;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
|
||||
@@ -55,10 +46,10 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
|
||||
private static final int REQUEST_CODE_AVATAR = 1;
|
||||
|
||||
private InputAwareLayout container;
|
||||
private ImageView avatar;
|
||||
private EditText name;
|
||||
private EditText statusView;
|
||||
private InputAwareLayout container;
|
||||
private ImageView avatar;
|
||||
private EditText name;
|
||||
private EditText statusView;
|
||||
|
||||
private boolean avatarChanged;
|
||||
private boolean imageLoaded;
|
||||
@@ -66,7 +57,6 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
private Bitmap avatarBmp;
|
||||
private AttachmentManager attachmentManager;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
@@ -84,17 +74,20 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
initializeProfileAvatar();
|
||||
initializeStatusText();
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (container.isInputOpen()) {
|
||||
container.hideCurrentInput(name);
|
||||
} else {
|
||||
setEnabled(false);
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
}
|
||||
}
|
||||
});
|
||||
getOnBackPressedDispatcher()
|
||||
.addCallback(
|
||||
this,
|
||||
new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (container.isInputOpen()) {
|
||||
container.hideCurrentInput(name);
|
||||
} else {
|
||||
setEnabled(false);
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -119,7 +112,8 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@@ -128,12 +122,12 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_AVATAR:
|
||||
Uri inputFile = (data != null ? data.getData() : null);
|
||||
Uri inputFile = (data != null ? data.getData() : null);
|
||||
onFileSelected(inputFile);
|
||||
break;
|
||||
|
||||
@@ -145,26 +139,28 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
|
||||
private void setAvatarView(Uri output) {
|
||||
GlideApp.with(this)
|
||||
.asBitmap()
|
||||
.load(output)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.centerCrop()
|
||||
.override(AvatarHelper.AVATAR_SIZE, AvatarHelper.AVATAR_SIZE)
|
||||
.into(new SimpleTarget<Bitmap>() {
|
||||
.asBitmap()
|
||||
.load(output)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.centerCrop()
|
||||
.override(AvatarHelper.AVATAR_SIZE, AvatarHelper.AVATAR_SIZE)
|
||||
.into(
|
||||
new SimpleTarget<Bitmap>() {
|
||||
@Override
|
||||
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
|
||||
public void onResourceReady(
|
||||
@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
|
||||
avatarChanged = true;
|
||||
imageLoaded = true;
|
||||
avatarBmp = resource;
|
||||
}
|
||||
});
|
||||
GlideApp.with(this)
|
||||
.load(output)
|
||||
.circleCrop()
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.into(avatar);
|
||||
.load(output)
|
||||
.circleCrop()
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.into(avatar);
|
||||
}
|
||||
|
||||
private void onFileSelected(Uri inputFile) {
|
||||
@@ -176,11 +172,11 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
TextView loginSuccessText = ViewUtil.findById(this, R.id.login_success_text);
|
||||
this.avatar = ViewUtil.findById(this, R.id.avatar);
|
||||
this.name = ViewUtil.findById(this, R.id.name_text);
|
||||
this.container = ViewUtil.findById(this, R.id.container);
|
||||
this.statusView = ViewUtil.findById(this, R.id.status_text);
|
||||
TextView loginSuccessText = ViewUtil.findById(this, R.id.login_success_text);
|
||||
this.avatar = ViewUtil.findById(this, R.id.avatar);
|
||||
this.name = ViewUtil.findById(this, R.id.name_text);
|
||||
this.container = ViewUtil.findById(this, R.id.container);
|
||||
this.statusView = ViewUtil.findById(this, R.id.status_text);
|
||||
|
||||
// add padding to avoid content hidden behind system bars
|
||||
ViewUtil.applyWindowInsets(container);
|
||||
@@ -189,7 +185,7 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
}
|
||||
|
||||
private void initializeProfileName() {
|
||||
String profileName = DcHelper.get(this, DcHelper.CONFIG_DISPLAY_NAME);
|
||||
String profileName = DcHelper.get(this, DcHelper.CONFIG_DISPLAY_NAME);
|
||||
if (!TextUtils.isEmpty(profileName)) {
|
||||
name.setText(profileName);
|
||||
name.setSelection(profileName.length(), profileName.length());
|
||||
@@ -200,18 +196,21 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
File avatarFile = AvatarHelper.getSelfAvatarFile(this);
|
||||
if (avatarFile.exists() && avatarFile.length() > 0) {
|
||||
imageLoaded = true;
|
||||
GlideApp.with(this)
|
||||
.load(avatarFile)
|
||||
.circleCrop()
|
||||
.into(avatar);
|
||||
GlideApp.with(this).load(avatarFile).circleCrop().into(avatar);
|
||||
} else {
|
||||
imageLoaded = false;
|
||||
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp).asDrawable(this, getResources().getColor(R.color.grey_400)));
|
||||
avatar.setImageDrawable(
|
||||
new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp)
|
||||
.asDrawable(this, getResources().getColor(R.color.grey_400)));
|
||||
}
|
||||
avatar.setOnClickListener(view ->
|
||||
new AvatarSelector(this, LoaderManager.getInstance(this), new AvatarSelectedListener(), imageLoaded)
|
||||
.show(this, avatar)
|
||||
);
|
||||
avatar.setOnClickListener(
|
||||
view ->
|
||||
new AvatarSelector(
|
||||
this,
|
||||
LoaderManager.getInstance(this),
|
||||
new AvatarSelectedListener(),
|
||||
imageLoaded)
|
||||
.show(this, avatar));
|
||||
}
|
||||
|
||||
private void initializeStatusText() {
|
||||
@@ -229,7 +228,7 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
new AsyncTask<Void, Void, Boolean>() {
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
Context context = CreateProfileActivity.this;
|
||||
Context context = CreateProfileActivity.this;
|
||||
DcHelper.set(context, DcHelper.CONFIG_DISPLAY_NAME, name);
|
||||
setStatusText();
|
||||
|
||||
@@ -253,7 +252,7 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
if (result) {
|
||||
attachmentManager.cleanup();
|
||||
finish();
|
||||
} else {
|
||||
} else {
|
||||
Toast.makeText(CreateProfileActivity.this, R.string.error, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
@@ -276,7 +275,10 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
avatarBmp = null;
|
||||
imageLoaded = false;
|
||||
avatarChanged = true;
|
||||
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp).asDrawable(CreateProfileActivity.this, getResources().getColor(R.color.grey_400)));
|
||||
avatar.setImageDrawable(
|
||||
new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp)
|
||||
.asDrawable(
|
||||
CreateProfileActivity.this, getResources().getColor(R.color.grey_400)));
|
||||
break;
|
||||
case AvatarSelector.TAKE_PHOTO:
|
||||
attachmentManager.capturePhoto(CreateProfileActivity.this, REQUEST_CODE_AVATAR);
|
||||
|
||||
@@ -4,7 +4,8 @@ import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
// dummy activity that just pushes the app to foreground when fired.
|
||||
// can also be used to work around android bug https://code.google.com/p/android/issues/detail?id=53313
|
||||
// can also be used to work around android bug
|
||||
// https://code.google.com/p/android/issues/detail?id=53313
|
||||
public class DummyActivity extends Activity {
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
|
||||
@@ -6,99 +6,120 @@ import android.view.ViewGroup;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.widget.TextViewCompat;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class EphemeralMessagesDialog {
|
||||
|
||||
private final static String TAG = EphemeralMessagesDialog.class.getSimpleName();
|
||||
private static final String TAG = EphemeralMessagesDialog.class.getSimpleName();
|
||||
|
||||
public static void show(final Context context, int currentSelectedTime, final @NonNull EphemeralMessagesInterface listener) {
|
||||
CharSequence[] choices = context.getResources().getStringArray(R.array.ephemeral_message_durations);
|
||||
int preselected = getPreselection(currentSelectedTime);
|
||||
final int[] selectedChoice = new int[]{preselected};
|
||||
public static void show(
|
||||
final Context context,
|
||||
int currentSelectedTime,
|
||||
final @NonNull EphemeralMessagesInterface listener) {
|
||||
CharSequence[] choices =
|
||||
context.getResources().getStringArray(R.array.ephemeral_message_durations);
|
||||
int preselected = getPreselection(currentSelectedTime);
|
||||
final int[] selectedChoice = new int[] {preselected};
|
||||
|
||||
View dialogView = View.inflate(context, R.layout.dialog_extended_options, null);
|
||||
RadioGroup container = dialogView.findViewById(R.id.optionsContainer);
|
||||
for (CharSequence choice : choices) {
|
||||
View dialogView = View.inflate(context, R.layout.dialog_extended_options, null);
|
||||
RadioGroup container = dialogView.findViewById(R.id.optionsContainer);
|
||||
for (CharSequence choice : choices) {
|
||||
|
||||
RadioButton radioButton = new RadioButton(context);
|
||||
radioButton.setText(choice);
|
||||
TextViewCompat.setTextAppearance(radioButton, android.R.style.TextAppearance_Medium);
|
||||
RadioButton radioButton = new RadioButton(context);
|
||||
radioButton.setText(choice);
|
||||
TextViewCompat.setTextAppearance(radioButton, android.R.style.TextAppearance_Medium);
|
||||
|
||||
RadioGroup.LayoutParams params = new RadioGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.setMargins(0, 0, 0, ViewUtil.dpToPx(context, 8));
|
||||
radioButton.setLayoutParams(params);
|
||||
container.addView(radioButton);
|
||||
}
|
||||
RadioGroup.LayoutParams params =
|
||||
new RadioGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.setMargins(0, 0, 0, ViewUtil.dpToPx(context, 8));
|
||||
radioButton.setLayoutParams(params);
|
||||
container.addView(radioButton);
|
||||
}
|
||||
|
||||
container.setOnCheckedChangeListener((group, checkedId) -> {
|
||||
int childCount = group.getChildCount();
|
||||
for (int x = 0; x < childCount; x++) {
|
||||
RadioButton btn = (RadioButton) group.getChildAt(x);
|
||||
if (btn.getId() == checkedId) {
|
||||
selectedChoice[0] = x;
|
||||
}
|
||||
container.setOnCheckedChangeListener(
|
||||
(group, checkedId) -> {
|
||||
int childCount = group.getChildCount();
|
||||
for (int x = 0; x < childCount; x++) {
|
||||
RadioButton btn = (RadioButton) group.getChildAt(x);
|
||||
if (btn.getId() == checkedId) {
|
||||
selectedChoice[0] = x;
|
||||
}
|
||||
}
|
||||
});
|
||||
container.check(container.getChildAt(preselected).getId());
|
||||
container.check(container.getChildAt(preselected).getId());
|
||||
|
||||
TextView messageView = dialogView.findViewById(R.id.description);
|
||||
messageView.setText(context.getString(R.string.ephemeral_messages_hint));
|
||||
TextView messageView = dialogView.findViewById(R.id.description);
|
||||
messageView.setText(context.getString(R.string.ephemeral_messages_hint));
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.ephemeral_messages)
|
||||
.setView(dialogView)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.ok, (dialog, which) -> {
|
||||
final long burnAfter;
|
||||
switch (selectedChoice[0]) {
|
||||
case 1: burnAfter = TimeUnit.MINUTES.toSeconds(5); break;
|
||||
case 2: burnAfter = TimeUnit.HOURS.toSeconds(1); break;
|
||||
case 3: burnAfter = TimeUnit.DAYS.toSeconds(1); break;
|
||||
case 4: burnAfter = TimeUnit.DAYS.toSeconds(7); break;
|
||||
case 5: burnAfter = TimeUnit.DAYS.toSeconds(35); break;
|
||||
case 6: burnAfter = TimeUnit.DAYS.toSeconds(365); break;
|
||||
default: burnAfter = 0; break;
|
||||
}
|
||||
listener.onTimeSelected(burnAfter);
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.ephemeral_messages)
|
||||
.setView(dialogView)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(
|
||||
R.string.ok,
|
||||
(dialog, which) -> {
|
||||
final long burnAfter;
|
||||
switch (selectedChoice[0]) {
|
||||
case 1:
|
||||
burnAfter = TimeUnit.MINUTES.toSeconds(5);
|
||||
break;
|
||||
case 2:
|
||||
burnAfter = TimeUnit.HOURS.toSeconds(1);
|
||||
break;
|
||||
case 3:
|
||||
burnAfter = TimeUnit.DAYS.toSeconds(1);
|
||||
break;
|
||||
case 4:
|
||||
burnAfter = TimeUnit.DAYS.toSeconds(7);
|
||||
break;
|
||||
case 5:
|
||||
burnAfter = TimeUnit.DAYS.toSeconds(35);
|
||||
break;
|
||||
case 6:
|
||||
burnAfter = TimeUnit.DAYS.toSeconds(365);
|
||||
break;
|
||||
default:
|
||||
burnAfter = 0;
|
||||
break;
|
||||
}
|
||||
listener.onTimeSelected(burnAfter);
|
||||
})
|
||||
.setNeutralButton(R.string.learn_more, (d, w) -> DcHelper.openHelp(context, "#ephemeralmsgs"));
|
||||
builder.show();
|
||||
}
|
||||
.setNeutralButton(
|
||||
R.string.learn_more, (d, w) -> DcHelper.openHelp(context, "#ephemeralmsgs"));
|
||||
builder.show();
|
||||
}
|
||||
|
||||
public interface EphemeralMessagesInterface {
|
||||
void onTimeSelected(long duration);
|
||||
}
|
||||
public interface EphemeralMessagesInterface {
|
||||
void onTimeSelected(long duration);
|
||||
}
|
||||
|
||||
private static int getPreselection(int timespan) {
|
||||
if (timespan == 0) {
|
||||
return 0; // off
|
||||
}
|
||||
// Choose timespan close to the current one out of available options.
|
||||
if (timespan < TimeUnit.HOURS.toSeconds(1)) {
|
||||
return 1; // 5 minutes
|
||||
}
|
||||
if (timespan < TimeUnit.DAYS.toSeconds(1)) {
|
||||
return 2; // 1 hour
|
||||
}
|
||||
if (timespan < TimeUnit.DAYS.toSeconds(7)) {
|
||||
return 3; // 1 day
|
||||
}
|
||||
if (timespan < TimeUnit.DAYS.toSeconds(35)) {
|
||||
return 4; // 1 week
|
||||
}
|
||||
if (timespan < TimeUnit.DAYS.toSeconds(365)) {
|
||||
return 5; // 5 weeks
|
||||
}
|
||||
return 6; // 1 year
|
||||
private static int getPreselection(int timespan) {
|
||||
if (timespan == 0) {
|
||||
return 0; // off
|
||||
}
|
||||
|
||||
// Choose timespan close to the current one out of available options.
|
||||
if (timespan < TimeUnit.HOURS.toSeconds(1)) {
|
||||
return 1; // 5 minutes
|
||||
}
|
||||
if (timespan < TimeUnit.DAYS.toSeconds(1)) {
|
||||
return 2; // 1 hour
|
||||
}
|
||||
if (timespan < TimeUnit.DAYS.toSeconds(7)) {
|
||||
return 3; // 1 day
|
||||
}
|
||||
if (timespan < TimeUnit.DAYS.toSeconds(35)) {
|
||||
return 4; // 1 week
|
||||
}
|
||||
if (timespan < TimeUnit.DAYS.toSeconds(365)) {
|
||||
return 5; // 5 weeks
|
||||
}
|
||||
return 6; // 1 year
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,29 +5,23 @@ import android.view.ContextMenu;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import android.widget.Toast;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.types.HttpResponse;
|
||||
import com.b44t.messenger.DcContext;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.thoughtcrime.securesms.util.Prefs;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.types.HttpResponse;
|
||||
|
||||
public class FullMsgActivity extends WebViewActivity
|
||||
{
|
||||
public class FullMsgActivity extends WebViewActivity {
|
||||
public static final String MSG_ID_EXTRA = "msg_id";
|
||||
public static final String BLOCK_LOADING_REMOTE = "block_loading_remote";
|
||||
private String imageUrl;
|
||||
@@ -91,7 +85,8 @@ public class FullMsgActivity extends WebViewActivity
|
||||
WebView.HitTestResult result = ((WebView) v).getHitTestResult();
|
||||
if (result != null) {
|
||||
int type = result.getType();
|
||||
if (type == WebView.HitTestResult.IMAGE_TYPE || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
||||
if (type == WebView.HitTestResult.IMAGE_TYPE
|
||||
|| type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
||||
imageUrl = result.getExtra();
|
||||
if (!imageUrl.startsWith("data:")) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
@@ -120,24 +115,30 @@ public class FullMsgActivity extends WebViewActivity
|
||||
}
|
||||
|
||||
private static void loadHtmlAsync(final WeakReference<FullMsgActivity> activityReference) {
|
||||
Util.runOnBackground(() -> {
|
||||
try {
|
||||
FullMsgActivity activity = activityReference.get();
|
||||
String html = activity.dcContext.getMsgHtml(activity.msgId);
|
||||
|
||||
activity.runOnUiThread(() -> {
|
||||
Util.runOnBackground(
|
||||
() -> {
|
||||
try {
|
||||
// a base URL is needed, otherwise clicking links that reference document sections will not jump to sections
|
||||
activityReference.get().webView.loadDataWithBaseURL("file://index.html", html, "text/html", null, null);
|
||||
FullMsgActivity activity = activityReference.get();
|
||||
String html = activity.dcContext.getMsgHtml(activity.msgId);
|
||||
|
||||
activity.runOnUiThread(
|
||||
() -> {
|
||||
try {
|
||||
// a base URL is needed, otherwise clicking links that reference document
|
||||
// sections will not jump to sections
|
||||
activityReference
|
||||
.get()
|
||||
.webView
|
||||
.loadDataWithBaseURL("file://index.html", html, "text/html", null, null);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -159,17 +160,21 @@ public class FullMsgActivity extends WebViewActivity
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
super.onOptionsItemSelected(item);
|
||||
if (item.getItemId() == R.id.load_remote_content) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.load_remote_content)
|
||||
.setMessage(R.string.load_remote_content_ask);
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.load_remote_content)
|
||||
.setMessage(R.string.load_remote_content_ask);
|
||||
|
||||
// we are using the buttons "[Always] [Never][Once]" in that order.
|
||||
// 1. Checkmarks before [Always] and [Never] show the current state.
|
||||
// 2. [Once] is also shown in always-mode and disables always-mode if selected
|
||||
// (there was the idea to hide [Once] in always mode, but that looks more like a bug in the end)
|
||||
// (maybe a usual Always-Checkbox and "[Cancel][OK]" buttons are an alternative, however, a [Once]
|
||||
// (there was the idea to hide [Once] in always mode, but that looks more like a bug in the
|
||||
// end)
|
||||
// (maybe a usual Always-Checkbox and "[Cancel][OK]" buttons are an alternative, however, a
|
||||
// [Once]
|
||||
// would be required as well - probably as the leftmost button which is not that usable in
|
||||
// not-always-mode where the dialog is used more often. Or [Ok] would mean "Once" as well as "Change checkbox setting",
|
||||
// not-always-mode where the dialog is used more often. Or [Ok] would mean "Once" as well as
|
||||
// "Change checkbox setting",
|
||||
// which is also a bit weird. Anyway, let's give the three buttons a try :)
|
||||
final String checkmark = DynamicTheme.getCheckmarkEmoji(this) + " ";
|
||||
String alwaysCheckmark = "";
|
||||
@@ -184,10 +189,16 @@ public class FullMsgActivity extends WebViewActivity
|
||||
}
|
||||
|
||||
if (!blockLoadingRemote) {
|
||||
builder.setNeutralButton(alwaysCheckmark + getString(R.string.always), (dialog, which) -> onChangeLoadRemoteContent(LoadRemoteContent.ALWAYS));
|
||||
builder.setNeutralButton(
|
||||
alwaysCheckmark + getString(R.string.always),
|
||||
(dialog, which) -> onChangeLoadRemoteContent(LoadRemoteContent.ALWAYS));
|
||||
}
|
||||
builder.setNegativeButton(neverCheckmark + getString(blockLoadingRemote ? R.string.no : R.string.never), (dialog, which) -> onChangeLoadRemoteContent(LoadRemoteContent.NEVER));
|
||||
builder.setPositiveButton(onceCheckmark + getString(R.string.once), (dialog, which) -> onChangeLoadRemoteContent(LoadRemoteContent.ONCE));
|
||||
builder.setNegativeButton(
|
||||
neverCheckmark + getString(blockLoadingRemote ? R.string.no : R.string.never),
|
||||
(dialog, which) -> onChangeLoadRemoteContent(LoadRemoteContent.NEVER));
|
||||
builder.setPositiveButton(
|
||||
onceCheckmark + getString(R.string.once),
|
||||
(dialog, which) -> onChangeLoadRemoteContent(LoadRemoteContent.ONCE));
|
||||
|
||||
builder.show();
|
||||
return true;
|
||||
@@ -234,10 +245,15 @@ public class FullMsgActivity extends WebViewActivity
|
||||
mimeType = "application/octet-stream";
|
||||
}
|
||||
byte[] blob = JsonUtils.decodeBase64(httpResponse.blob);
|
||||
res = new WebResourceResponse(mimeType, httpResponse.encoding, new ByteArrayInputStream(blob));
|
||||
res =
|
||||
new WebResourceResponse(mimeType, httpResponse.encoding, new ByteArrayInputStream(blob));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
res = new WebResourceResponse("text/plain", "UTF-8", new ByteArrayInputStream(("Error: " + e.getMessage()).getBytes()));
|
||||
res =
|
||||
new WebResourceResponse(
|
||||
"text/plain",
|
||||
"UTF-8",
|
||||
new ByteArrayInputStream(("Error: " + e.getMessage()).getBytes()));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -16,18 +16,20 @@ import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
|
||||
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.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.request.target.CustomTarget;
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import org.thoughtcrime.securesms.components.AvatarSelector;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||
@@ -41,16 +43,8 @@ import org.thoughtcrime.securesms.util.SelectedContactsAdapter.ItemClickListener
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
|
||||
public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
implements ItemClickListener
|
||||
{
|
||||
implements ItemClickListener {
|
||||
|
||||
private static final String TAG = GroupCreateActivity.class.getSimpleName();
|
||||
public static final String EDIT_GROUP_CHAT_ID = "edit_group_chat_id";
|
||||
@@ -65,15 +59,15 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private boolean unencrypted;
|
||||
private boolean broadcast;
|
||||
private EditText groupName;
|
||||
private EditText chatDescription;
|
||||
private ListView lv;
|
||||
private ImageView avatar;
|
||||
private Bitmap avatarBmp;
|
||||
private int groupChatId;
|
||||
private boolean isEdit;
|
||||
private boolean avatarChanged;
|
||||
private boolean imageLoaded;
|
||||
private EditText groupName;
|
||||
private EditText chatDescription;
|
||||
private ListView lv;
|
||||
private ImageView avatar;
|
||||
private Bitmap avatarBmp;
|
||||
private int groupChatId;
|
||||
private boolean isEdit;
|
||||
private boolean avatarChanged;
|
||||
private boolean imageLoaded;
|
||||
private AttachmentManager attachmentManager;
|
||||
|
||||
@Override
|
||||
@@ -91,7 +85,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
// groupChatId may be set during creation,
|
||||
// so always check isEdit()
|
||||
if(groupChatId !=0) {
|
||||
if (groupChatId != 0) {
|
||||
isEdit = true;
|
||||
DcChat dcChat = dcContext.getChat(groupChatId);
|
||||
broadcast = dcChat.isOutBroadcast();
|
||||
@@ -100,7 +94,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
int chatId = getIntent().getIntExtra(CLONE_CHAT_EXTRA, 0);
|
||||
if (chatId != 0) {
|
||||
DcChat dcChat = dcContext.getChat(chatId);
|
||||
DcChat dcChat = dcContext.getChat(chatId);
|
||||
broadcast = dcChat.isOutBroadcast();
|
||||
unencrypted = !dcChat.isEncrypted();
|
||||
}
|
||||
@@ -125,27 +119,24 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
groupName.setEnabled(true);
|
||||
|
||||
String title;
|
||||
if(isEdit()) {
|
||||
if (isEdit()) {
|
||||
title = getString(R.string.global_menu_edit_desktop);
|
||||
}
|
||||
else if(broadcast) {
|
||||
} else if (broadcast) {
|
||||
title = getString(R.string.new_channel);
|
||||
}
|
||||
else if(unencrypted) {
|
||||
} else if (unencrypted) {
|
||||
title = getString(R.string.new_email);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
title = getString(R.string.menu_new_group);
|
||||
}
|
||||
getSupportActionBar().setTitle(title);
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
lv = ViewUtil.findById(this, R.id.selected_contacts_list);
|
||||
avatar = ViewUtil.findById(this, R.id.avatar);
|
||||
groupName = ViewUtil.findById(this, R.id.group_name);
|
||||
chatDescription = ViewUtil.findById(this, R.id.chat_description);
|
||||
TextView chatHints = ViewUtil.findById(this, R.id.chat_hints);
|
||||
lv = ViewUtil.findById(this, R.id.selected_contacts_list);
|
||||
avatar = ViewUtil.findById(this, R.id.avatar);
|
||||
groupName = ViewUtil.findById(this, R.id.group_name);
|
||||
chatDescription = ViewUtil.findById(this, R.id.chat_description);
|
||||
TextView chatHints = ViewUtil.findById(this, R.id.chat_hints);
|
||||
|
||||
// add padding to avoid content hidden behind system bars
|
||||
ViewUtil.applyWindowInsets(lv, false, false, false, true);
|
||||
@@ -154,7 +145,8 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
initializeAvatarView();
|
||||
|
||||
SelectedContactsAdapter adapter = new SelectedContactsAdapter(this, GlideApp.with(this), broadcast);
|
||||
SelectedContactsAdapter adapter =
|
||||
new SelectedContactsAdapter(this, GlideApp.with(this), broadcast);
|
||||
adapter.setItemClickListener(this);
|
||||
lv.setAdapter(adapter);
|
||||
|
||||
@@ -189,7 +181,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
chatHints.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if(isEdit()) {
|
||||
if (isEdit()) {
|
||||
groupName.setText(dcContext.getChat(groupChatId).getName());
|
||||
lv.setVisibility(View.GONE);
|
||||
|
||||
@@ -210,19 +202,22 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
File avatarFile = new File(avatarPath);
|
||||
if (avatarFile.exists()) {
|
||||
imageLoaded = true;
|
||||
GlideApp.with(this)
|
||||
.load(avatarFile)
|
||||
.circleCrop()
|
||||
.into(avatar);
|
||||
GlideApp.with(this).load(avatarFile).circleCrop().into(avatar);
|
||||
}
|
||||
}
|
||||
if (!imageLoaded) {
|
||||
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_group_white_24dp).asDrawable(this, ThemeUtil.getDummyContactColor(this)));
|
||||
avatar.setImageDrawable(
|
||||
new ResourceContactPhoto(R.drawable.ic_group_white_24dp)
|
||||
.asDrawable(this, ThemeUtil.getDummyContactColor(this)));
|
||||
}
|
||||
avatar.setOnClickListener(view ->
|
||||
new AvatarSelector(this, LoaderManager.getInstance(this), new AvatarSelectedListener(), imageLoaded)
|
||||
.show(this, avatar)
|
||||
);
|
||||
avatar.setOnClickListener(
|
||||
view ->
|
||||
new AvatarSelector(
|
||||
this,
|
||||
LoaderManager.getInstance(this),
|
||||
new AvatarSelectedListener(),
|
||||
imageLoaded)
|
||||
.show(this, avatar));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -287,7 +282,10 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
groupChatId = rpc.createGroupChat(accId, groupName, false);
|
||||
}
|
||||
|
||||
rpc.setChatDescription(accId, groupChatId, getChatDescription());
|
||||
String description = getChatDescription();
|
||||
if (!description.isEmpty()) {
|
||||
rpc.setChatDescription(accId, groupChatId, description);
|
||||
}
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "RPC error", e);
|
||||
return;
|
||||
@@ -296,7 +294,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
for (int contactId : getAdapter().getContacts()) {
|
||||
dcContext.addContactToChat(groupChatId, contactId);
|
||||
}
|
||||
if (avatarBmp!=null) {
|
||||
if (avatarBmp != null) {
|
||||
AvatarHelper.setGroupAvatar(this, groupChatId, avatarBmp);
|
||||
}
|
||||
|
||||
@@ -308,8 +306,9 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private boolean showGroupNameEmptyToast(String groupName) {
|
||||
if(groupName == null) {
|
||||
Toast.makeText(this, getString(R.string.group_please_enter_group_name), Toast.LENGTH_LONG).show();
|
||||
if (groupName == null) {
|
||||
Toast.makeText(this, getString(R.string.group_please_enter_group_name), Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -339,26 +338,27 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private SelectedContactsAdapter getAdapter() {
|
||||
return (SelectedContactsAdapter)lv.getAdapter();
|
||||
return (SelectedContactsAdapter) lv.getAdapter();
|
||||
}
|
||||
|
||||
private @Nullable String getGroupName() {
|
||||
String ret = groupName.getText() != null ? groupName.getText().toString() : null;
|
||||
if(ret!=null) {
|
||||
if (ret != null) {
|
||||
ret = ret.trim();
|
||||
if(ret.isEmpty()) {
|
||||
if (ret.isEmpty()) {
|
||||
ret = null;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private @Nullable String getChatDescription() {
|
||||
private String getChatDescription() {
|
||||
return chatDescription.getText() != null ? chatDescription.getText().toString().trim() : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
public void onRequestPermissionsResult(
|
||||
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
@@ -367,19 +367,20 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
public void onActivityResult(int reqCode, int resultCode, final Intent data) {
|
||||
super.onActivityResult(reqCode, resultCode, data);
|
||||
|
||||
if (resultCode != Activity.RESULT_OK)
|
||||
return;
|
||||
if (resultCode != Activity.RESULT_OK) return;
|
||||
|
||||
switch (reqCode) {
|
||||
case REQUEST_CODE_AVATAR:
|
||||
Uri inputFile = (data != null ? data.getData() : null);
|
||||
Uri inputFile = (data != null ? data.getData() : null);
|
||||
onFileSelected(inputFile);
|
||||
break;
|
||||
|
||||
case PICK_CONTACT:
|
||||
ArrayList<Integer> contactIds = new ArrayList<>();
|
||||
for (Integer contactId : Objects.requireNonNull(data.getIntegerArrayListExtra(ContactMultiSelectionActivity.CONTACTS_EXTRA))) {
|
||||
if(contactId != null) {
|
||||
for (Integer contactId :
|
||||
Objects.requireNonNull(
|
||||
data.getIntegerArrayListExtra(ContactMultiSelectionActivity.CONTACTS_EXTRA))) {
|
||||
if (contactId != null) {
|
||||
contactIds.add(contactId);
|
||||
}
|
||||
}
|
||||
@@ -394,15 +395,17 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private void setAvatarView(Uri output) {
|
||||
GlideApp.with(this)
|
||||
.asBitmap()
|
||||
.load(output)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.centerCrop()
|
||||
.override(AvatarHelper.AVATAR_SIZE, AvatarHelper.AVATAR_SIZE)
|
||||
.into(new CustomTarget<Bitmap>() {
|
||||
.asBitmap()
|
||||
.load(output)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.centerCrop()
|
||||
.override(AvatarHelper.AVATAR_SIZE, AvatarHelper.AVATAR_SIZE)
|
||||
.into(
|
||||
new CustomTarget<Bitmap>() {
|
||||
@Override
|
||||
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
|
||||
public void onResourceReady(
|
||||
@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
|
||||
setAvatar(output, resource);
|
||||
}
|
||||
|
||||
@@ -416,18 +419,17 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
avatarChanged = true;
|
||||
imageLoaded = true;
|
||||
GlideApp.with(this)
|
||||
.load(model)
|
||||
.circleCrop()
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.into(avatar);
|
||||
.load(model)
|
||||
.circleCrop()
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.into(avatar);
|
||||
}
|
||||
|
||||
private boolean isEdit() {
|
||||
return isEdit;
|
||||
}
|
||||
|
||||
|
||||
private class AvatarSelectedListener implements AvatarSelector.AttachmentClickedListener {
|
||||
@Override
|
||||
public void onClick(int type) {
|
||||
@@ -439,7 +441,11 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
avatarBmp = null;
|
||||
imageLoaded = false;
|
||||
avatarChanged = true;
|
||||
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_group_white_24dp).asDrawable(GroupCreateActivity.this, ThemeUtil.getDummyContactColor(GroupCreateActivity.this)));
|
||||
avatar.setImageDrawable(
|
||||
new ResourceContactPhoto(R.drawable.ic_group_white_24dp)
|
||||
.asDrawable(
|
||||
GroupCreateActivity.this,
|
||||
ThemeUtil.getDummyContactColor(GroupCreateActivity.this)));
|
||||
break;
|
||||
case AvatarSelector.TAKE_PHOTO:
|
||||
attachmentManager.capturePhoto(GroupCreateActivity.this, REQUEST_CODE_AVATAR);
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms;
|
||||
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_PROXY_ENABLED;
|
||||
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_PROXY_URL;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
@@ -12,7 +11,6 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
@@ -24,12 +22,12 @@ import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
import com.b44t.messenger.DcLot;
|
||||
@@ -38,7 +36,12 @@ import com.bumptech.glide.request.target.CustomTarget;
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import org.thoughtcrime.securesms.components.AvatarSelector;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
@@ -59,17 +62,8 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.ProgressDialog;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
|
||||
public class InstantOnboardingActivity extends BaseActionBarActivity implements DcEventCenter.DcEventDelegate {
|
||||
public class InstantOnboardingActivity extends BaseActionBarActivity
|
||||
implements DcEventCenter.DcEventDelegate {
|
||||
|
||||
private static final String TAG = InstantOnboardingActivity.class.getSimpleName();
|
||||
private static final String DCACCOUNT = "dcaccount";
|
||||
@@ -90,7 +84,7 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
private String providerHost;
|
||||
private String providerQrData;
|
||||
private String rawQrData;
|
||||
private DcLot parsedQrData;
|
||||
private DcLot parsedQrData;
|
||||
private boolean isDcLogin;
|
||||
private boolean isContactInvitation;
|
||||
private boolean isGroupInvitation;
|
||||
@@ -111,14 +105,16 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
|
||||
setContentView(R.layout.instant_onboarding_activity);
|
||||
|
||||
Objects.requireNonNull(getSupportActionBar()).setTitle(R.string.onboarding_create_instant_account);
|
||||
Objects.requireNonNull(getSupportActionBar())
|
||||
.setTitle(R.string.onboarding_create_instant_account);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
boolean configured = DcHelper.getContext(this).isConfigured() == 1;
|
||||
boolean configured = DcHelper.getContext(this).isConfigured() == 1;
|
||||
|
||||
if (configured) {
|
||||
// if account is configured it means we didn't come from Welcome screen nor from QR scanner,
|
||||
// instead, user clicked a dcaccount:// URI directly, so we need to just offer to add a new relay
|
||||
// instead, user clicked a dcaccount:// URI directly, so we need to just offer to add a new
|
||||
// relay
|
||||
Uri uri = getIntent().getData();
|
||||
if (uri != null) {
|
||||
Intent intent = new Intent(this, RelayListActivity.class);
|
||||
@@ -157,7 +153,8 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
proxyItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
||||
} 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.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
}
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
@@ -187,12 +184,12 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (resultCode != RESULT_OK) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_AVATAR:
|
||||
Uri inputFile = (data != null ? data.getData() : null);
|
||||
Uri inputFile = (data != null ? data.getData() : null);
|
||||
onFileSelected(inputFile);
|
||||
break;
|
||||
|
||||
@@ -217,7 +214,7 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
DcLot qrParsed = dcContext.checkQr(rawQr);
|
||||
switch (qrParsed.getState()) {
|
||||
case DcContext.DC_QR_LOGIN:
|
||||
isDcLogin = true; // Intentional fall-through
|
||||
isDcLogin = true; // Intentional fall-through
|
||||
case DcContext.DC_QR_ACCOUNT:
|
||||
providerHost = qrParsed.getText1();
|
||||
providerQrData = rawQr;
|
||||
@@ -237,14 +234,15 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
break;
|
||||
default:
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.qraccount_qr_code_cannot_be_used)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
.setMessage(R.string.qraccount_qr_code_cannot_be_used)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
public void onRequestPermissionsResult(
|
||||
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
@@ -254,12 +252,15 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
super.onPause();
|
||||
|
||||
// Save display name and avatar in the unconfigured profile.
|
||||
// If the currently selected profile is configured, then this means that rollbackAccountCreation()
|
||||
// was called (see handleOnBackPressed() above), i.e. the newly created profile was removed already
|
||||
// If the currently selected profile is configured, then this means that
|
||||
// rollbackAccountCreation()
|
||||
// was called (see handleOnBackPressed() above), i.e. the newly created profile was removed
|
||||
// already
|
||||
// and we can't save the display name & avatar.
|
||||
if (DcHelper.getContext(this).isConfigured() == 0) {
|
||||
final String displayName = name.getText().toString();
|
||||
DcHelper.set(this, DcHelper.CONFIG_DISPLAY_NAME, TextUtils.isEmpty(displayName) ? null : displayName);
|
||||
DcHelper.set(
|
||||
this, DcHelper.CONFIG_DISPLAY_NAME, TextUtils.isEmpty(displayName) ? null : displayName);
|
||||
|
||||
if (avatarChanged) {
|
||||
try {
|
||||
@@ -291,7 +292,8 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
Uri uri = getIntent().getData();
|
||||
if (uri == null) return;
|
||||
|
||||
if (uri.getScheme().equalsIgnoreCase(DCACCOUNT) || uri.getScheme().equalsIgnoreCase(DCLOGIN)) {
|
||||
if (uri.getScheme().equalsIgnoreCase(DCACCOUNT)
|
||||
|| uri.getScheme().equalsIgnoreCase(DCLOGIN)) {
|
||||
setProviderFromQr(uri.toString());
|
||||
}
|
||||
}
|
||||
@@ -299,29 +301,31 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
|
||||
private void setAvatarView(Uri output) {
|
||||
GlideApp.with(this)
|
||||
.asBitmap()
|
||||
.load(output)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.centerCrop()
|
||||
.override(AvatarHelper.AVATAR_SIZE, AvatarHelper.AVATAR_SIZE)
|
||||
.into(new CustomTarget<Bitmap>() {
|
||||
@Override
|
||||
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
|
||||
avatarChanged = true;
|
||||
imageLoaded = true;
|
||||
avatarBmp = resource;
|
||||
}
|
||||
.asBitmap()
|
||||
.load(output)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.centerCrop()
|
||||
.override(AvatarHelper.AVATAR_SIZE, AvatarHelper.AVATAR_SIZE)
|
||||
.into(
|
||||
new CustomTarget<Bitmap>() {
|
||||
@Override
|
||||
public void onResourceReady(
|
||||
@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
|
||||
avatarChanged = true;
|
||||
imageLoaded = true;
|
||||
avatarBmp = resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCleared(@Nullable Drawable placeholder) {}
|
||||
});
|
||||
@Override
|
||||
public void onLoadCleared(@Nullable Drawable placeholder) {}
|
||||
});
|
||||
GlideApp.with(this)
|
||||
.load(output)
|
||||
.circleCrop()
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.into(avatar);
|
||||
.load(output)
|
||||
.circleCrop()
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.into(avatar);
|
||||
}
|
||||
|
||||
private void onFileSelected(Uri inputFile) {
|
||||
@@ -333,35 +337,43 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
this.avatar = findViewById(R.id.avatar);
|
||||
this.name = findViewById(R.id.name_text);
|
||||
this.invitationText = findViewById(R.id.invitation_label);
|
||||
this.avatar = findViewById(R.id.avatar);
|
||||
this.name = findViewById(R.id.name_text);
|
||||
this.invitationText = findViewById(R.id.invitation_label);
|
||||
this.privacyPolicyBtn = findViewById(R.id.privacy_policy_button);
|
||||
this.signUpBtn = findViewById(R.id.signup_button);
|
||||
this.signUpBtn = findViewById(R.id.signup_button);
|
||||
|
||||
// add padding to avoid content hidden behind system bars
|
||||
ViewUtil.applyWindowInsets(findViewById(R.id.container));
|
||||
|
||||
privacyPolicyBtn.setOnClickListener(view -> {
|
||||
if (!isDcLogin) {
|
||||
IntentUtils.showInBrowser(this, "https://" + providerHost + "/privacy.html");
|
||||
}
|
||||
});
|
||||
privacyPolicyBtn.setOnClickListener(
|
||||
view -> {
|
||||
if (!isDcLogin) {
|
||||
IntentUtils.showInBrowser(this, "https://" + providerHost + "/privacy.html");
|
||||
}
|
||||
});
|
||||
|
||||
signUpBtn.setOnClickListener(view -> createProfile());
|
||||
|
||||
Button otherServerButton = findViewById(R.id.use_other_server);
|
||||
otherServerButton.setText(
|
||||
TextUtil.markAsExternal(getString(R.string.instant_onboarding_other_server)));
|
||||
otherServerButton.setOnClickListener((v) -> {
|
||||
IntentUtils.showInBrowser(this, INSTANCES_URL);
|
||||
});
|
||||
findViewById(R.id.login_button).setOnClickListener((v) -> {
|
||||
startActivity(new Intent(this, EditRelayActivity.class));
|
||||
});
|
||||
findViewById(R.id.scan_qr_button).setOnClickListener((v) -> {
|
||||
new IntentIntegrator(this).setCaptureActivity(RegistrationQrActivity.class).initiateScan();
|
||||
});
|
||||
TextUtil.markAsExternal(getString(R.string.instant_onboarding_other_server)));
|
||||
otherServerButton.setOnClickListener(
|
||||
(v) -> {
|
||||
IntentUtils.showInBrowser(this, INSTANCES_URL);
|
||||
});
|
||||
findViewById(R.id.login_button)
|
||||
.setOnClickListener(
|
||||
(v) -> {
|
||||
startActivity(new Intent(this, EditRelayActivity.class));
|
||||
});
|
||||
findViewById(R.id.scan_qr_button)
|
||||
.setOnClickListener(
|
||||
(v) -> {
|
||||
new IntentIntegrator(this)
|
||||
.setCaptureActivity(RegistrationQrActivity.class)
|
||||
.initiateScan();
|
||||
});
|
||||
}
|
||||
|
||||
private void updateProvider() {
|
||||
@@ -372,16 +384,18 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
} else {
|
||||
signUpBtn.setText(R.string.instant_onboarding_create);
|
||||
|
||||
try (TypedArray typedArray = obtainStyledAttributes(new int[]{R.attr.colorAccent})) {
|
||||
try (TypedArray typedArray = obtainStyledAttributes(new int[] {R.attr.colorAccent})) {
|
||||
privacyPolicyBtn.setTextColor(typedArray.getColor(0, Color.BLACK));
|
||||
}
|
||||
|
||||
if (DEFAULT_CHATMAIL_HOST.equals(providerHost)) {
|
||||
privacyPolicyBtn.setText(TextUtil.markAsExternal(
|
||||
getString(R.string.instant_onboarding_agree_default2, providerHost)));
|
||||
privacyPolicyBtn.setText(
|
||||
TextUtil.markAsExternal(
|
||||
getString(R.string.instant_onboarding_agree_default2, providerHost)));
|
||||
} else {
|
||||
privacyPolicyBtn.setText(TextUtil.markAsExternal(
|
||||
getString(R.string.instant_onboarding_agree_instance, providerHost)));
|
||||
privacyPolicyBtn.setText(
|
||||
TextUtil.markAsExternal(
|
||||
getString(R.string.instant_onboarding_agree_instance, providerHost)));
|
||||
}
|
||||
|
||||
if (parsedQrData != null) {
|
||||
@@ -395,7 +409,6 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
invitationText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,11 +419,18 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
GlideApp.with(this).load(avatarFile).circleCrop().into(avatar);
|
||||
} else {
|
||||
imageLoaded = false;
|
||||
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp).asDrawable(this, getResources().getColor(R.color.grey_400)));
|
||||
avatar.setImageDrawable(
|
||||
new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp)
|
||||
.asDrawable(this, getResources().getColor(R.color.grey_400)));
|
||||
}
|
||||
avatar.setOnClickListener(view ->
|
||||
new AvatarSelector(this, LoaderManager.getInstance(this), new AvatarSelectedListener(), imageLoaded).show(this, avatar)
|
||||
);
|
||||
avatar.setOnClickListener(
|
||||
view ->
|
||||
new AvatarSelector(
|
||||
this,
|
||||
LoaderManager.getInstance(this),
|
||||
new AvatarSelectedListener(),
|
||||
imageLoaded)
|
||||
.show(this, avatar));
|
||||
|
||||
name.setText(DcHelper.get(this, DcHelper.CONFIG_DISPLAY_NAME));
|
||||
}
|
||||
@@ -427,14 +447,15 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
|
||||
if (eventId == DcContext.DC_EVENT_CONFIGURE_PROGRESS) {
|
||||
long progress = event.getData1Int();
|
||||
progressUpdate((int)progress);
|
||||
progressUpdate((int) progress);
|
||||
}
|
||||
}
|
||||
|
||||
private void progressUpdate(int progress) {
|
||||
int percent = progress / 10;
|
||||
if (progressDialog != null) {
|
||||
progressDialog.setMessage(getResources().getString(R.string.one_moment)+String.format(" %d%%", percent));
|
||||
progressDialog.setMessage(
|
||||
getResources().getString(R.string.one_moment) + String.format(" %d%%", percent));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,35 +493,38 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
}
|
||||
final String name = this.name.getText().toString();
|
||||
|
||||
executor.execute(() -> {
|
||||
Context context = InstantOnboardingActivity.this;
|
||||
DcHelper.set(context, DcHelper.CONFIG_DISPLAY_NAME, name);
|
||||
executor.execute(
|
||||
() -> {
|
||||
Context context = InstantOnboardingActivity.this;
|
||||
DcHelper.set(context, DcHelper.CONFIG_DISPLAY_NAME, name);
|
||||
|
||||
boolean result = true;
|
||||
if (avatarChanged) {
|
||||
try {
|
||||
AvatarHelper.setSelfAvatar(InstantOnboardingActivity.this, avatarBmp);
|
||||
Prefs.setProfileAvatarId(InstantOnboardingActivity.this, new SecureRandom().nextInt());
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
boolean result = true;
|
||||
if (avatarChanged) {
|
||||
try {
|
||||
AvatarHelper.setSelfAvatar(InstantOnboardingActivity.this, avatarBmp);
|
||||
Prefs.setProfileAvatarId(
|
||||
InstantOnboardingActivity.this, new SecureRandom().nextInt());
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean finalResult = result;
|
||||
runOnUiThread(() -> {
|
||||
if (finalResult) {
|
||||
attachmentManager.cleanup();
|
||||
startQrAccountCreation(providerQrData);
|
||||
} else {
|
||||
Toast.makeText(InstantOnboardingActivity.this, R.string.error, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
boolean finalResult = result;
|
||||
runOnUiThread(
|
||||
() -> {
|
||||
if (finalResult) {
|
||||
attachmentManager.cleanup();
|
||||
startQrAccountCreation(providerQrData);
|
||||
} else {
|
||||
Toast.makeText(InstantOnboardingActivity.this, R.string.error, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void startQrAccountCreation(String qrCode)
|
||||
{
|
||||
private void startQrAccountCreation(String qrCode) {
|
||||
if (progressDialog != null) {
|
||||
progressDialog.dismiss();
|
||||
progressDialog = null;
|
||||
@@ -512,27 +536,32 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
progressDialog.setMessage(getResources().getString(R.string.one_moment));
|
||||
progressDialog.setCanceledOnTouchOutside(false);
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getResources().getString(android.R.string.cancel), (dialog, which) -> {
|
||||
cancelled = true;
|
||||
dcContext.stopOngoingProcess();
|
||||
});
|
||||
progressDialog.setButton(
|
||||
DialogInterface.BUTTON_NEGATIVE,
|
||||
getResources().getString(android.R.string.cancel),
|
||||
(dialog, which) -> {
|
||||
cancelled = true;
|
||||
dcContext.stopOngoingProcess();
|
||||
});
|
||||
progressDialog.show();
|
||||
|
||||
DcHelper.getEventCenter(this).captureNextError();
|
||||
|
||||
new Thread(() -> {
|
||||
Rpc rpc = DcHelper.getRpc(this);
|
||||
try {
|
||||
rpc.addTransportFromQr(dcContext.getAccountId(), qrCode);
|
||||
DcHelper.getEventCenter(this).endCaptureNextError();
|
||||
progressSuccess();
|
||||
} catch (RpcException e) {
|
||||
DcHelper.getEventCenter(this).endCaptureNextError();
|
||||
if (!cancelled) {
|
||||
Util.runOnMain(() -> progressError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
new Thread(
|
||||
() -> {
|
||||
Rpc rpc = DcHelper.getRpc(this);
|
||||
try {
|
||||
rpc.addTransportFromQr(dcContext.getAccountId(), qrCode);
|
||||
DcHelper.getEventCenter(this).endCaptureNextError();
|
||||
progressSuccess();
|
||||
} catch (RpcException e) {
|
||||
DcHelper.getEventCenter(this).endCaptureNextError();
|
||||
if (!cancelled) {
|
||||
Util.runOnMain(() -> progressError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
private class AvatarSelectedListener implements AvatarSelector.AttachmentClickedListener {
|
||||
@@ -546,7 +575,10 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
avatarBmp = null;
|
||||
imageLoaded = false;
|
||||
avatarChanged = true;
|
||||
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp).asDrawable(InstantOnboardingActivity.this, getResources().getColor(R.color.grey_400)));
|
||||
avatar.setImageDrawable(
|
||||
new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp)
|
||||
.asDrawable(
|
||||
InstantOnboardingActivity.this, getResources().getColor(R.color.grey_400)));
|
||||
break;
|
||||
case AvatarSelector.TAKE_PHOTO:
|
||||
attachmentManager.capturePhoto(InstantOnboardingActivity.this, REQUEST_CODE_AVATAR);
|
||||
@@ -559,5 +591,4 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
onFileSelected(inputFile);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,21 +4,19 @@ import android.content.res.AssetManager;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Locale;
|
||||
import org.thoughtcrime.securesms.util.TextUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Locale;
|
||||
|
||||
public class LocalHelpActivity extends WebViewActivity
|
||||
{
|
||||
public class LocalHelpActivity extends WebViewActivity {
|
||||
public static final String SECTION_EXTRA = "section_extra";
|
||||
|
||||
@Override
|
||||
protected boolean allowInLockedMode() { return true; }
|
||||
protected boolean allowInLockedMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle state, boolean ready) {
|
||||
@@ -35,31 +33,37 @@ public class LocalHelpActivity extends WebViewActivity
|
||||
String appCountry = locale.getCountry();
|
||||
if (assetExists(helpPath.replace("LANG", appLang))) {
|
||||
helpLang = appLang;
|
||||
} else if (assetExists(helpPath.replace("LANG", appLang+"_"+appCountry))) {
|
||||
helpLang = appLang+"_"+appCountry;
|
||||
} else if (assetExists(helpPath.replace("LANG", appLang + "_" + appCountry))) {
|
||||
helpLang = appLang + "_" + appCountry;
|
||||
} else {
|
||||
appLang = appLang.substring(0, 2);
|
||||
if (assetExists(helpPath.replace("LANG", appLang))) {
|
||||
helpLang = appLang;
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
} else {
|
||||
setEnabled(false);
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
}
|
||||
}
|
||||
});
|
||||
getOnBackPressedDispatcher()
|
||||
.addCallback(
|
||||
this,
|
||||
new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
} else {
|
||||
setEnabled(false);
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
webView.loadUrl("file:///android_asset/" + helpPath.replace("LANG", helpLang) + (section!=null? section : ""));
|
||||
webView.loadUrl(
|
||||
"file:///android_asset/"
|
||||
+ helpPath.replace("LANG", helpLang)
|
||||
+ (section != null ? section : ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,7 +128,7 @@ public class LocalHelpActivity extends WebViewActivity
|
||||
InputStream is = assetManager.open(fileName);
|
||||
exists = true;
|
||||
is.close();
|
||||
} catch(Exception e) {
|
||||
} catch (Exception e) {
|
||||
; // a non-existent asset is no error, the function's purpose is to check exactly that.
|
||||
}
|
||||
return exists;
|
||||
|
||||
@@ -9,16 +9,13 @@ import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import java.io.File;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.util.FileProviderUtil;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class LogViewActivity extends BaseActionBarActivity {
|
||||
|
||||
private static final String TAG = LogViewActivity.class.getSimpleName();
|
||||
@@ -60,18 +57,20 @@ public class LogViewActivity extends BaseActionBarActivity {
|
||||
return true;
|
||||
} else if (itemId == R.id.save_log) {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.alwaysGrantOnSdk30()
|
||||
.ifNecessary()
|
||||
.onAllGranted(() -> {
|
||||
File outputDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
boolean success = logViewFragment.saveLogFile(outputDir) != null;
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(success ? R.string.pref_saved_log : R.string.pref_save_log_failed)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
})
|
||||
.execute();
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.alwaysGrantOnSdk30()
|
||||
.ifNecessary()
|
||||
.onAllGranted(
|
||||
() -> {
|
||||
File outputDir =
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
boolean success = logViewFragment.saveLogFile(outputDir) != null;
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(success ? R.string.pref_saved_log : R.string.pref_save_log_failed)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
})
|
||||
.execute();
|
||||
return true;
|
||||
} else if (itemId == R.id.share_log) {
|
||||
shareLog();
|
||||
@@ -110,7 +109,8 @@ public class LogViewActivity extends BaseActionBarActivity {
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,17 +32,11 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.PermissionChecker;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.notifications.FcmReceiveService;
|
||||
import org.thoughtcrime.securesms.util.Prefs;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
@@ -53,15 +47,16 @@ import java.lang.ref.WeakReference;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.notifications.FcmReceiveService;
|
||||
import org.thoughtcrime.securesms.util.Prefs;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class LogViewFragment extends Fragment {
|
||||
private EditText logPreview;
|
||||
|
||||
public LogViewFragment() {
|
||||
}
|
||||
public LogViewFragment() {}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
@@ -69,8 +64,8 @@ public class LogViewFragment extends Fragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
public View onCreateView(
|
||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_view_log, container, false);
|
||||
}
|
||||
|
||||
@@ -78,19 +73,22 @@ public class LogViewFragment extends Fragment {
|
||||
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
logPreview = (EditText) getView().findViewById(R.id.log_preview);
|
||||
logPreview = (EditText) getView().findViewById(R.id.log_preview);
|
||||
|
||||
// add padding to avoid content hidden behind system bars
|
||||
ViewUtil.applyWindowInsets(getView().findViewById(R.id.content_container), true, false, true, true);
|
||||
ViewUtil.applyWindowInsets(
|
||||
getView().findViewById(R.id.content_container), true, false, true, true);
|
||||
|
||||
new PopulateLogcatAsyncTask(this).execute();
|
||||
}
|
||||
|
||||
public String getLogText() {
|
||||
return logPreview==null? "null" : logPreview.getText().toString();
|
||||
return logPreview == null ? "null" : logPreview.getText().toString();
|
||||
}
|
||||
|
||||
public Float getLogTextSize() { return logPreview.getTextSize(); }
|
||||
public Float getLogTextSize() {
|
||||
return logPreview.getTextSize();
|
||||
}
|
||||
|
||||
public void setLogTextSize(Float textSize) {
|
||||
logPreview.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
|
||||
@@ -107,16 +105,16 @@ public class LogViewFragment extends Fragment {
|
||||
}
|
||||
|
||||
public File saveLogFile(File outputDir) {
|
||||
File logFile = null;
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
||||
Date now = new Date();
|
||||
String logFileName = "deltachat-log-" + dateFormat.format(now) + ".txt";
|
||||
File logFile = null;
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
||||
Date now = new Date();
|
||||
String logFileName = "deltachat-log-" + dateFormat.format(now) + ".txt";
|
||||
|
||||
try {
|
||||
String logText = logPreview.getText().toString();
|
||||
if(!logText.trim().equals("")){
|
||||
String logText = logPreview.getText().toString();
|
||||
if (!logText.trim().equals("")) {
|
||||
logFile = new File(outputDir + "/" + logFileName);
|
||||
if(!logFile.exists()) logFile.createNewFile();
|
||||
if (!logFile.exists()) logFile.createNewFile();
|
||||
|
||||
FileWriter logFileWriter = new FileWriter(logFile, false);
|
||||
BufferedWriter logFileBufferWriter = new BufferedWriter(logFileWriter);
|
||||
@@ -132,10 +130,11 @@ public class LogViewFragment extends Fragment {
|
||||
public static String grabLogcat() {
|
||||
String command = "logcat -v threadtime -d -t 10000 *:I";
|
||||
try {
|
||||
final Process process = Runtime.getRuntime().exec(command);
|
||||
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
final StringBuilder log = new StringBuilder();
|
||||
final String separator = System.getProperty("line.separator");
|
||||
final Process process = Runtime.getRuntime().exec(command);
|
||||
final BufferedReader bufferedReader =
|
||||
new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
final StringBuilder log = new StringBuilder();
|
||||
final String separator = System.getProperty("line.separator");
|
||||
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
@@ -152,7 +151,7 @@ public class LogViewFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private class PopulateLogcatAsyncTask extends AsyncTask<Void,Void,String> {
|
||||
private class PopulateLogcatAsyncTask extends AsyncTask<Void, Void, String> {
|
||||
private final WeakReference<LogViewFragment> weakFragment;
|
||||
|
||||
public PopulateLogcatAsyncTask(LogViewFragment fragment) {
|
||||
@@ -164,8 +163,10 @@ public class LogViewFragment extends Fragment {
|
||||
LogViewFragment fragment = weakFragment.get();
|
||||
if (fragment == null) return null;
|
||||
|
||||
return "**This log may contain sensitive information. If you want to post it publicly you may examine and edit it beforehand.**\n\n" +
|
||||
buildDescription(fragment) + "\n" + grabLogcat();
|
||||
return "**This log may contain sensitive information. If you want to post it publicly you may examine and edit it beforehand.**\n\n"
|
||||
+ buildDescription(fragment)
|
||||
+ "\n"
|
||||
+ grabLogcat();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -179,7 +180,8 @@ public class LogViewFragment extends Fragment {
|
||||
super.onPostExecute(logcat);
|
||||
if (TextUtils.isEmpty(logcat)) {
|
||||
// the log is in english, so it is fine if some of explaining strings are in english as well
|
||||
logPreview.setText("Could not read the log on your device. You can still use ADB to get a debug log instead.");
|
||||
logPreview.setText(
|
||||
"Could not read the log on your device. You can still use ADB to get a debug log instead.");
|
||||
return;
|
||||
}
|
||||
logPreview.setText(logcat);
|
||||
@@ -192,15 +194,18 @@ public class LogViewFragment extends Fragment {
|
||||
|
||||
public static String getMemoryUsage(Context context) {
|
||||
Runtime info = Runtime.getRuntime();
|
||||
return String.format(Locale.ENGLISH, "%dM (%.2f%% free, %dM max)",
|
||||
asMegs(info.totalMemory()),
|
||||
(float)info.freeMemory() / info.totalMemory() * 100f,
|
||||
asMegs(info.maxMemory()));
|
||||
return String.format(
|
||||
Locale.ENGLISH,
|
||||
"%dM (%.2f%% free, %dM max)",
|
||||
asMegs(info.totalMemory()),
|
||||
(float) info.freeMemory() / info.totalMemory() * 100f,
|
||||
asMegs(info.maxMemory()));
|
||||
}
|
||||
|
||||
public static String getMemoryClass(Context context) {
|
||||
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
String lowMem = "";
|
||||
ActivityManager activityManager =
|
||||
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
String lowMem = "";
|
||||
|
||||
if (activityManager.isLowRamDevice()) {
|
||||
lowMem = ", low-mem device";
|
||||
@@ -211,17 +216,26 @@ public class LogViewFragment extends Fragment {
|
||||
private static String buildDescription(LogViewFragment fragment) {
|
||||
Context context = fragment.getActivity();
|
||||
|
||||
PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
|
||||
final PackageManager pm = context.getPackageManager();
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
final PackageManager pm = context.getPackageManager();
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append("device=")
|
||||
.append(Build.MANUFACTURER).append(" ")
|
||||
.append(Build.MODEL).append(" (")
|
||||
.append(Build.PRODUCT).append(")\n");
|
||||
builder.append("android=").append(VERSION.RELEASE).append(" (")
|
||||
.append(VERSION.INCREMENTAL).append(", ")
|
||||
.append(Build.DISPLAY).append(")\n");
|
||||
builder
|
||||
.append("device=")
|
||||
.append(Build.MANUFACTURER)
|
||||
.append(" ")
|
||||
.append(Build.MODEL)
|
||||
.append(" (")
|
||||
.append(Build.PRODUCT)
|
||||
.append(")\n");
|
||||
builder
|
||||
.append("android=")
|
||||
.append(VERSION.RELEASE)
|
||||
.append(" (")
|
||||
.append(VERSION.INCREMENTAL)
|
||||
.append(", ")
|
||||
.append(Build.DISPLAY)
|
||||
.append(")\n");
|
||||
builder.append("sdk=").append(Build.VERSION.SDK_INT).append("\n");
|
||||
builder.append("memory=").append(getMemoryUsage(context)).append("\n");
|
||||
builder.append("memoryClass=").append(getMemoryClass(context)).append("\n");
|
||||
@@ -229,25 +243,29 @@ public class LogViewFragment extends Fragment {
|
||||
builder.append("applicationId=").append(BuildConfig.APPLICATION_ID).append("\n");
|
||||
builder.append("app=");
|
||||
try {
|
||||
builder.append(pm.getApplicationLabel(pm.getApplicationInfo(context.getPackageName(), 0)))
|
||||
.append(" ")
|
||||
.append(pm.getPackageInfo(context.getPackageName(), 0).versionName)
|
||||
.append("-")
|
||||
.append(BuildConfig.FLAVOR)
|
||||
.append(BuildConfig.DEBUG? "-debug" : "")
|
||||
.append("\n");
|
||||
builder.append("versionCode=")
|
||||
.append(pm.getPackageInfo(context.getPackageName(), 0).versionCode)
|
||||
.append("\n");
|
||||
builder.append("installer=")
|
||||
.append(pm.getInstallerPackageName(context.getPackageName()))
|
||||
.append("\n");
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
builder.append("ignoreBatteryOptimizations=").append(
|
||||
powerManager.isIgnoringBatteryOptimizations(context.getPackageName())).append("\n");
|
||||
builder
|
||||
.append(pm.getApplicationLabel(pm.getApplicationInfo(context.getPackageName(), 0)))
|
||||
.append(" ")
|
||||
.append(pm.getPackageInfo(context.getPackageName(), 0).versionName)
|
||||
.append("-")
|
||||
.append(BuildConfig.FLAVOR)
|
||||
.append(BuildConfig.DEBUG ? "-debug" : "")
|
||||
.append("\n");
|
||||
builder
|
||||
.append("versionCode=")
|
||||
.append(pm.getPackageInfo(context.getPackageName(), 0).versionCode)
|
||||
.append("\n");
|
||||
builder
|
||||
.append("installer=")
|
||||
.append(pm.getInstallerPackageName(context.getPackageName()))
|
||||
.append("\n");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
builder
|
||||
.append("ignoreBatteryOptimizations=")
|
||||
.append(powerManager.isIgnoringBatteryOptimizations(context.getPackageName()))
|
||||
.append("\n");
|
||||
}
|
||||
builder.append("reliableService=").append(
|
||||
Prefs.reliableService(context)).append("\n");
|
||||
builder.append("reliableService=").append(Prefs.reliableService(context)).append("\n");
|
||||
|
||||
Locale locale = Util.getLocale();
|
||||
builder.append("lang=").append(locale.toString()).append("\n");
|
||||
@@ -255,7 +273,9 @@ public class LogViewFragment extends Fragment {
|
||||
builder.append("rtl=").append(isRtl).append("\n");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
boolean notifPermGranted = PermissionChecker.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PermissionChecker.PERMISSION_GRANTED;
|
||||
boolean notifPermGranted =
|
||||
PermissionChecker.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS)
|
||||
== PermissionChecker.PERMISSION_GRANTED;
|
||||
builder.append("post-notifications-granted=").append(notifPermGranted).append("\n");
|
||||
} else {
|
||||
builder.append("post-notifications-granted=<not needed>").append("\n");
|
||||
|
||||
@@ -34,7 +34,6 @@ import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
@@ -42,12 +41,12 @@ import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcMediaGalleryElement;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.WeakHashMap;
|
||||
import org.thoughtcrime.securesms.components.MediaView;
|
||||
import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
@@ -66,24 +65,19 @@ import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
|
||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* Activity for displaying media attachments in-app
|
||||
*/
|
||||
/** Activity for displaying media attachments in-app */
|
||||
public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
implements RecipientModifiedListener, LoaderManager.LoaderCallbacks<DcMediaGalleryElement> {
|
||||
|
||||
private final static String TAG = MediaPreviewActivity.class.getSimpleName();
|
||||
private static final String TAG = MediaPreviewActivity.class.getSimpleName();
|
||||
|
||||
public static final String ACTIVITY_TITLE_EXTRA = "activity_title";
|
||||
public static final String EDIT_AVATAR_CHAT_ID = "avatar_for_chat_id";
|
||||
public static final String ADDRESS_EXTRA = "address";
|
||||
public static final String OUTGOING_EXTRA = "outgoing";
|
||||
public static final String EDIT_AVATAR_CHAT_ID = "avatar_for_chat_id";
|
||||
public static final String ADDRESS_EXTRA = "address";
|
||||
public static final String OUTGOING_EXTRA = "outgoing";
|
||||
public static final String LEFT_IS_RECENT_EXTRA = "left_is_recent";
|
||||
public static final String DC_MSG_ID = "dc_msg_id";
|
||||
public static final String OPENED_FROM_PROFILE = "opened_from_profile";
|
||||
public static final String DC_MSG_ID = "dc_msg_id";
|
||||
public static final String OPENED_FROM_PROFILE = "opened_from_profile";
|
||||
|
||||
/** USE ONLY IF YOU HAVE NO MESSAGE ID! */
|
||||
public static final String DATE_EXTRA = "date";
|
||||
@@ -91,13 +85,12 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
/** USE ONLY IF YOU HAVE NO MESSAGE ID! */
|
||||
public static final String SIZE_EXTRA = "size";
|
||||
|
||||
@Nullable
|
||||
private DcMsg messageRecord;
|
||||
@Nullable private DcMsg messageRecord;
|
||||
private DcContext dcContext;
|
||||
private MediaItem initialMedia;
|
||||
private ViewPager mediaPager;
|
||||
private Recipient conversationRecipient;
|
||||
private boolean leftIsRecent;
|
||||
private boolean leftIsRecent;
|
||||
|
||||
private int restartItem = -1;
|
||||
|
||||
@@ -105,12 +98,14 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
dynamicTheme = new DynamicTheme() {
|
||||
public void onCreate(Activity activity) {
|
||||
activity.setTheme(R.style.TextSecure_DarkTheme); // force dark theme
|
||||
}
|
||||
public void onResume(Activity activity) {}
|
||||
};
|
||||
dynamicTheme =
|
||||
new DynamicTheme() {
|
||||
public void onCreate(Activity activity) {
|
||||
activity.setTheme(R.style.TextSecure_DarkTheme); // force dark theme
|
||||
}
|
||||
|
||||
public void onResume(Activity activity) {}
|
||||
};
|
||||
super.onPreCreate();
|
||||
}
|
||||
|
||||
@@ -118,15 +113,16 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle, boolean ready) {
|
||||
setFullscreenIfPossible();
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
getWindow()
|
||||
.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
setContentView(R.layout.media_preview_activity);
|
||||
|
||||
editAvatarChatId = getIntent().getIntExtra(EDIT_AVATAR_CHAT_ID, 0);
|
||||
@Nullable String title = getIntent().getStringExtra(ACTIVITY_TITLE_EXTRA);
|
||||
if (title!=null) {
|
||||
if (title != null) {
|
||||
getSupportActionBar().setTitle(title);
|
||||
}
|
||||
|
||||
@@ -135,7 +131,8 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@@ -161,8 +158,8 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
relativeTimeSpan = getString(R.string.draft);
|
||||
}
|
||||
|
||||
if (mediaItem.outgoing) getSupportActionBar().setTitle(getString(R.string.self));
|
||||
else {
|
||||
if (mediaItem.outgoing) getSupportActionBar().setTitle(getString(R.string.self));
|
||||
else {
|
||||
int fromId = dcContext.getMsg(mediaItem.msgId).getFromId();
|
||||
getSupportActionBar().setTitle(dcContext.getContact(fromId).getDisplayName());
|
||||
}
|
||||
@@ -203,12 +200,20 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
this.dcContext = DcHelper.getContext(context);
|
||||
final int msgId = getIntent().getIntExtra(DC_MSG_ID, DcMsg.DC_MSG_NO_ID);
|
||||
|
||||
if(msgId == DcMsg.DC_MSG_NO_ID) {
|
||||
if (msgId == DcMsg.DC_MSG_NO_ID) {
|
||||
messageRecord = null;
|
||||
long date = getIntent().getLongExtra(DATE_EXTRA, 0);
|
||||
long size = getIntent().getLongExtra(SIZE_EXTRA, 0);
|
||||
initialMedia = new MediaItem(null, getIntent().getData(), null, getIntent().getType(),
|
||||
DcMsg.DC_MSG_NO_ID, date, size, false);
|
||||
initialMedia =
|
||||
new MediaItem(
|
||||
null,
|
||||
getIntent().getData(),
|
||||
null,
|
||||
getIntent().getType(),
|
||||
DcMsg.DC_MSG_NO_ID,
|
||||
date,
|
||||
size,
|
||||
false);
|
||||
|
||||
if (address != null) {
|
||||
conversationRecipient = Recipient.from(context, address);
|
||||
@@ -217,14 +222,20 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
} else {
|
||||
messageRecord = dcContext.getMsg(msgId);
|
||||
initialMedia = new MediaItem(Recipient.fromChat(context, msgId), Uri.fromFile(messageRecord.getFileAsFile()),
|
||||
messageRecord.getFilename(), messageRecord.getFilemime(), messageRecord.getId(), messageRecord.getDateReceived(),
|
||||
messageRecord.getFilebytes(), messageRecord.isOutgoing());
|
||||
initialMedia =
|
||||
new MediaItem(
|
||||
Recipient.fromChat(context, msgId),
|
||||
Uri.fromFile(messageRecord.getFileAsFile()),
|
||||
messageRecord.getFilename(),
|
||||
messageRecord.getFilemime(),
|
||||
messageRecord.getId(),
|
||||
messageRecord.getDateReceived(),
|
||||
messageRecord.getFilebytes(),
|
||||
messageRecord.isOutgoing());
|
||||
conversationRecipient = Recipient.fromChat(context, msgId);
|
||||
}
|
||||
leftIsRecent = getIntent().getBooleanExtra(LEFT_IS_RECENT_EXTRA, false);
|
||||
restartItem = -1;
|
||||
|
||||
leftIsRecent = getIntent().getBooleanExtra(LEFT_IS_RECENT_EXTRA, false);
|
||||
restartItem = -1;
|
||||
}
|
||||
|
||||
private void initializeMedia() {
|
||||
@@ -235,8 +246,15 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
if (messageRecord != null) {
|
||||
getSupportLoaderManager().restartLoader(0, null, this);
|
||||
} else {
|
||||
mediaPager.setAdapter(new SingleItemPagerAdapter(this, GlideApp.with(this),
|
||||
getWindow(), initialMedia.uri, initialMedia.name, initialMedia.type, initialMedia.size));
|
||||
mediaPager.setAdapter(
|
||||
new SingleItemPagerAdapter(
|
||||
this,
|
||||
GlideApp.with(this),
|
||||
getWindow(),
|
||||
initialMedia.uri,
|
||||
initialMedia.name,
|
||||
initialMedia.type,
|
||||
initialMedia.size));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,21 +274,20 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
finish(); // avoid the need to update the enlarged-avatar
|
||||
}
|
||||
|
||||
|
||||
private void showOverview() {
|
||||
if (getIntent().getBooleanExtra(OPENED_FROM_PROFILE, false)) {
|
||||
finish();
|
||||
}
|
||||
else if(conversationRecipient.getAddress().isDcChat()) {
|
||||
} else if (conversationRecipient.getAddress().isDcChat()) {
|
||||
Intent intent = new Intent(this, AllMediaActivity.class);
|
||||
intent.putExtra(AllMediaActivity.CHAT_ID_EXTRA, conversationRecipient.getAddress().getDcChatId());
|
||||
intent.putExtra(
|
||||
AllMediaActivity.CHAT_ID_EXTRA, conversationRecipient.getAddress().getDcChatId());
|
||||
intent.putExtra(AllMediaActivity.FORCE_GALLERY, true);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
else if(conversationRecipient.getAddress().isDcContact()) {
|
||||
} else if (conversationRecipient.getAddress().isDcContact()) {
|
||||
Intent intent = new Intent(this, AllMediaActivity.class);
|
||||
intent.putExtra(AllMediaActivity.CONTACT_ID_EXTRA, conversationRecipient.getAddress().getDcContactId());
|
||||
intent.putExtra(
|
||||
AllMediaActivity.CONTACT_ID_EXTRA, conversationRecipient.getAddress().getDcContactId());
|
||||
intent.putExtra(AllMediaActivity.FORCE_GALLERY, true);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
@@ -290,29 +307,35 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
MediaItem mediaItem = getCurrentMediaItem();
|
||||
|
||||
if (mediaItem != null) {
|
||||
SaveAttachmentTask.showWarningDialog(this, (dialogInterface, i) -> {
|
||||
if (StorageUtil.canWriteToMediaStore(this)) {
|
||||
performSavetoDisk(mediaItem);
|
||||
return;
|
||||
}
|
||||
SaveAttachmentTask.showWarningDialog(
|
||||
this,
|
||||
(dialogInterface, i) -> {
|
||||
if (StorageUtil.canWriteToMediaStore(this)) {
|
||||
performSavetoDisk(mediaItem);
|
||||
return;
|
||||
}
|
||||
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.alwaysGrantOnSdk30()
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(getString(R.string.perm_explain_access_to_storage_denied))
|
||||
.onAllGranted(() -> {
|
||||
performSavetoDisk(mediaItem);
|
||||
})
|
||||
.execute();
|
||||
});
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.alwaysGrantOnSdk30()
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(
|
||||
getString(R.string.perm_explain_access_to_storage_denied))
|
||||
.onAllGranted(
|
||||
() -> {
|
||||
performSavetoDisk(mediaItem);
|
||||
})
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void performSavetoDisk(@NonNull MediaItem mediaItem) {
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this);
|
||||
long saveDate = (mediaItem.date > 0) ? mediaItem.date : System.currentTimeMillis();
|
||||
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(mediaItem.uri, mediaItem.type, saveDate, mediaItem.name));
|
||||
long saveDate = (mediaItem.date > 0) ? mediaItem.date : System.currentTimeMillis();
|
||||
saveTask.executeOnExecutor(
|
||||
AsyncTask.THREAD_POOL_EXECUTOR,
|
||||
new Attachment(mediaItem.uri, mediaItem.type, saveDate, mediaItem.name));
|
||||
}
|
||||
|
||||
private void showInChat() {
|
||||
@@ -330,7 +353,8 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
Intent intent = new Intent(this, ConversationActivity.class);
|
||||
intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcMsg.getChatId());
|
||||
intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, DcMsg.getMessagePosition(dcMsg, dcContext));
|
||||
intent.putExtra(
|
||||
ConversationActivity.STARTING_POSITION_EXTRA, DcMsg.getMessagePosition(dcMsg, dcContext));
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@@ -344,24 +368,28 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
DcMsg dcMsg = dcContext.getMsg(mediaItem.msgId);
|
||||
DcChat dcChat = dcContext.getChat(dcMsg.getChatId());
|
||||
|
||||
String text = getResources().getQuantityString(R.plurals.ask_delete_messages,1, 1);
|
||||
String text = getResources().getQuantityString(R.plurals.ask_delete_messages, 1, 1);
|
||||
int positiveBtnLabel = dcChat.isSelfTalk() ? R.string.delete : R.string.delete_for_me;
|
||||
final int[] messageIds = new int[]{mediaItem.msgId};
|
||||
final int[] messageIds = new int[] {mediaItem.msgId};
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setMessage(text);
|
||||
builder.setCancelable(true);
|
||||
builder.setNeutralButton(android.R.string.cancel, null);
|
||||
builder.setPositiveButton(positiveBtnLabel, (dialogInterface, which) -> {
|
||||
Util.runOnAnyBackgroundThread(() -> dcContext.deleteMsgs(messageIds));
|
||||
finish();
|
||||
});
|
||||
builder.setPositiveButton(
|
||||
positiveBtnLabel,
|
||||
(dialogInterface, which) -> {
|
||||
Util.runOnAnyBackgroundThread(() -> dcContext.deleteMsgs(messageIds));
|
||||
finish();
|
||||
});
|
||||
|
||||
if(dcChat.isEncrypted() && dcChat.canSend() && !dcChat.isSelfTalk() && dcMsg.isOutgoing()) {
|
||||
builder.setNegativeButton(R.string.delete_for_everyone, (d, which) -> {
|
||||
Util.runOnAnyBackgroundThread(() -> dcContext.sendDeleteRequest(messageIds));
|
||||
finish();
|
||||
});
|
||||
if (dcChat.isEncrypted() && dcChat.canSend() && !dcChat.isSelfTalk() && dcMsg.isOutgoing()) {
|
||||
builder.setNegativeButton(
|
||||
R.string.delete_for_everyone,
|
||||
(d, which) -> {
|
||||
Util.runOnAnyBackgroundThread(() -> dcContext.sendDeleteRequest(messageIds));
|
||||
finish();
|
||||
});
|
||||
AlertDialog dialog = builder.show();
|
||||
Util.redButton(dialog, AlertDialog.BUTTON_NEGATIVE);
|
||||
Util.redPositiveButton(dialog);
|
||||
@@ -387,8 +415,8 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
menu.findItem(R.id.show_in_chat).setVisible(false);
|
||||
}
|
||||
|
||||
if (editAvatarChatId==0) {
|
||||
menu.findItem(R.id.media_preview__edit).setVisible(false);
|
||||
if (editAvatarChatId == 0) {
|
||||
menu.findItem(R.id.media_preview__edit).setVisible(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -430,7 +458,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private @Nullable MediaItem getCurrentMediaItem() {
|
||||
MediaItemAdapter adapter = (MediaItemAdapter)mediaPager.getAdapter();
|
||||
MediaItemAdapter adapter = (MediaItemAdapter) mediaPager.getAdapter();
|
||||
|
||||
if (adapter != null) {
|
||||
return adapter.getMediaItemFor(mediaPager.getCurrentItem());
|
||||
@@ -449,23 +477,22 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<DcMediaGalleryElement> loader, @Nullable DcMediaGalleryElement data) {
|
||||
public void onLoadFinished(
|
||||
Loader<DcMediaGalleryElement> loader, @Nullable DcMediaGalleryElement data) {
|
||||
if (data != null) {
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
DcMediaPagerAdapter adapter = new DcMediaPagerAdapter(this, GlideApp.with(this),
|
||||
getWindow(), data, leftIsRecent);
|
||||
DcMediaPagerAdapter adapter =
|
||||
new DcMediaPagerAdapter(this, GlideApp.with(this), getWindow(), data, leftIsRecent);
|
||||
mediaPager.setAdapter(adapter);
|
||||
adapter.setActive(true);
|
||||
|
||||
if (restartItem < 0) mediaPager.setCurrentItem(data.getPosition());
|
||||
else mediaPager.setCurrentItem(restartItem);
|
||||
else mediaPager.setCurrentItem(restartItem);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<DcMediaGalleryElement> loader) {
|
||||
|
||||
}
|
||||
public void onLoaderReset(Loader<DcMediaGalleryElement> loader) {}
|
||||
|
||||
private class ViewPagerListener extends ExtendedOnPageChangedListener {
|
||||
|
||||
@@ -473,7 +500,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
public void onPageSelected(int position) {
|
||||
super.onPageSelected(position);
|
||||
|
||||
MediaItemAdapter adapter = (MediaItemAdapter)mediaPager.getAdapter();
|
||||
MediaItemAdapter adapter = (MediaItemAdapter) mediaPager.getAdapter();
|
||||
|
||||
if (adapter != null) {
|
||||
MediaItem item = adapter.getMediaItemFor(position);
|
||||
@@ -483,10 +510,9 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onPageUnselected(int position) {
|
||||
MediaItemAdapter adapter = (MediaItemAdapter)mediaPager.getAdapter();
|
||||
MediaItemAdapter adapter = (MediaItemAdapter) mediaPager.getAdapter();
|
||||
|
||||
if (adapter != null) {
|
||||
try {
|
||||
@@ -503,25 +529,29 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
private static class SingleItemPagerAdapter extends PagerAdapter implements MediaItemAdapter {
|
||||
|
||||
private final GlideRequests glideRequests;
|
||||
private final Window window;
|
||||
private final Uri uri;
|
||||
private final String name;
|
||||
private final String mediaType;
|
||||
private final long size;
|
||||
private final Window window;
|
||||
private final Uri uri;
|
||||
private final String name;
|
||||
private final String mediaType;
|
||||
private final long size;
|
||||
|
||||
private final LayoutInflater inflater;
|
||||
|
||||
SingleItemPagerAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests,
|
||||
@NonNull Window window, @NonNull Uri uri, @Nullable String name, @NonNull String mediaType,
|
||||
long size)
|
||||
{
|
||||
SingleItemPagerAdapter(
|
||||
@NonNull Context context,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Window window,
|
||||
@NonNull Uri uri,
|
||||
@Nullable String name,
|
||||
@NonNull String mediaType,
|
||||
long size) {
|
||||
this.glideRequests = glideRequests;
|
||||
this.window = window;
|
||||
this.uri = uri;
|
||||
this.name = name;
|
||||
this.mediaType = mediaType;
|
||||
this.size = size;
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.window = window;
|
||||
this.uri = uri;
|
||||
this.name = name;
|
||||
this.mediaType = mediaType;
|
||||
this.size = size;
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -536,7 +566,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) {
|
||||
View itemView = inflater.inflate(R.layout.media_view_page, container, false);
|
||||
View itemView = inflater.inflate(R.layout.media_view_page, container, false);
|
||||
MediaView mediaView = itemView.findViewById(R.id.media_view);
|
||||
|
||||
try {
|
||||
@@ -552,10 +582,10 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
MediaView mediaView = ((FrameLayout)object).findViewById(R.id.media_view);
|
||||
MediaView mediaView = ((FrameLayout) object).findViewById(R.id.media_view);
|
||||
mediaView.cleanup();
|
||||
|
||||
container.removeView((FrameLayout)object);
|
||||
container.removeView((FrameLayout) object);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -564,33 +594,33 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause(int position) {
|
||||
|
||||
}
|
||||
public void pause(int position) {}
|
||||
}
|
||||
|
||||
private static class DcMediaPagerAdapter extends PagerAdapter implements MediaItemAdapter {
|
||||
|
||||
private final WeakHashMap<Integer, MediaView> mediaViews = new WeakHashMap<>();
|
||||
|
||||
private final Context context;
|
||||
private final Context context;
|
||||
private final GlideRequests glideRequests;
|
||||
private final Window window;
|
||||
private final Window window;
|
||||
private final DcMediaGalleryElement gallery;
|
||||
private final boolean leftIsRecent;
|
||||
private final boolean leftIsRecent;
|
||||
|
||||
private boolean active;
|
||||
private int autoPlayPosition;
|
||||
private int autoPlayPosition;
|
||||
|
||||
DcMediaPagerAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests,
|
||||
@NonNull Window window, @NonNull DcMediaGalleryElement gallery,
|
||||
boolean leftIsRecent)
|
||||
{
|
||||
this.context = context.getApplicationContext();
|
||||
this.glideRequests = glideRequests;
|
||||
this.window = window;
|
||||
this.gallery = gallery;
|
||||
this.leftIsRecent = leftIsRecent;
|
||||
DcMediaPagerAdapter(
|
||||
@NonNull Context context,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Window window,
|
||||
@NonNull DcMediaGalleryElement gallery,
|
||||
boolean leftIsRecent) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.glideRequests = glideRequests;
|
||||
this.window = window;
|
||||
this.gallery = gallery;
|
||||
this.leftIsRecent = leftIsRecent;
|
||||
this.autoPlayPosition = gallery.getPosition();
|
||||
}
|
||||
|
||||
@@ -602,7 +632,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
public int getCount() {
|
||||
if (!active) return 0;
|
||||
else return gallery.getCount();
|
||||
else return gallery.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -612,10 +642,11 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) {
|
||||
View itemView = LayoutInflater.from(context).inflate(R.layout.media_view_page, container, false);
|
||||
MediaView mediaView = itemView.findViewById(R.id.media_view);
|
||||
boolean autoplay = position == autoPlayPosition;
|
||||
int cursorPosition = getCursorPosition(position);
|
||||
View itemView =
|
||||
LayoutInflater.from(context).inflate(R.layout.media_view_page, container, false);
|
||||
MediaView mediaView = itemView.findViewById(R.id.media_view);
|
||||
boolean autoplay = position == autoPlayPosition;
|
||||
int cursorPosition = getCursorPosition(position);
|
||||
|
||||
autoPlayPosition = -1;
|
||||
|
||||
@@ -625,8 +656,14 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
mediaView.set(glideRequests, window, Uri.fromFile(msg.getFileAsFile()), msg.getFilename(),
|
||||
msg.getFilemime(), msg.getFilebytes(), autoplay);
|
||||
mediaView.set(
|
||||
glideRequests,
|
||||
window,
|
||||
Uri.fromFile(msg.getFileAsFile()),
|
||||
msg.getFilename(),
|
||||
msg.getFilemime(),
|
||||
msg.getFilebytes(),
|
||||
autoplay);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
@@ -639,27 +676,28 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
MediaView mediaView = ((FrameLayout)object).findViewById(R.id.media_view);
|
||||
MediaView mediaView = ((FrameLayout) object).findViewById(R.id.media_view);
|
||||
mediaView.cleanup();
|
||||
|
||||
mediaViews.remove(position);
|
||||
container.removeView((FrameLayout)object);
|
||||
container.removeView((FrameLayout) object);
|
||||
}
|
||||
|
||||
public MediaItem getMediaItemFor(int position) {
|
||||
gallery.moveToPosition(getCursorPosition(position));
|
||||
DcMsg msg = gallery.getMessage();
|
||||
DcMsg msg = gallery.getMessage();
|
||||
|
||||
if (msg.getFile() == null) throw new AssertionError();
|
||||
|
||||
return new MediaItem(Recipient.fromChat(context, msg.getId()),
|
||||
Uri.fromFile(msg.getFileAsFile()),
|
||||
msg.getFilename(),
|
||||
msg.getFilemime(),
|
||||
msg.getId(),
|
||||
msg.getDateReceived(),
|
||||
msg.getFilebytes(),
|
||||
msg.isOutgoing());
|
||||
return new MediaItem(
|
||||
Recipient.fromChat(context, msg.getId()),
|
||||
Uri.fromFile(msg.getFileAsFile()),
|
||||
msg.getFilename(),
|
||||
msg.getFilemime(),
|
||||
msg.getId(),
|
||||
msg.getDateReceived(),
|
||||
msg.getFilebytes(),
|
||||
msg.isOutgoing());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -670,42 +708,43 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private int getCursorPosition(int position) {
|
||||
if (leftIsRecent) return position;
|
||||
else return gallery.getCount() - 1 - position;
|
||||
else return gallery.getCount() - 1 - position;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MediaItem {
|
||||
private final @Nullable Recipient recipient;
|
||||
private final @NonNull Uri uri;
|
||||
private final @Nullable String name;
|
||||
private final @NonNull String type;
|
||||
private final int msgId;
|
||||
private final long date;
|
||||
private final long size;
|
||||
private final boolean outgoing;
|
||||
private final @Nullable Recipient recipient;
|
||||
private final @NonNull Uri uri;
|
||||
private final @Nullable String name;
|
||||
private final @NonNull String type;
|
||||
private final int msgId;
|
||||
private final long date;
|
||||
private final long size;
|
||||
private final boolean outgoing;
|
||||
|
||||
private MediaItem(@Nullable Recipient recipient,
|
||||
@NonNull Uri uri,
|
||||
@Nullable String name,
|
||||
@NonNull String type,
|
||||
int msgId,
|
||||
long date,
|
||||
long size,
|
||||
boolean outgoing)
|
||||
{
|
||||
this.recipient = recipient;
|
||||
this.uri = uri;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.msgId = msgId;
|
||||
this.date = date;
|
||||
this.size = size;
|
||||
this.outgoing = outgoing;
|
||||
private MediaItem(
|
||||
@Nullable Recipient recipient,
|
||||
@NonNull Uri uri,
|
||||
@Nullable String name,
|
||||
@NonNull String type,
|
||||
int msgId,
|
||||
long date,
|
||||
long size,
|
||||
boolean outgoing) {
|
||||
this.recipient = recipient;
|
||||
this.uri = uri;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.msgId = msgId;
|
||||
this.date = date;
|
||||
this.size = size;
|
||||
this.outgoing = outgoing;
|
||||
}
|
||||
}
|
||||
|
||||
interface MediaItemAdapter {
|
||||
MediaItem getMediaItemFor(int position);
|
||||
|
||||
void pause(int position);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,16 +10,14 @@ import android.view.Menu;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import java.util.Set;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
@@ -27,12 +25,8 @@ import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class MessageSelectorFragment
|
||||
extends Fragment
|
||||
implements DcEventCenter.DcEventDelegate
|
||||
{
|
||||
public abstract class MessageSelectorFragment extends Fragment
|
||||
implements DcEventCenter.DcEventDelegate {
|
||||
protected ActionMode actionMode;
|
||||
|
||||
protected abstract void setCorrectMenuVisibility(Menu menu);
|
||||
@@ -43,7 +37,7 @@ public abstract class MessageSelectorFragment
|
||||
|
||||
protected DcMsg getSelectedMessageRecord(Set<DcMsg> messageRecords) {
|
||||
if (messageRecords.size() == 1) return messageRecords.iterator().next();
|
||||
else throw new AssertionError();
|
||||
else throw new AssertionError();
|
||||
}
|
||||
|
||||
protected void handleDisplayDetails(DcMsg dcMsg) {
|
||||
@@ -51,7 +45,8 @@ public abstract class MessageSelectorFragment
|
||||
TextView detailsText = view.findViewById(R.id.details_text);
|
||||
detailsText.setText(DcHelper.getContext(getContext()).getMsgInfo(dcMsg.getId()));
|
||||
|
||||
AlertDialog d = new AlertDialog.Builder(getActivity())
|
||||
AlertDialog d =
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setView(view)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create();
|
||||
@@ -62,16 +57,28 @@ public abstract class MessageSelectorFragment
|
||||
handleDeleteMessages(chatId, DcMsg.msgSetToIds(messageRecords), null, null);
|
||||
}
|
||||
|
||||
protected void handleDeleteMessages(int chatId, final Set<DcMsg> messageRecords, Consumer<int[]> deleteForMeListenerExtra, Consumer<int[]> deleteForAllListenerExtra) {
|
||||
handleDeleteMessages(chatId, DcMsg.msgSetToIds(messageRecords), deleteForMeListenerExtra, deleteForAllListenerExtra);
|
||||
protected void handleDeleteMessages(
|
||||
int chatId,
|
||||
final Set<DcMsg> messageRecords,
|
||||
Consumer<int[]> deleteForMeListenerExtra,
|
||||
Consumer<int[]> deleteForAllListenerExtra) {
|
||||
handleDeleteMessages(
|
||||
chatId,
|
||||
DcMsg.msgSetToIds(messageRecords),
|
||||
deleteForMeListenerExtra,
|
||||
deleteForAllListenerExtra);
|
||||
}
|
||||
|
||||
protected void handleDeleteMessages(int chatId, final int[] messageIds, Consumer<int[]> deleteForMeListenerExtra, Consumer<int[]> deleteForAllListenerExtra) {
|
||||
protected void handleDeleteMessages(
|
||||
int chatId,
|
||||
final int[] messageIds,
|
||||
Consumer<int[]> deleteForMeListenerExtra,
|
||||
Consumer<int[]> deleteForAllListenerExtra) {
|
||||
DcContext dcContext = DcHelper.getContext(getContext());
|
||||
DcChat dcChat = dcContext.getChat(chatId);
|
||||
boolean canDeleteForAll = true;
|
||||
if (dcChat.isEncrypted() && dcChat.canSend() && !dcChat.isSelfTalk()) {
|
||||
for(int msgId : messageIds) {
|
||||
for (int msgId : messageIds) {
|
||||
DcMsg msg = dcContext.getMsg(msgId);
|
||||
if (!msg.isOutgoing() || msg.isInfo()) {
|
||||
canDeleteForAll = false;
|
||||
@@ -82,26 +89,32 @@ public abstract class MessageSelectorFragment
|
||||
canDeleteForAll = false;
|
||||
}
|
||||
|
||||
String text = getActivity().getResources().getQuantityString(R.plurals.ask_delete_messages, messageIds.length, messageIds.length);
|
||||
String text =
|
||||
getActivity()
|
||||
.getResources()
|
||||
.getQuantityString(R.plurals.ask_delete_messages, messageIds.length, messageIds.length);
|
||||
int positiveBtnLabel = dcChat.isSelfTalk() ? R.string.delete : R.string.delete_for_me;
|
||||
|
||||
DialogInterface.OnClickListener deleteForMeListener = (d, which) -> {
|
||||
Util.runOnAnyBackgroundThread(() -> dcContext.deleteMsgs(messageIds));
|
||||
if (actionMode != null) actionMode.finish();
|
||||
if (deleteForMeListenerExtra != null) deleteForMeListenerExtra.accept(messageIds);
|
||||
};
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity())
|
||||
.setMessage(text)
|
||||
.setCancelable(true)
|
||||
.setNeutralButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(positiveBtnLabel, deleteForMeListener);
|
||||
DialogInterface.OnClickListener deleteForMeListener =
|
||||
(d, which) -> {
|
||||
Util.runOnAnyBackgroundThread(() -> dcContext.deleteMsgs(messageIds));
|
||||
if (actionMode != null) actionMode.finish();
|
||||
if (deleteForMeListenerExtra != null) deleteForMeListenerExtra.accept(messageIds);
|
||||
};
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(requireActivity())
|
||||
.setMessage(text)
|
||||
.setCancelable(true)
|
||||
.setNeutralButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(positiveBtnLabel, deleteForMeListener);
|
||||
|
||||
if(canDeleteForAll) {
|
||||
DialogInterface.OnClickListener deleteForAllListener = (d, which) -> {
|
||||
Util.runOnAnyBackgroundThread(() -> dcContext.sendDeleteRequest(messageIds));
|
||||
if (actionMode != null) actionMode.finish();
|
||||
if (deleteForAllListenerExtra != null) deleteForAllListenerExtra.accept(messageIds);
|
||||
};
|
||||
if (canDeleteForAll) {
|
||||
DialogInterface.OnClickListener deleteForAllListener =
|
||||
(d, which) -> {
|
||||
Util.runOnAnyBackgroundThread(() -> dcContext.sendDeleteRequest(messageIds));
|
||||
if (actionMode != null) actionMode.finish();
|
||||
if (deleteForAllListenerExtra != null) deleteForAllListenerExtra.accept(messageIds);
|
||||
};
|
||||
builder.setNegativeButton(R.string.delete_for_everyone, deleteForAllListener);
|
||||
AlertDialog dialog = builder.show();
|
||||
Util.redButton(dialog, AlertDialog.BUTTON_NEGATIVE);
|
||||
@@ -113,29 +126,36 @@ public abstract class MessageSelectorFragment
|
||||
}
|
||||
|
||||
protected void handleSaveAttachment(final Set<DcMsg> messageRecords) {
|
||||
SaveAttachmentTask.showWarningDialog(getContext(), (dialogInterface, i) -> {
|
||||
if (StorageUtil.canWriteToMediaStore(getContext())) {
|
||||
performSave(messageRecords);
|
||||
return;
|
||||
}
|
||||
SaveAttachmentTask.showWarningDialog(
|
||||
getContext(),
|
||||
(dialogInterface, i) -> {
|
||||
if (StorageUtil.canWriteToMediaStore(getContext())) {
|
||||
performSave(messageRecords);
|
||||
return;
|
||||
}
|
||||
|
||||
Permissions.with(getActivity())
|
||||
Permissions.with(getActivity())
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.alwaysGrantOnSdk30()
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(getString(R.string.perm_explain_access_to_storage_denied))
|
||||
.onAllGranted(() -> performSave(messageRecords))
|
||||
.execute();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void performSave(Set<DcMsg> messageRecords) {
|
||||
SaveAttachmentTask.Attachment[] attachments = new SaveAttachmentTask.Attachment[messageRecords.size()];
|
||||
SaveAttachmentTask.Attachment[] attachments =
|
||||
new SaveAttachmentTask.Attachment[messageRecords.size()];
|
||||
int index = 0;
|
||||
for (DcMsg message : messageRecords) {
|
||||
attachments[index] = new SaveAttachmentTask.Attachment(
|
||||
Uri.fromFile(message.getFileAsFile()), message.getFilemime(), message.getDateReceived(), message.getFilename());
|
||||
index++;
|
||||
attachments[index] =
|
||||
new SaveAttachmentTask.Attachment(
|
||||
Uri.fromFile(message.getFileAsFile()),
|
||||
message.getFilemime(),
|
||||
message.getDateReceived(),
|
||||
message.getFilename());
|
||||
index++;
|
||||
}
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(getContext());
|
||||
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachments);
|
||||
@@ -144,7 +164,9 @@ public abstract class MessageSelectorFragment
|
||||
protected void handleShowInChat(final DcMsg dcMsg) {
|
||||
Intent intent = new Intent(getContext(), ConversationActivity.class);
|
||||
intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcMsg.getChatId());
|
||||
intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, DcMsg.getMessagePosition(dcMsg, DcHelper.getContext(getContext())));
|
||||
intent.putExtra(
|
||||
ConversationActivity.STARTING_POSITION_EXTRA,
|
||||
DcMsg.getMessagePosition(dcMsg, DcHelper.getContext(getContext())));
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@@ -155,22 +177,24 @@ public abstract class MessageSelectorFragment
|
||||
protected void handleResendMessage(final Set<DcMsg> dcMsgsSet) {
|
||||
int[] ids = DcMsg.msgSetToIds(dcMsgsSet);
|
||||
DcContext dcContext = DcHelper.getContext(getContext());
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
boolean success = dcContext.resendMsgs(ids);
|
||||
Util.runOnMain(() -> {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null || activity.isFinishing()) return;
|
||||
if (success) {
|
||||
actionMode.finish();
|
||||
Toast.makeText(getContext(), R.string.sending, Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setMessage(dcContext.getLastError())
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
Util.runOnAnyBackgroundThread(
|
||||
() -> {
|
||||
boolean success = dcContext.resendMsgs(ids);
|
||||
Util.runOnMain(
|
||||
() -> {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null || activity.isFinishing()) return;
|
||||
if (success) {
|
||||
actionMode.finish();
|
||||
Toast.makeText(getContext(), R.string.sending, Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setMessage(dcContext.getLastError())
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MuteDialog {
|
||||
@@ -13,21 +11,35 @@ public class MuteDialog {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.menu_mute);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setItems(R.array.mute_durations, (dialog, which) -> {
|
||||
final long muteUntil;
|
||||
builder.setItems(
|
||||
R.array.mute_durations,
|
||||
(dialog, which) -> {
|
||||
final long muteUntil;
|
||||
|
||||
// See https://c.delta.chat/classdc__context__t.html#a6460395925d49d2053bc95224bf5ce37.
|
||||
switch (which) {
|
||||
case 0: muteUntil = TimeUnit.HOURS.toSeconds(1); break;
|
||||
case 1: muteUntil = TimeUnit.HOURS.toSeconds(8); break;
|
||||
case 2: muteUntil = TimeUnit.DAYS.toSeconds(1); break;
|
||||
case 3: muteUntil = TimeUnit.DAYS.toSeconds(7); break;
|
||||
case 4: muteUntil = -1; break; // mute forever
|
||||
default: muteUntil = 0; break;
|
||||
}
|
||||
// See https://c.delta.chat/classdc__context__t.html#a6460395925d49d2053bc95224bf5ce37.
|
||||
switch (which) {
|
||||
case 0:
|
||||
muteUntil = TimeUnit.HOURS.toSeconds(1);
|
||||
break;
|
||||
case 1:
|
||||
muteUntil = TimeUnit.HOURS.toSeconds(8);
|
||||
break;
|
||||
case 2:
|
||||
muteUntil = TimeUnit.DAYS.toSeconds(1);
|
||||
break;
|
||||
case 3:
|
||||
muteUntil = TimeUnit.DAYS.toSeconds(7);
|
||||
break;
|
||||
case 4:
|
||||
muteUntil = -1;
|
||||
break; // mute forever
|
||||
default:
|
||||
muteUntil = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
listener.onMuted(muteUntil);
|
||||
});
|
||||
listener.onMuted(muteUntil);
|
||||
});
|
||||
|
||||
builder.show();
|
||||
}
|
||||
@@ -35,5 +47,4 @@ public class MuteDialog {
|
||||
public interface MuteSelectionListener {
|
||||
void onMuted(long duration);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,26 +23,21 @@ import static org.thoughtcrime.securesms.util.ShareUtil.isRelayingMessageContent
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import chat.delta.rpc.types.SecurejoinSource;
|
||||
import chat.delta.rpc.types.SecurejoinUiPath;
|
||||
import com.b44t.messenger.DcContact;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.qr.QrActivity;
|
||||
import org.thoughtcrime.securesms.qr.QrCodeHandler;
|
||||
|
||||
import chat.delta.rpc.types.SecurejoinSource;
|
||||
import chat.delta.rpc.types.SecurejoinUiPath;
|
||||
|
||||
/**
|
||||
* Activity container for starting a new conversation.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class NewConversationActivity extends ContactSelectionActivity {
|
||||
|
||||
@@ -57,32 +52,34 @@ public class NewConversationActivity extends ContactSelectionActivity {
|
||||
|
||||
@Override
|
||||
public void onContactSelected(int contactId) {
|
||||
if(contactId == DcContact.DC_CONTACT_ID_NEW_GROUP) {
|
||||
if (contactId == DcContact.DC_CONTACT_ID_NEW_GROUP) {
|
||||
startActivity(new Intent(this, GroupCreateActivity.class));
|
||||
} else if(contactId == DcContact.DC_CONTACT_ID_NEW_UNENCRYPTED_GROUP) {
|
||||
} else if (contactId == DcContact.DC_CONTACT_ID_NEW_UNENCRYPTED_GROUP) {
|
||||
Intent intent = new Intent(this, GroupCreateActivity.class);
|
||||
intent.putExtra(GroupCreateActivity.UNENCRYPTED, true);
|
||||
startActivity(intent);
|
||||
} else if(contactId == DcContact.DC_CONTACT_ID_NEW_BROADCAST) {
|
||||
} else if (contactId == DcContact.DC_CONTACT_ID_NEW_BROADCAST) {
|
||||
Intent intent = new Intent(this, GroupCreateActivity.class);
|
||||
intent.putExtra(GroupCreateActivity.CREATE_BROADCAST, true);
|
||||
startActivity(intent);
|
||||
} else if (contactId == DcContact.DC_CONTACT_ID_QR_INVITE) {
|
||||
new IntentIntegrator(this).setCaptureActivity(QrActivity.class).initiateScan();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
final DcContext dcContext = DcHelper.getContext(this);
|
||||
if (dcContext.getChatIdByContactId(contactId)!=0) {
|
||||
if (dcContext.getChatIdByContactId(contactId) != 0) {
|
||||
openConversation(dcContext.getChatIdByContactId(contactId));
|
||||
} else {
|
||||
String name = dcContext.getContact(contactId).getDisplayName();
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(getString(R.string.ask_start_chat_with, name))
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
.setMessage(getString(R.string.ask_start_chat_with, name))
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(
|
||||
android.R.string.ok,
|
||||
(dialog, which) -> {
|
||||
openConversation(dcContext.createChatByContactId(contactId));
|
||||
}).show();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,7 +93,8 @@ public class NewConversationActivity extends ContactSelectionActivity {
|
||||
case IntentIntegrator.REQUEST_CODE:
|
||||
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
|
||||
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
|
||||
qrCodeHandler.handleOnlySecureJoinQr(scanResult.getContents(), SecurejoinSource.Scan, SecurejoinUiPath.NewContact);
|
||||
qrCodeHandler.handleOnlySecureJoinQr(
|
||||
scanResult.getContents(), SecurejoinSource.Scan, SecurejoinUiPath.NewContact);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
||||
|
||||
@@ -50,7 +49,9 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
||||
// The app is "locked" to an activity if you will.
|
||||
// In "Locked Mode" the user should not leave that activity otherwise the state would be lost -
|
||||
// so eg. tapping app icon or notifications MUST NOT replace activity stack.
|
||||
// However, sometimes it is fine to allow to pushing activities in these situations,
|
||||
// However, sometimes it is fine to allow to pushing activities in these situations,
|
||||
// like to see the logs or offline help.
|
||||
protected boolean allowInLockedMode() { return false; }
|
||||
protected boolean allowInLockedMode() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,18 +12,19 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
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 java.io.File;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
@@ -32,31 +33,25 @@ import org.thoughtcrime.securesms.util.ShareUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
|
||||
public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||
implements DcEventCenter.DcEventDelegate
|
||||
{
|
||||
implements DcEventCenter.DcEventDelegate {
|
||||
|
||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||
public static final String CONTACT_ID_EXTRA = "contact_id";
|
||||
|
||||
private static final int REQUEST_CODE_PICK_RINGTONE = 1;
|
||||
|
||||
private DcContext dcContext;
|
||||
private DcContext dcContext;
|
||||
private Rpc rpc;
|
||||
private int chatId;
|
||||
private boolean chatIsMultiUser;
|
||||
private boolean chatIsDeviceTalk;
|
||||
private boolean chatIsMailingList;
|
||||
private boolean chatIsOutBroadcast;
|
||||
private boolean chatIsInBroadcast;
|
||||
private int contactId;
|
||||
private boolean contactIsBot;
|
||||
private Toolbar toolbar;
|
||||
private int chatId;
|
||||
private boolean chatIsMultiUser;
|
||||
private boolean chatIsDeviceTalk;
|
||||
private boolean chatIsMailingList;
|
||||
private boolean chatIsOutBroadcast;
|
||||
private boolean chatIsInBroadcast;
|
||||
private int contactId;
|
||||
private boolean contactIsBot;
|
||||
private Toolbar toolbar;
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
@@ -110,7 +105,9 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
if (chatId != 0) {
|
||||
DcChat dcChat = dcContext.getChat(chatId);
|
||||
menu.findItem(R.id.menu_clone).setVisible(chatIsMultiUser && !chatIsInBroadcast && !chatIsOutBroadcast && !chatIsMailingList);
|
||||
menu.findItem(R.id.menu_clone)
|
||||
.setVisible(
|
||||
chatIsMultiUser && !chatIsInBroadcast && !chatIsOutBroadcast && !chatIsMailingList);
|
||||
if (chatIsDeviceTalk) {
|
||||
menu.findItem(R.id.edit_name).setVisible(false);
|
||||
menu.findItem(R.id.show_encr_info).setVisible(false);
|
||||
@@ -120,9 +117,7 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||
if (chatIsOutBroadcast) {
|
||||
canReceive = false;
|
||||
} else {
|
||||
if (!dcChat.isEncrypted()
|
||||
|| !dcChat.canSend()
|
||||
|| chatIsMailingList) {
|
||||
if (!dcChat.isEncrypted() || !dcChat.canSend() || chatIsMailingList) {
|
||||
menu.findItem(R.id.edit_name).setVisible(false);
|
||||
}
|
||||
}
|
||||
@@ -155,14 +150,18 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
MenuItem item = menu.findItem(R.id.block_contact);
|
||||
if(item!=null) {
|
||||
item.setTitle(dcContext.getContact(contactId).isBlocked()? R.string.menu_unblock_contact : R.string.menu_block_contact);
|
||||
if (item != null) {
|
||||
item.setTitle(
|
||||
dcContext.getContact(contactId).isBlocked()
|
||||
? R.string.menu_unblock_contact
|
||||
: R.string.menu_block_contact);
|
||||
Util.redMenuItem(menu, R.id.block_contact);
|
||||
}
|
||||
|
||||
item = menu.findItem(R.id.menu_mute_notifications);
|
||||
if(item!=null) {
|
||||
item.setTitle(dcContext.getChat(chatId).isMuted()? R.string.menu_unmute : R.string.menu_mute);
|
||||
if (item != null) {
|
||||
item.setTitle(
|
||||
dcContext.getChat(chatId).isMuted() ? R.string.menu_unmute : R.string.menu_mute);
|
||||
}
|
||||
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
@@ -182,48 +181,47 @@ public class ProfileActivity 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);
|
||||
contactIsBot = false;
|
||||
chatIsMultiUser = false;
|
||||
chatId = getIntent().getIntExtra(CHAT_ID_EXTRA, 0);
|
||||
contactId = getIntent().getIntExtra(CONTACT_ID_EXTRA, 0);
|
||||
contactIsBot = false;
|
||||
chatIsMultiUser = false;
|
||||
chatIsDeviceTalk = false;
|
||||
chatIsMailingList= false;
|
||||
chatIsMailingList = false;
|
||||
chatIsInBroadcast = false;
|
||||
chatIsOutBroadcast = false;
|
||||
|
||||
if (contactId!=0) {
|
||||
if (contactId != 0) {
|
||||
DcContact dcContact = dcContext.getContact(contactId);
|
||||
chatId = dcContext.getChatIdByContactId(contactId);
|
||||
contactIsBot = dcContact.isBot();
|
||||
}
|
||||
|
||||
if(chatId!=0) {
|
||||
if (chatId != 0) {
|
||||
DcChat dcChat = dcContext.getChat(chatId);
|
||||
chatIsMultiUser = dcChat.isMultiUser();
|
||||
chatIsDeviceTalk = dcChat.isDeviceTalk();
|
||||
chatIsMailingList = dcChat.isMailingList();
|
||||
chatIsInBroadcast = dcChat.isInBroadcast();
|
||||
chatIsOutBroadcast = dcChat.isOutBroadcast();
|
||||
if(!chatIsMultiUser) {
|
||||
if (!chatIsMultiUser) {
|
||||
final int[] members = dcContext.getChatContacts(chatId);
|
||||
contactId = members.length>=1? members[0] : 0;
|
||||
contactId = members.length >= 1 ? members[0] : 0;
|
||||
}
|
||||
}
|
||||
|
||||
this.toolbar = ViewUtil.findById(this, R.id.toolbar);
|
||||
this.toolbar = ViewUtil.findById(this, R.id.toolbar);
|
||||
}
|
||||
|
||||
private boolean isContactProfile() {
|
||||
// contact-profiles are profiles without a chat or with a one-to-one chat
|
||||
return contactId!=0 && (chatId==0 || !chatIsMultiUser);
|
||||
return contactId != 0 && (chatId == 0 || !chatIsMultiUser);
|
||||
}
|
||||
|
||||
private boolean isSelfProfile() {
|
||||
return isContactProfile() && contactId==DcContact.DC_CONTACT_ID_SELF;
|
||||
return isContactProfile() && contactId == DcContact.DC_CONTACT_ID_SELF;
|
||||
}
|
||||
|
||||
// handle events
|
||||
@@ -270,8 +268,7 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||
private void onNotifyOnOff() {
|
||||
if (dcContext.getChat(chatId).isMuted()) {
|
||||
setMuted(0);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
MuteDialog.show(this, this::setMuted);
|
||||
}
|
||||
}
|
||||
@@ -286,7 +283,7 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||
Uri current = Prefs.getChatRingtone(this, dcContext.getAccountId(), chatId);
|
||||
Uri defaultUri = Prefs.getNotificationRingtone(this);
|
||||
|
||||
if (current == null) current = Settings.System.DEFAULT_NOTIFICATION_URI;
|
||||
if (current == null) current = Settings.System.DEFAULT_NOTIFICATION_URI;
|
||||
else if (current.toString().isEmpty()) current = null;
|
||||
|
||||
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
|
||||
@@ -301,15 +298,23 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private void onVibrateSettings() {
|
||||
int checkedItem = Prefs.getChatVibrate(this, dcContext.getAccountId(), chatId).getId();
|
||||
int[] selectedChoice = new int[]{checkedItem};
|
||||
int[] selectedChoice = new int[] {checkedItem};
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.pref_vibrate)
|
||||
.setSingleChoiceItems(R.array.recipient_vibrate_entries, checkedItem,
|
||||
(dialog, which) -> selectedChoice[0] = which)
|
||||
.setPositiveButton(R.string.ok,
|
||||
(dialog, which) -> Prefs.setChatVibrate(this, dcContext.getAccountId(), chatId, Prefs.VibrateState.fromId(selectedChoice[0])))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
.setTitle(R.string.pref_vibrate)
|
||||
.setSingleChoiceItems(
|
||||
R.array.recipient_vibrate_entries,
|
||||
checkedItem,
|
||||
(dialog, which) -> selectedChoice[0] = which)
|
||||
.setPositiveButton(
|
||||
R.string.ok,
|
||||
(dialog, which) ->
|
||||
Prefs.setChatVibrate(
|
||||
this,
|
||||
dcContext.getAccountId(),
|
||||
chatId,
|
||||
Prefs.VibrateState.fromId(selectedChoice[0])))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
public void onEnlargeAvatar() {
|
||||
@@ -317,7 +322,7 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||
String title;
|
||||
Uri profileImageUri;
|
||||
boolean enlargeAvatar = true;
|
||||
if(chatId!=0) {
|
||||
if (chatId != 0) {
|
||||
DcChat dcChat = dcContext.getChat(chatId);
|
||||
profileImagePath = dcChat.getProfileImage();
|
||||
title = dcChat.getName();
|
||||
@@ -338,11 +343,10 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||
intent.setDataAndType(profileImageUri, type);
|
||||
intent.putExtra(MediaPreviewActivity.ACTIVITY_TITLE_EXTRA, title);
|
||||
intent.putExtra( // show edit-button, if the user is allowed to edit the name/avatar
|
||||
MediaPreviewActivity.EDIT_AVATAR_CHAT_ID,
|
||||
(chatIsMultiUser && !chatIsInBroadcast && !chatIsMailingList) ? chatId : 0
|
||||
);
|
||||
MediaPreviewActivity.EDIT_AVATAR_CHAT_ID,
|
||||
(chatIsMultiUser && !chatIsInBroadcast && !chatIsMailingList) ? chatId : 0);
|
||||
startActivity(intent);
|
||||
} else if (chatIsMultiUser){
|
||||
} else if (chatIsMultiUser) {
|
||||
onEditName();
|
||||
}
|
||||
}
|
||||
@@ -355,8 +359,7 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||
intent.putExtra(GroupCreateActivity.EDIT_GROUP_CHAT_ID, chatId);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
int accountId = dcContext.getAccountId();
|
||||
DcContact dcContact = dcContext.getContact(contactId);
|
||||
|
||||
@@ -375,14 +378,16 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||
.setTitle(R.string.menu_edit_name)
|
||||
.setMessage(getString(R.string.edit_name_explain, authName))
|
||||
.setView(gl)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
|
||||
String newName = inputField.getText().toString();
|
||||
try {
|
||||
rpc.changeContactName(accountId, contactId, newName);
|
||||
} catch (RpcException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
})
|
||||
.setPositiveButton(
|
||||
android.R.string.ok,
|
||||
(dialog, whichButton) -> {
|
||||
String newName = inputField.getText().toString();
|
||||
try {
|
||||
rpc.changeContactName(accountId, contactId, newName);
|
||||
} catch (RpcException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
@@ -407,33 +412,46 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void onEncrInfo() {
|
||||
String infoStr = isContactProfile() ?
|
||||
dcContext.getContactEncrInfo(contactId) : dcContext.getChatEncrInfo(chatId);
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(infoStr)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
String infoStr =
|
||||
isContactProfile()
|
||||
? dcContext.getContactEncrInfo(contactId)
|
||||
: dcContext.getChatEncrInfo(chatId);
|
||||
AlertDialog dialog =
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(infoStr)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
TextView messageView = dialog.findViewById(android.R.id.message);
|
||||
if (messageView != null) {
|
||||
messageView.setTextIsSelectable(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void onBlockContact() {
|
||||
DcContact dcContact = dcContext.getContact(contactId);
|
||||
if(dcContact.isBlocked()) {
|
||||
if (dcContact.isBlocked()) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.ask_unblock_contact)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.menu_unblock_contact, (dialog, which) -> {
|
||||
dcContext.blockContact(contactId, 0);
|
||||
}).show();
|
||||
}
|
||||
else {
|
||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.ask_block_contact)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.menu_block_contact, (d, which) -> {
|
||||
dcContext.blockContact(contactId, 1);
|
||||
}).show();
|
||||
.setPositiveButton(
|
||||
R.string.menu_unblock_contact,
|
||||
(dialog, which) -> {
|
||||
dcContext.blockContact(contactId, 0);
|
||||
})
|
||||
.show();
|
||||
} else {
|
||||
AlertDialog dialog =
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.ask_block_contact)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(
|
||||
R.string.menu_block_contact,
|
||||
(d, which) -> {
|
||||
dcContext.blockContact(contactId, 1);
|
||||
})
|
||||
.show();
|
||||
Util.redPositiveButton(dialog);
|
||||
}
|
||||
}
|
||||
@@ -447,15 +465,16 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode==REQUEST_CODE_PICK_RINGTONE && resultCode== Activity.RESULT_OK && data!=null) {
|
||||
if (requestCode == REQUEST_CODE_PICK_RINGTONE
|
||||
&& resultCode == Activity.RESULT_OK
|
||||
&& data != null) {
|
||||
Uri value = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
|
||||
Uri defaultValue = Prefs.getNotificationRingtone(this);
|
||||
|
||||
if (defaultValue.equals(value)) value = null;
|
||||
else if (value == null) value = Uri.EMPTY;
|
||||
else if (value == null) value = Uri.EMPTY;
|
||||
|
||||
Prefs.setChatRingtone(this, dcContext.getAccountId(), chatId, value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,36 +6,30 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcChatlist;
|
||||
import com.b44t.messenger.DcContact;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcLot;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
|
||||
public class ProfileAdapter extends RecyclerView.Adapter
|
||||
{
|
||||
public class ProfileAdapter extends RecyclerView.Adapter {
|
||||
private static final String TAG = ProfileAdapter.class.getSimpleName();
|
||||
|
||||
public static final int ITEM_AVATAR = 10;
|
||||
@@ -50,22 +44,22 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
public static final int ITEM_MEMBERS = 55;
|
||||
public static final int ITEM_SHARED_CHATS = 60;
|
||||
|
||||
private final @NonNull Context context;
|
||||
private final @NonNull Fragment fragment;
|
||||
private final @NonNull DcContext dcContext;
|
||||
private @Nullable DcChat dcChat;
|
||||
private @Nullable DcContact dcContact;
|
||||
private final @NonNull Context context;
|
||||
private final @NonNull Fragment fragment;
|
||||
private final @NonNull DcContext dcContext;
|
||||
private @Nullable DcChat dcChat;
|
||||
private @Nullable DcContact dcContact;
|
||||
|
||||
private final @NonNull ArrayList<ItemData> itemData = new ArrayList<>();
|
||||
private DcChatlist itemDataSharedChats;
|
||||
private String itemDataStatusText;
|
||||
private boolean isOutBroadcast;
|
||||
private int[] memberList;
|
||||
private final Set<Integer> selectedMembers;
|
||||
private final @NonNull ArrayList<ItemData> itemData = new ArrayList<>();
|
||||
private DcChatlist itemDataSharedChats;
|
||||
private String itemDataStatusText;
|
||||
private boolean isOutBroadcast;
|
||||
private int[] memberList;
|
||||
private final Set<Integer> selectedMembers;
|
||||
|
||||
private final LayoutInflater layoutInflater;
|
||||
private final ItemClickListener clickListener;
|
||||
private final GlideRequests glideRequests;
|
||||
private final LayoutInflater layoutInflater;
|
||||
private final ItemClickListener clickListener;
|
||||
private final GlideRequests glideRequests;
|
||||
|
||||
static class ItemData {
|
||||
final int viewType;
|
||||
@@ -82,27 +76,29 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
this(viewType, contactId, chatlistIndex, null, 0);
|
||||
}
|
||||
|
||||
private ItemData(int viewType, int contactId, int chatlistIndex, @Nullable String label, int icon) {
|
||||
this.viewType = viewType;
|
||||
this.contactId = contactId;
|
||||
private ItemData(
|
||||
int viewType, int contactId, int chatlistIndex, @Nullable String label, int icon) {
|
||||
this.viewType = viewType;
|
||||
this.contactId = contactId;
|
||||
this.chatlistIndex = chatlistIndex;
|
||||
this.label = label;
|
||||
this.icon = icon;
|
||||
this.label = label;
|
||||
this.icon = icon;
|
||||
}
|
||||
};
|
||||
}
|
||||
;
|
||||
|
||||
public ProfileAdapter(@NonNull Fragment fragment,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@Nullable ItemClickListener clickListener)
|
||||
{
|
||||
public ProfileAdapter(
|
||||
@NonNull Fragment fragment,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@Nullable ItemClickListener clickListener) {
|
||||
super();
|
||||
this.fragment = fragment;
|
||||
this.context = fragment.requireContext();
|
||||
this.glideRequests = glideRequests;
|
||||
this.clickListener = clickListener;
|
||||
this.dcContext = DcHelper.getContext(context);
|
||||
this.fragment = fragment;
|
||||
this.context = fragment.requireContext();
|
||||
this.glideRequests = glideRequests;
|
||||
this.clickListener = clickListener;
|
||||
this.dcContext = DcHelper.getContext(context);
|
||||
this.layoutInflater = LayoutInflater.from(context);
|
||||
this.selectedMembers= new HashSet<>();
|
||||
this.selectedMembers = new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,32 +121,47 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
@Override
|
||||
public ProfileAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
if (viewType == ITEM_HEADER) {
|
||||
final View item = LayoutInflater.from(context).inflate(R.layout.contact_selection_list_divider, parent, false);
|
||||
final View item =
|
||||
LayoutInflater.from(context)
|
||||
.inflate(R.layout.contact_selection_list_divider, parent, false);
|
||||
return new ViewHolder(item);
|
||||
} else if (viewType == ITEM_DIVIDER) {
|
||||
final View item = LayoutInflater.from(context).inflate(R.layout.profile_divider, parent, false);
|
||||
final View item =
|
||||
LayoutInflater.from(context).inflate(R.layout.profile_divider, parent, false);
|
||||
return new ViewHolder(item);
|
||||
} else if (viewType == ITEM_MEMBERS) {
|
||||
final ContactSelectionListItem item = (ContactSelectionListItem)layoutInflater.inflate(R.layout.contact_selection_list_item, parent, false);
|
||||
final ContactSelectionListItem item =
|
||||
(ContactSelectionListItem)
|
||||
layoutInflater.inflate(R.layout.contact_selection_list_item, parent, false);
|
||||
return new ViewHolder(item);
|
||||
} else if (viewType == ITEM_SHARED_CHATS) {
|
||||
final ConversationListItem item = (ConversationListItem)layoutInflater.inflate(R.layout.conversation_list_item_view, parent, false);
|
||||
final ConversationListItem item =
|
||||
(ConversationListItem)
|
||||
layoutInflater.inflate(R.layout.conversation_list_item_view, parent, false);
|
||||
item.hideItemDivider();
|
||||
return new ViewHolder(item);
|
||||
} else if (viewType == ITEM_SIGNATURE) {
|
||||
final ProfileStatusItem item = (ProfileStatusItem)layoutInflater.inflate(R.layout.profile_status_item, parent, false);
|
||||
final ProfileStatusItem item =
|
||||
(ProfileStatusItem) layoutInflater.inflate(R.layout.profile_status_item, parent, false);
|
||||
return new ViewHolder(item);
|
||||
} else if (viewType == ITEM_AVATAR) {
|
||||
final ProfileAvatarItem item = (ProfileAvatarItem)layoutInflater.inflate(R.layout.profile_avatar_item, parent, false);
|
||||
final ProfileAvatarItem item =
|
||||
(ProfileAvatarItem) layoutInflater.inflate(R.layout.profile_avatar_item, parent, false);
|
||||
return new ViewHolder(item);
|
||||
} else if (viewType == ITEM_ALL_MEDIA_BUTTON || viewType == ITEM_SEND_MESSAGE_BUTTON) {
|
||||
final ProfileTextItem item = (ProfileTextItem)layoutInflater.inflate(R.layout.profile_text_item_button, parent, false);
|
||||
final ProfileTextItem item =
|
||||
(ProfileTextItem)
|
||||
layoutInflater.inflate(R.layout.profile_text_item_button, parent, false);
|
||||
return new ViewHolder(item);
|
||||
} else if (viewType == ITEM_LAST_SEEN || viewType == ITEM_INTRODUCED_BY || viewType == ITEM_BLOCKED) {
|
||||
final ProfileTextItem item = (ProfileTextItem)layoutInflater.inflate(R.layout.profile_text_item_small, parent, false);
|
||||
} else if (viewType == ITEM_LAST_SEEN
|
||||
|| viewType == ITEM_INTRODUCED_BY
|
||||
|| viewType == ITEM_BLOCKED) {
|
||||
final ProfileTextItem item =
|
||||
(ProfileTextItem) layoutInflater.inflate(R.layout.profile_text_item_small, parent, false);
|
||||
return new ViewHolder(item);
|
||||
} else {
|
||||
final ProfileTextItem item = (ProfileTextItem)layoutInflater.inflate(R.layout.profile_text_item, parent, false);
|
||||
final ProfileTextItem item =
|
||||
(ProfileTextItem) layoutInflater.inflate(R.layout.profile_text_item, parent, false);
|
||||
return new ViewHolder(item);
|
||||
}
|
||||
}
|
||||
@@ -170,11 +181,9 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
|
||||
if (contactId == DcContact.DC_CONTACT_ID_ADD_MEMBER) {
|
||||
name = context.getString(R.string.group_add_members);
|
||||
}
|
||||
else if (contactId == DcContact.DC_CONTACT_ID_QR_INVITE) {
|
||||
} else if (contactId == DcContact.DC_CONTACT_ID_QR_INVITE) {
|
||||
name = context.getString(R.string.qrshow_title);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
dcContact = dcContext.getContact(contactId);
|
||||
name = dcContact.getDisplayName();
|
||||
addr = dcContact.getAddr();
|
||||
@@ -184,9 +193,12 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
contactItem.set(glideRequests, contactId, dcContact, name, addr, label, false, true);
|
||||
contactItem.setSelected(selectedMembers.contains(contactId));
|
||||
contactItem.setOnClickListener(view -> clickListener.onMemberClicked(contactId));
|
||||
contactItem.setOnLongClickListener(view -> {clickListener.onMemberLongClicked(contactId); return true;});
|
||||
}
|
||||
else if (holder.itemView instanceof ConversationListItem) {
|
||||
contactItem.setOnLongClickListener(
|
||||
view -> {
|
||||
clickListener.onMemberLongClicked(contactId);
|
||||
return true;
|
||||
});
|
||||
} else if (holder.itemView instanceof ConversationListItem) {
|
||||
ConversationListItem conversationListItem = (ConversationListItem) holder.itemView;
|
||||
int chatlistIndex = data.chatlistIndex;
|
||||
|
||||
@@ -194,39 +206,55 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
DcChat chat = dcContext.getChat(chatId);
|
||||
DcLot summary = itemDataSharedChats.getSummary(chatlistIndex, chat);
|
||||
|
||||
conversationListItem.bind(DcHelper.getThreadRecord(context, summary, chat),
|
||||
itemDataSharedChats.getMsgId(chatlistIndex), summary, glideRequests,
|
||||
Collections.emptySet(), false);
|
||||
conversationListItem.bind(
|
||||
DcHelper.getThreadRecord(context, summary, chat),
|
||||
itemDataSharedChats.getMsgId(chatlistIndex),
|
||||
summary,
|
||||
glideRequests,
|
||||
Collections.emptySet(),
|
||||
false);
|
||||
conversationListItem.setOnClickListener(view -> clickListener.onSharedChatClicked(chatId));
|
||||
}
|
||||
else if(holder.itemView instanceof ProfileStatusItem) {
|
||||
} else if (holder.itemView instanceof ProfileStatusItem) {
|
||||
ProfileStatusItem item = (ProfileStatusItem) holder.itemView;
|
||||
item.setOnLongClickListener(view -> {clickListener.onStatusLongClicked(dcContact == null); return true;});
|
||||
item.setOnLongClickListener(
|
||||
view -> {
|
||||
clickListener.onStatusLongClicked(dcContact == null);
|
||||
return true;
|
||||
});
|
||||
item.set(data.label);
|
||||
}
|
||||
else if(holder.itemView instanceof ProfileAvatarItem) {
|
||||
} else if (holder.itemView instanceof ProfileAvatarItem) {
|
||||
ProfileAvatarItem item = (ProfileAvatarItem) holder.itemView;
|
||||
item.setAvatarClickListener(view -> clickListener.onAvatarClicked());
|
||||
item.set(glideRequests, dcChat, dcContact, memberList);
|
||||
}
|
||||
else if(holder.itemView instanceof ProfileTextItem) {
|
||||
} else if (holder.itemView instanceof ProfileTextItem) {
|
||||
ProfileTextItem item = (ProfileTextItem) holder.itemView;
|
||||
item.setOnClickListener(view -> clickListener.onSettingsClicked(data.viewType));
|
||||
boolean tintIcon = data.viewType != ITEM_INTRODUCED_BY && data.viewType != ITEM_BLOCKED;
|
||||
item.set(data.label, data.icon, tintIcon);
|
||||
if (data.viewType == ITEM_BLOCKED) {
|
||||
int padding = (int)((float)context.getResources().getDimensionPixelSize(R.dimen.contact_list_normal_padding) * 1.2);
|
||||
item.setPadding(item.getPaddingLeft(), item.getPaddingTop(), item.getPaddingRight(), padding);
|
||||
int padding =
|
||||
(int)
|
||||
((float)
|
||||
context
|
||||
.getResources()
|
||||
.getDimensionPixelSize(R.dimen.contact_list_normal_padding)
|
||||
* 1.2);
|
||||
item.setPadding(
|
||||
item.getPaddingLeft(), item.getPaddingTop(), item.getPaddingRight(), padding);
|
||||
} else if (data.viewType == ITEM_INTRODUCED_BY) {
|
||||
int padding = context.getResources().getDimensionPixelSize(R.dimen.contact_list_normal_padding);
|
||||
item.setPadding(item.getPaddingLeft(), padding, item.getPaddingRight(), item.getPaddingBottom());
|
||||
int padding =
|
||||
context.getResources().getDimensionPixelSize(R.dimen.contact_list_normal_padding);
|
||||
item.setPadding(
|
||||
item.getPaddingLeft(), padding, item.getPaddingRight(), item.getPaddingBottom());
|
||||
} else if (data.viewType == ITEM_ALL_MEDIA_BUTTON && dcChat != null) {
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
String c = getAllMediaCountString(dcChat.getId());
|
||||
Util.runOnMain(() -> {
|
||||
item.setValue(c);
|
||||
});
|
||||
});
|
||||
Util.runOnAnyBackgroundThread(
|
||||
() -> {
|
||||
String c = getAllMediaCountString(dcChat.getId());
|
||||
Util.runOnMain(
|
||||
() -> {
|
||||
item.setValue(c);
|
||||
});
|
||||
});
|
||||
}
|
||||
} else if (data.viewType == ITEM_HEADER) {
|
||||
TextView textView = holder.itemView.findViewById(R.id.label);
|
||||
@@ -236,10 +264,15 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
|
||||
public interface ItemClickListener {
|
||||
void onSettingsClicked(int settingsId);
|
||||
|
||||
void onStatusLongClicked(boolean isMultiUser);
|
||||
|
||||
void onSharedChatClicked(int chatId);
|
||||
|
||||
void onMemberClicked(int contactId);
|
||||
|
||||
void onMemberLongClicked(int contactId);
|
||||
|
||||
void onAvatarClicked();
|
||||
}
|
||||
|
||||
@@ -269,7 +302,11 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void changeData(@Nullable int[] memberList, @Nullable DcContact dcContact, @Nullable DcChatlist sharedChats, @Nullable DcChat dcChat) {
|
||||
public void changeData(
|
||||
@Nullable int[] memberList,
|
||||
@Nullable DcContact dcContact,
|
||||
@Nullable DcChatlist sharedChats,
|
||||
@Nullable DcChat dcChat) {
|
||||
this.dcChat = dcChat;
|
||||
this.dcContact = dcContact;
|
||||
itemData.clear();
|
||||
@@ -304,10 +341,18 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
itemData.add(new ItemData(ITEM_DIVIDER, null, 0));
|
||||
}
|
||||
|
||||
itemData.add(new ItemData(ITEM_ALL_MEDIA_BUTTON, context.getString(R.string.apps_and_media), R.drawable.ic_apps_24));
|
||||
itemData.add(
|
||||
new ItemData(
|
||||
ITEM_ALL_MEDIA_BUTTON,
|
||||
context.getString(R.string.apps_and_media),
|
||||
R.drawable.ic_apps_24));
|
||||
|
||||
if (dcContact != null && !isDeviceTalk && !isSelfTalk) {
|
||||
itemData.add(new ItemData(ITEM_SEND_MESSAGE_BUTTON, context.getString(R.string.send_message), R.drawable.ic_send_sms_white_24dp));
|
||||
itemData.add(
|
||||
new ItemData(
|
||||
ITEM_SEND_MESSAGE_BUTTON,
|
||||
context.getString(R.string.send_message),
|
||||
R.drawable.ic_send_sms_white_24dp));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -316,19 +361,25 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
String lastSeenTxt;
|
||||
if (lastSeenTimestamp == 0) {
|
||||
lastSeenTxt = context.getString(R.string.last_seen_unknown);
|
||||
}
|
||||
else {
|
||||
lastSeenTxt = context.getString(R.string.last_seen_at, DateUtils.getExtendedTimeSpanString(context, lastSeenTimestamp));
|
||||
} else {
|
||||
lastSeenTxt =
|
||||
context.getString(
|
||||
R.string.last_seen_at,
|
||||
DateUtils.getExtendedTimeSpanString(context, lastSeenTimestamp));
|
||||
}
|
||||
itemData.add(new ItemData(ITEM_LAST_SEEN, lastSeenTxt, 0));
|
||||
}
|
||||
*/
|
||||
|
||||
if (dcContact != null && !isDeviceTalk && !isSelfTalk && dcContact.isBlocked()) {
|
||||
itemData.add(new ItemData(ITEM_BLOCKED, context.getString(R.string.contact_blocked), R.drawable.contact_blocked_24));
|
||||
itemData.add(
|
||||
new ItemData(
|
||||
ITEM_BLOCKED,
|
||||
context.getString(R.string.contact_blocked),
|
||||
R.drawable.contact_blocked_24));
|
||||
}
|
||||
|
||||
if (memberList!=null && !isInBroadcast && !isMailingList) {
|
||||
if (memberList != null && !isInBroadcast && !isMailingList) {
|
||||
itemData.add(new ItemData(ITEM_DIVIDER, null, 0));
|
||||
if (dcChat != null) {
|
||||
if (dcChat.canSend() && dcChat.isEncrypted()) {
|
||||
@@ -358,9 +409,15 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
if (verifierId == DcContact.DC_CONTACT_ID_SELF) {
|
||||
introducedBy = context.getString(R.string.verified_by_you);
|
||||
} else {
|
||||
introducedBy = context.getString(R.string.verified_by, dcContext.getContact(verifierId).getDisplayName());
|
||||
introducedBy =
|
||||
context.getString(
|
||||
R.string.verified_by, dcContext.getContact(verifierId).getDisplayName());
|
||||
}
|
||||
itemData.add(new ItemData(ITEM_INTRODUCED_BY, introducedBy, dcContact.isVerified()? R.drawable.ic_verified : 0));
|
||||
itemData.add(
|
||||
new ItemData(
|
||||
ITEM_INTRODUCED_BY,
|
||||
introducedBy,
|
||||
dcContact.isVerified() ? R.drawable.ic_verified : 0));
|
||||
} else if (dcContact.isVerified()) {
|
||||
String introducedBy = context.getString(R.string.verified_by_unknown);
|
||||
itemData.add(new ItemData(ITEM_INTRODUCED_BY, introducedBy, R.drawable.ic_verified));
|
||||
@@ -371,8 +428,11 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
}
|
||||
|
||||
public int ALL_MEDIA_COUNT_MAX = 500;
|
||||
|
||||
public int getAllMediaCount(int chatId) {
|
||||
int c = dcContext.getChatMedia(chatId, DcMsg.DC_MSG_IMAGE, DcMsg.DC_MSG_GIF, DcMsg.DC_MSG_VIDEO).length;
|
||||
int c =
|
||||
dcContext.getChatMedia(chatId, DcMsg.DC_MSG_IMAGE, DcMsg.DC_MSG_GIF, DcMsg.DC_MSG_VIDEO)
|
||||
.length;
|
||||
if (c < ALL_MEDIA_COUNT_MAX) {
|
||||
c += dcContext.getChatMedia(chatId, DcMsg.DC_MSG_AUDIO, DcMsg.DC_MSG_VOICE, 0).length;
|
||||
}
|
||||
|
||||
@@ -5,13 +5,10 @@ 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 com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContact;
|
||||
|
||||
import org.thoughtcrime.securesms.components.AvatarView;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -22,12 +19,12 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class ProfileAvatarItem extends LinearLayout implements RecipientModifiedListener {
|
||||
|
||||
private AvatarView avatarView;
|
||||
private TextView nameView;
|
||||
private TextView subtitleView;
|
||||
private AvatarView avatarView;
|
||||
private TextView nameView;
|
||||
private TextView subtitleView;
|
||||
|
||||
private Recipient recipient;
|
||||
private GlideRequests glideRequests;
|
||||
private Recipient recipient;
|
||||
private GlideRequests glideRequests;
|
||||
|
||||
public ProfileAvatarItem(Context context) {
|
||||
super(context);
|
||||
@@ -40,14 +37,18 @@ public class ProfileAvatarItem extends LinearLayout implements RecipientModified
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
avatarView = findViewById(R.id.avatar);
|
||||
nameView = findViewById(R.id.name);
|
||||
subtitleView = findViewById(R.id.subtitle);
|
||||
avatarView = findViewById(R.id.avatar);
|
||||
nameView = findViewById(R.id.name);
|
||||
subtitleView = findViewById(R.id.subtitle);
|
||||
|
||||
ViewUtil.setTextViewGravityStart(nameView, getContext());
|
||||
}
|
||||
|
||||
public void set(@NonNull GlideRequests glideRequests, @Nullable DcChat dcChat, @Nullable DcContact dcContact, @Nullable int[] members) {
|
||||
public void set(
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@Nullable DcChat dcChat,
|
||||
@Nullable DcContact dcContact,
|
||||
@Nullable int[] members) {
|
||||
this.glideRequests = glideRequests;
|
||||
int memberCount = members != null ? members.length : 0;
|
||||
|
||||
@@ -60,17 +61,27 @@ public class ProfileAvatarItem extends LinearLayout implements RecipientModified
|
||||
if (dcChat.isMailingList()) {
|
||||
subtitle = dcChat.getMailinglistAddr();
|
||||
} else if (dcChat.isOutBroadcast()) {
|
||||
subtitle = getContext().getResources().getQuantityString(R.plurals.n_recipients, memberCount, memberCount);
|
||||
subtitle =
|
||||
getContext()
|
||||
.getResources()
|
||||
.getQuantityString(R.plurals.n_recipients, memberCount, memberCount);
|
||||
} else if (dcChat.getType() == DcChat.DC_CHAT_TYPE_GROUP) {
|
||||
if (memberCount > 1 || Util.contains(members, DcContact.DC_CONTACT_ID_SELF)) {
|
||||
subtitle = getContext().getResources().getQuantityString(R.plurals.n_members, memberCount, memberCount);
|
||||
subtitle =
|
||||
getContext()
|
||||
.getResources()
|
||||
.getQuantityString(R.plurals.n_members, memberCount, memberCount);
|
||||
}
|
||||
} else if (dcContact != null && !dcChat.isSelfTalk() && !dcChat.isDeviceTalk()) {
|
||||
long timestamp = dcContact.getLastSeen();
|
||||
if (timestamp == 0) {
|
||||
subtitle = getContext().getString(R.string.last_seen_unknown);
|
||||
} else {
|
||||
subtitle = getContext().getString(R.string.last_seen_at, DateUtils.getExtendedTimeSpanString(getContext(), timestamp));
|
||||
subtitle =
|
||||
getContext()
|
||||
.getString(
|
||||
R.string.last_seen_at,
|
||||
DateUtils.getExtendedTimeSpanString(getContext(), timestamp));
|
||||
}
|
||||
}
|
||||
} else if (dcContact != null) {
|
||||
@@ -81,7 +92,11 @@ public class ProfileAvatarItem extends LinearLayout implements RecipientModified
|
||||
if (timestamp == 0) {
|
||||
subtitle = getContext().getString(R.string.last_seen_unknown);
|
||||
} else {
|
||||
subtitle = getContext().getString(R.string.last_seen_at, DateUtils.getExtendedTimeSpanString(getContext(), timestamp));
|
||||
subtitle =
|
||||
getContext()
|
||||
.getString(
|
||||
R.string.last_seen_at,
|
||||
DateUtils.getExtendedTimeSpanString(getContext(), timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,12 +130,13 @@ public class ProfileAvatarItem extends LinearLayout implements RecipientModified
|
||||
@Override
|
||||
public void onModified(final Recipient recipient) {
|
||||
if (this.recipient == recipient) {
|
||||
Util.runOnMain(() -> {
|
||||
avatarView.setAvatar(glideRequests, recipient, false);
|
||||
DcContact contact = recipient.getDcContact();
|
||||
avatarView.setSeenRecently(contact != null && contact.wasSeenRecently());
|
||||
nameView.setText(recipient.toShortString());
|
||||
});
|
||||
Util.runOnMain(
|
||||
() -> {
|
||||
avatarView.setAvatar(glideRequests, recipient, false);
|
||||
DcContact contact = recipient.getDcContact();
|
||||
avatarView.setSeenRecently(contact != null && contact.wasSeenRecently());
|
||||
nameView.setText(recipient.toShortString());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,15 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
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;
|
||||
@@ -18,13 +20,14 @@ import androidx.appcompat.view.ActionMode;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcChatlist;
|
||||
import com.b44t.messenger.DcContact;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
@@ -32,26 +35,21 @@ import org.thoughtcrime.securesms.qr.QrShowActivity;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class ProfileFragment extends Fragment
|
||||
implements ProfileAdapter.ItemClickListener, DcEventCenter.DcEventDelegate {
|
||||
implements ProfileAdapter.ItemClickListener, DcEventCenter.DcEventDelegate {
|
||||
|
||||
private static final String TAG = ProfileFragment.class.getSimpleName();
|
||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||
public static final String CONTACT_ID_EXTRA = "contact_id";
|
||||
|
||||
private static final int REQUEST_CODE_PICK_CONTACT = 2;
|
||||
|
||||
private ActivityResultLauncher<Intent> pickContactLauncher;
|
||||
private ProfileAdapter adapter;
|
||||
private ActionMode actionMode;
|
||||
private ActionMode actionMode;
|
||||
private final ActionModeCallback actionModeCallback = new ActionModeCallback();
|
||||
|
||||
|
||||
private DcContext dcContext;
|
||||
protected int chatId;
|
||||
private int contactId;
|
||||
private DcContext dcContext;
|
||||
protected int chatId;
|
||||
private int contactId;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
@@ -60,10 +58,54 @@ public class ProfileFragment extends Fragment
|
||||
chatId = getArguments() != null ? getArguments().getInt(CHAT_ID_EXTRA, -1) : -1;
|
||||
contactId = getArguments().getInt(CONTACT_ID_EXTRA, -1);
|
||||
dcContext = DcHelper.getContext(requireContext());
|
||||
pickContactLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
Intent data = result.getData();
|
||||
Log.i(
|
||||
TAG,
|
||||
"Received result from activity, resultCode="
|
||||
+ result.getResultCode()
|
||||
+ ", data="
|
||||
+ data);
|
||||
if (result.getResultCode() == Activity.RESULT_OK && data != null) {
|
||||
List<Integer> selected =
|
||||
data.getIntegerArrayListExtra(ContactMultiSelectionActivity.CONTACTS_EXTRA);
|
||||
List<Integer> deselected =
|
||||
data.getIntegerArrayListExtra(
|
||||
ContactMultiSelectionActivity.DESELECTED_CONTACTS_EXTRA);
|
||||
Util.runOnAnyBackgroundThread(
|
||||
() -> {
|
||||
if (deselected != null) { // Remove members that were deselected
|
||||
Log.i(TAG, deselected.size() + " members removed");
|
||||
int[] members = dcContext.getChatContacts(chatId);
|
||||
for (int contactId : deselected) {
|
||||
for (int memberId : members) {
|
||||
if (memberId == contactId) {
|
||||
dcContext.removeContactFromChat(chatId, memberId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selected != null) { // Add new members
|
||||
Log.i(TAG, selected.size() + " members added");
|
||||
for (Integer contactId : selected) {
|
||||
if (contactId != null) {
|
||||
dcContext.addContactToChat(chatId, contactId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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_fragment, container, false);
|
||||
adapter = new ProfileAdapter(this, GlideApp.with(this), this);
|
||||
|
||||
@@ -73,7 +115,8 @@ public class ProfileFragment extends Fragment
|
||||
ViewUtil.applyWindowInsets(list);
|
||||
|
||||
list.setAdapter(adapter);
|
||||
list.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
|
||||
list.setLayoutManager(
|
||||
new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
|
||||
|
||||
update();
|
||||
|
||||
@@ -96,33 +139,34 @@ public class ProfileFragment extends Fragment
|
||||
update();
|
||||
}
|
||||
|
||||
private void update()
|
||||
{
|
||||
int[] memberList = null;
|
||||
private void update() {
|
||||
int[] memberList = null;
|
||||
DcChatlist sharedChats = null;
|
||||
|
||||
DcChat dcChat = null;
|
||||
DcContact dcContact = null;
|
||||
if (contactId>0) { dcContact = dcContext.getContact(contactId); }
|
||||
if (chatId>0) { dcChat = dcContext.getChat(chatId); }
|
||||
|
||||
if(dcChat!=null && dcChat.isMultiUser()) {
|
||||
memberList = dcContext.getChatContacts(chatId);
|
||||
if (contactId > 0) {
|
||||
dcContact = dcContext.getContact(contactId);
|
||||
}
|
||||
else if(contactId>0 && contactId!=DcContact.DC_CONTACT_ID_SELF) {
|
||||
if (chatId > 0) {
|
||||
dcChat = dcContext.getChat(chatId);
|
||||
}
|
||||
|
||||
if (dcChat != null && dcChat.isMultiUser()) {
|
||||
memberList = dcContext.getChatContacts(chatId);
|
||||
} else if (contactId > 0 && contactId != DcContact.DC_CONTACT_ID_SELF) {
|
||||
sharedChats = dcContext.getChatlist(0, null, contactId);
|
||||
}
|
||||
|
||||
adapter.changeData(memberList, dcContact, sharedChats, dcChat);
|
||||
}
|
||||
|
||||
|
||||
// handle events
|
||||
// =========================================================================
|
||||
|
||||
@Override
|
||||
public void onSettingsClicked(int settingsId) {
|
||||
switch(settingsId) {
|
||||
switch (settingsId) {
|
||||
case ProfileAdapter.ITEM_ALL_MEDIA_BUTTON:
|
||||
if (chatId > 0) {
|
||||
Intent intent = new Intent(getActivity(), AllMediaActivity.class);
|
||||
@@ -141,28 +185,31 @@ public class ProfileFragment extends Fragment
|
||||
|
||||
@Override
|
||||
public void onStatusLongClicked(boolean isMultiUser) {
|
||||
Context context = requireContext();
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(isMultiUser? R.string.chat_description : R.string.pref_default_status_label)
|
||||
.setItems(new CharSequence[]{
|
||||
context.getString(R.string.menu_copy_to_clipboard)
|
||||
},
|
||||
(dialogInterface, i) -> {
|
||||
Util.writeTextToClipboard(context, adapter.getStatusText());
|
||||
Toast.makeText(context, context.getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show();
|
||||
})
|
||||
Context context = requireContext();
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(isMultiUser ? R.string.chat_description : R.string.pref_default_status_label)
|
||||
.setItems(
|
||||
new CharSequence[] {context.getString(R.string.menu_copy_to_clipboard)},
|
||||
(dialogInterface, i) -> {
|
||||
Util.writeTextToClipboard(context, adapter.getStatusText());
|
||||
Toast.makeText(
|
||||
context, context.getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMemberLongClicked(int contactId) {
|
||||
if (contactId>DcContact.DC_CONTACT_ID_LAST_SPECIAL || contactId==DcContact.DC_CONTACT_ID_SELF) {
|
||||
if (actionMode==null) {
|
||||
if (contactId > DcContact.DC_CONTACT_ID_LAST_SPECIAL
|
||||
|| contactId == DcContact.DC_CONTACT_ID_SELF) {
|
||||
if (actionMode == null) {
|
||||
DcChat dcChat = dcContext.getChat(chatId);
|
||||
if (dcChat.canSend() && dcChat.isEncrypted()) {
|
||||
adapter.toggleMemberSelection(contactId);
|
||||
actionMode = ((AppCompatActivity) requireActivity()).startSupportActionMode(actionModeCallback);
|
||||
actionMode =
|
||||
((AppCompatActivity) requireActivity()).startSupportActionMode(actionModeCallback);
|
||||
}
|
||||
} else {
|
||||
onMemberClicked(contactId);
|
||||
@@ -172,8 +219,9 @@ public class ProfileFragment extends Fragment
|
||||
|
||||
@Override
|
||||
public void onMemberClicked(int contactId) {
|
||||
if (actionMode!=null) {
|
||||
if (contactId>DcContact.DC_CONTACT_ID_LAST_SPECIAL || contactId==DcContact.DC_CONTACT_ID_SELF) {
|
||||
if (actionMode != null) {
|
||||
if (contactId > DcContact.DC_CONTACT_ID_LAST_SPECIAL
|
||||
|| contactId == DcContact.DC_CONTACT_ID_SELF) {
|
||||
adapter.toggleMemberSelection(contactId);
|
||||
if (adapter.getSelectedMembersCount() == 0) {
|
||||
actionMode.finish();
|
||||
@@ -182,14 +230,11 @@ public class ProfileFragment extends Fragment
|
||||
actionMode.setTitle(String.valueOf(adapter.getSelectedMembersCount()));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(contactId==DcContact.DC_CONTACT_ID_ADD_MEMBER) {
|
||||
} else if (contactId == DcContact.DC_CONTACT_ID_ADD_MEMBER) {
|
||||
onAddMember();
|
||||
}
|
||||
else if(contactId==DcContact.DC_CONTACT_ID_QR_INVITE) {
|
||||
} else if (contactId == DcContact.DC_CONTACT_ID_QR_INVITE) {
|
||||
onQrInvite();
|
||||
}
|
||||
else if(contactId>DcContact.DC_CONTACT_ID_LAST_SPECIAL) {
|
||||
} else if (contactId > DcContact.DC_CONTACT_ID_LAST_SPECIAL) {
|
||||
Intent intent = new Intent(getContext(), ProfileActivity.class);
|
||||
intent.putExtra(ProfileActivity.CONTACT_ID_EXTRA, contactId);
|
||||
startActivity(intent);
|
||||
@@ -198,7 +243,7 @@ public class ProfileFragment extends Fragment
|
||||
|
||||
@Override
|
||||
public void onAvatarClicked() {
|
||||
ProfileActivity activity = (ProfileActivity)getActivity();
|
||||
ProfileActivity activity = (ProfileActivity) getActivity();
|
||||
activity.onEnlargeAvatar();
|
||||
}
|
||||
|
||||
@@ -210,7 +255,7 @@ public class ProfileFragment extends Fragment
|
||||
preselectedContacts.add(memberId);
|
||||
}
|
||||
intent.putExtra(ContactSelectionListFragment.PRESELECTED_CONTACTS, preselectedContacts);
|
||||
startActivityForResult(intent, REQUEST_CODE_PICK_CONTACT);
|
||||
pickContactLauncher.launch(intent);
|
||||
}
|
||||
|
||||
public void onQrInvite() {
|
||||
@@ -282,16 +327,24 @@ public class ProfileFragment extends Fragment
|
||||
readableToDelList.append(dcContext.getContact(toDelId).getDisplayName());
|
||||
}
|
||||
DcChat dcChat = dcContext.getChat(chatId);
|
||||
AlertDialog dialog = new AlertDialog.Builder(requireContext())
|
||||
.setPositiveButton(R.string.remove_desktop, (d, which) -> {
|
||||
for (Integer toDelId : toDelIds) {
|
||||
dcContext.removeContactFromChat(chatId, toDelId);
|
||||
}
|
||||
mode.finish();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setMessage(getString(dcChat.isOutBroadcast() ? R.string.ask_remove_from_channel : R.string.ask_remove_members, readableToDelList))
|
||||
.show();
|
||||
AlertDialog dialog =
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setPositiveButton(
|
||||
R.string.remove_desktop,
|
||||
(d, which) -> {
|
||||
for (Integer toDelId : toDelIds) {
|
||||
dcContext.removeContactFromChat(chatId, toDelId);
|
||||
}
|
||||
mode.finish();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setMessage(
|
||||
getString(
|
||||
dcChat.isOutBroadcast()
|
||||
? R.string.ask_remove_from_channel
|
||||
: R.string.ask_remove_members,
|
||||
readableToDelList))
|
||||
.show();
|
||||
Util.redPositiveButton(dialog);
|
||||
return true;
|
||||
}
|
||||
@@ -304,36 +357,4 @@ public class ProfileFragment extends Fragment
|
||||
adapter.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode==REQUEST_CODE_PICK_CONTACT && resultCode==Activity.RESULT_OK && data!=null) {
|
||||
List<Integer> selected = data.getIntegerArrayListExtra(ContactMultiSelectionActivity.CONTACTS_EXTRA);
|
||||
List<Integer> deselected = data.getIntegerArrayListExtra(ContactMultiSelectionActivity.DESELECTED_CONTACTS_EXTRA);
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
if (deselected != null) {
|
||||
// Remove members that were deselected
|
||||
int[] members = dcContext.getChatContacts(chatId);
|
||||
for (int contactId : deselected) {
|
||||
for (int memberId : members) {
|
||||
if (memberId == contactId) {
|
||||
dcContext.removeContactFromChat(chatId, memberId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selected != null) {
|
||||
// Add new members
|
||||
for (Integer contactId : selected) {
|
||||
if (contactId != null) {
|
||||
dcContext.addContactToChat(chatId, contactId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,16 +5,14 @@ import android.text.SpannableString;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Linkifier;
|
||||
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||
|
||||
public class ProfileStatusItem extends LinearLayout {
|
||||
|
||||
private AppCompatTextView statusTextView;
|
||||
private final PassthroughClickListener passthroughClickListener = new PassthroughClickListener();
|
||||
private final PassthroughClickListener passthroughClickListener = new PassthroughClickListener();
|
||||
|
||||
public ProfileStatusItem(Context context) {
|
||||
super(context);
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.util.ResUtil;
|
||||
|
||||
public class ProfileTextItem extends LinearLayout {
|
||||
@@ -40,7 +37,10 @@ public class ProfileTextItem extends LinearLayout {
|
||||
if (icon != 0) {
|
||||
Drawable orgDrawable = ContextCompat.getDrawable(getContext(), icon);
|
||||
if (orgDrawable != null) {
|
||||
Drawable drawable = orgDrawable.mutate(); // avoid global state modification and showing eg. app-icon tinted also elsewhere
|
||||
Drawable drawable =
|
||||
orgDrawable
|
||||
.mutate(); // avoid global state modification and showing eg. app-icon tinted also
|
||||
// elsewhere
|
||||
drawable = DrawableCompat.wrap(drawable);
|
||||
if (tint) {
|
||||
int color = ResUtil.getColor(getContext(), R.attr.colorAccent);
|
||||
|
||||
@@ -8,112 +8,117 @@ import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
|
||||
import de.cketti.safecontentresolver.SafeContentResolver;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashSet;
|
||||
|
||||
import de.cketti.safecontentresolver.SafeContentResolver;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
|
||||
public class ResolveMediaTask extends AsyncTask<Uri, Void, Uri> {
|
||||
|
||||
private static final String TAG = ResolveMediaTask.class.getSimpleName();
|
||||
private static final String TAG = ResolveMediaTask.class.getSimpleName();
|
||||
|
||||
interface OnMediaResolvedListener {
|
||||
void onMediaResolved(Uri uri);
|
||||
interface OnMediaResolvedListener {
|
||||
void onMediaResolved(Uri uri);
|
||||
}
|
||||
|
||||
private final WeakReference<Activity> contextRef;
|
||||
private final WeakReference<OnMediaResolvedListener> listenerWeakReference;
|
||||
|
||||
private static final HashSet<ResolveMediaTask> instances = new HashSet<>();
|
||||
|
||||
ResolveMediaTask(Activity activityContext, ResolveMediaTask.OnMediaResolvedListener listener) {
|
||||
this.contextRef = new WeakReference<>(activityContext);
|
||||
this.listenerWeakReference = new WeakReference<>(listener);
|
||||
instances.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Uri doInBackground(Uri... uris) {
|
||||
try {
|
||||
Uri uri = uris[0];
|
||||
if (uris.length != 1 || uri == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
InputStream inputStream;
|
||||
String fileName = null;
|
||||
Long fileSize = null;
|
||||
|
||||
SafeContentResolver safeContentResolver = SafeContentResolver.newInstance(contextRef.get());
|
||||
inputStream = safeContentResolver.openInputStream(uri);
|
||||
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Cursor cursor =
|
||||
contextRef
|
||||
.get()
|
||||
.getContentResolver()
|
||||
.query(
|
||||
uri,
|
||||
new String[] {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE},
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
try {
|
||||
fileName = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
|
||||
fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE));
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
|
||||
private final WeakReference<Activity> contextRef;
|
||||
private final WeakReference<OnMediaResolvedListener> listenerWeakReference;
|
||||
if (fileName == null) {
|
||||
fileName = uri.getLastPathSegment();
|
||||
}
|
||||
|
||||
private static final HashSet<ResolveMediaTask> instances = new HashSet<>();
|
||||
|
||||
ResolveMediaTask(Activity activityContext, ResolveMediaTask.OnMediaResolvedListener listener) {
|
||||
this.contextRef = new WeakReference<>(activityContext);
|
||||
this.listenerWeakReference = new WeakReference<>(listener);
|
||||
instances.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Uri doInBackground(Uri... uris) {
|
||||
try {
|
||||
Uri uri = uris[0];
|
||||
if (uris.length != 1 || uri == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
InputStream inputStream;
|
||||
String fileName = null;
|
||||
Long fileSize = null;
|
||||
|
||||
SafeContentResolver safeContentResolver = SafeContentResolver.newInstance(contextRef.get());
|
||||
inputStream = safeContentResolver.openInputStream(uri);
|
||||
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Cursor cursor = contextRef.get().getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}, null, null, null);
|
||||
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
try {
|
||||
fileName = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
|
||||
fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE));
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
|
||||
if (fileName == null) {
|
||||
fileName = uri.getLastPathSegment();
|
||||
}
|
||||
|
||||
String mimeType = getMimeType(contextRef.get(), uri);
|
||||
return PersistentBlobProvider.getInstance().create(contextRef.get(), inputStream, mimeType, fileName, fileSize);
|
||||
} catch (NullPointerException | FileNotFoundException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Uri uri) {
|
||||
instances.remove(this);
|
||||
if (!this.isCancelled()) {
|
||||
listenerWeakReference.get().onMediaResolved(uri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
instances.remove(this);
|
||||
super.onCancelled();
|
||||
listenerWeakReference.get().onMediaResolved(null);
|
||||
|
||||
}
|
||||
|
||||
public static boolean isExecuting() {
|
||||
return !instances.isEmpty();
|
||||
}
|
||||
|
||||
public static void cancelTasks() {
|
||||
for (ResolveMediaTask task : instances) {
|
||||
task.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasFileScheme(Uri uri) {
|
||||
if (uri == null) {
|
||||
return false;
|
||||
}
|
||||
return "file".equals(uri.getScheme());
|
||||
String mimeType = getMimeType(contextRef.get(), uri);
|
||||
return PersistentBlobProvider.getInstance()
|
||||
.create(contextRef.get(), inputStream, mimeType, fileName, fileSize);
|
||||
} catch (NullPointerException | FileNotFoundException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Uri uri) {
|
||||
instances.remove(this);
|
||||
if (!this.isCancelled()) {
|
||||
listenerWeakReference.get().onMediaResolved(uri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
instances.remove(this);
|
||||
super.onCancelled();
|
||||
listenerWeakReference.get().onMediaResolved(null);
|
||||
}
|
||||
|
||||
public static boolean isExecuting() {
|
||||
return !instances.isEmpty();
|
||||
}
|
||||
|
||||
public static void cancelTasks() {
|
||||
for (ResolveMediaTask task : instances) {
|
||||
task.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasFileScheme(Uri uri) {
|
||||
if (uri == null) {
|
||||
return false;
|
||||
}
|
||||
return "file".equals(uri.getScheme());
|
||||
}
|
||||
}
|
||||
|
||||
+2
-6
@@ -2,15 +2,11 @@ package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
/**
|
||||
* Like LinearLayoutManager but you can set a starting position
|
||||
*/
|
||||
/** Like LinearLayoutManager but you can set a starting position */
|
||||
class SetStartingPositionLinearLayoutManager extends LinearLayoutManager {
|
||||
|
||||
private int pendingStartingPos = -1;
|
||||
@@ -46,4 +42,4 @@ class SetStartingPositionLinearLayoutManager extends LinearLayoutManager {
|
||||
public void setStartingPosition(int position) {
|
||||
pendingStartingPos = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,15 +24,14 @@ import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||
|
||||
import com.b44t.messenger.DcContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
@@ -41,24 +40,21 @@ import org.thoughtcrime.securesms.util.MailtoUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.ShareUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An activity to quickly share content with chats
|
||||
*
|
||||
* @author Jake McGinty
|
||||
*/
|
||||
public class ShareActivity extends PassphraseRequiredActionBarActivity implements ResolveMediaTask.OnMediaResolvedListener
|
||||
{
|
||||
public class ShareActivity extends PassphraseRequiredActionBarActivity
|
||||
implements ResolveMediaTask.OnMediaResolvedListener {
|
||||
private static final String TAG = ShareActivity.class.getSimpleName();
|
||||
|
||||
public static final String EXTRA_ACC_ID = "acc_id";
|
||||
public static final String EXTRA_CHAT_ID = "chat_id";
|
||||
|
||||
private ArrayList<Uri> resolvedExtras;
|
||||
private DcContext dcContext;
|
||||
private boolean isResolvingUrisOnMainThread;
|
||||
private ArrayList<Uri> resolvedExtras;
|
||||
private DcContext dcContext;
|
||||
private boolean isResolvingUrisOnMainThread;
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
@@ -115,10 +111,10 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity implement
|
||||
}
|
||||
}
|
||||
|
||||
if (Intent.ACTION_SEND.equals(getIntent().getAction()) &&
|
||||
getIntent().getParcelableExtra(Intent.EXTRA_STREAM) != null) {
|
||||
Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
streamExtras.add(uri);
|
||||
if (Intent.ACTION_SEND.equals(getIntent().getAction())
|
||||
&& getIntent().getParcelableExtra(Intent.EXTRA_STREAM) != null) {
|
||||
Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
streamExtras.add(uri);
|
||||
} else if (getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM) != null) {
|
||||
streamExtras = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
||||
} else if (getIntent().getData() != null) {
|
||||
@@ -137,7 +133,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity implement
|
||||
}
|
||||
|
||||
private boolean needsFilePermission(List<Uri> uris) {
|
||||
for(Uri uri : uris) {
|
||||
for (Uri uri : uris) {
|
||||
// uri may be null, however, hasFileScheme() just returns false in this case
|
||||
if (hasFileScheme(uri)) {
|
||||
return true;
|
||||
@@ -148,17 +144,19 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity implement
|
||||
|
||||
private void requestPermissionForFiles(List<Uri> streamExtras) {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.alwaysGrantOnSdk33()
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(getString(R.string.perm_explain_access_to_storage_denied))
|
||||
.onAllGranted(() -> resolveUris(streamExtras))
|
||||
.onAnyDenied(this::abortShare)
|
||||
.execute();
|
||||
.request(
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.alwaysGrantOnSdk33()
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(getString(R.string.perm_explain_access_to_storage_denied))
|
||||
.onAllGranted(() -> resolveUris(streamExtras))
|
||||
.onAnyDenied(this::abortShare)
|
||||
.execute();
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@@ -187,7 +185,9 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity implement
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
super.onOptionsItemSelected(item);
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home: finish(); return true;
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -199,15 +199,16 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity implement
|
||||
}
|
||||
|
||||
if (!ResolveMediaTask.isExecuting() && !isResolvingUrisOnMainThread) {
|
||||
handleResolvedMedia(getIntent());
|
||||
handleResolvedMedia(getIntent());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleResolvedMedia(Intent intent) {
|
||||
int accId = intent.getIntExtra(EXTRA_ACC_ID, -1);
|
||||
int chatId = intent.getIntExtra(EXTRA_CHAT_ID, -1);
|
||||
int accId = intent.getIntExtra(EXTRA_ACC_ID, -1);
|
||||
int chatId = intent.getIntExtra(EXTRA_CHAT_ID, -1);
|
||||
|
||||
// the intent coming from shortcuts in the share selector might not include the custom extras but the shortcut ID
|
||||
// the intent coming from shortcuts in the share selector might not include the custom extras
|
||||
// but the shortcut ID
|
||||
String shortcutId = intent.getStringExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID);
|
||||
if ((chatId == -1 || accId == -1) && shortcutId != null && shortcutId.startsWith("chat-")) {
|
||||
String[] args = shortcutId.split("-");
|
||||
@@ -219,47 +220,47 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity implement
|
||||
|
||||
String[] extraEmail = getIntent().getStringArrayExtra(Intent.EXTRA_EMAIL);
|
||||
/*
|
||||
usually, external app will try to start "e-mail sharing" intent, providing it:
|
||||
1. address(s), packed in array, marked as Intent.EXTRA_EMAIL - mandatory
|
||||
2. shared content (files, pics, video), packed in Intent.EXTRA_STREAM - optional
|
||||
usually, external app will try to start "e-mail sharing" intent, providing it:
|
||||
1. address(s), packed in array, marked as Intent.EXTRA_EMAIL - mandatory
|
||||
2. shared content (files, pics, video), packed in Intent.EXTRA_STREAM - optional
|
||||
|
||||
here is a sample code to trigger this routine from within external app:
|
||||
here is a sample code to trigger this routine from within external app:
|
||||
|
||||
try {
|
||||
Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(
|
||||
"mailto", "someone@example.com", null));
|
||||
File f = new File(getFilesDir() + "/somebinaryfile.bin");
|
||||
f.createNewFile();
|
||||
f.setReadable(true, false);
|
||||
byte[] b = new byte[1024];
|
||||
new Random().nextBytes(b);
|
||||
FileOutputStream fOut = new FileOutputStream(f);
|
||||
DataOutputStream dataStream = new DataOutputStream(fOut);
|
||||
dataStream.write(b);
|
||||
dataStream.close();
|
||||
try {
|
||||
Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(
|
||||
"mailto", "someone@example.com", null));
|
||||
File f = new File(getFilesDir() + "/somebinaryfile.bin");
|
||||
f.createNewFile();
|
||||
f.setReadable(true, false);
|
||||
byte[] b = new byte[1024];
|
||||
new Random().nextBytes(b);
|
||||
FileOutputStream fOut = new FileOutputStream(f);
|
||||
DataOutputStream dataStream = new DataOutputStream(fOut);
|
||||
dataStream.write(b);
|
||||
dataStream.close();
|
||||
|
||||
Uri sharedURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", f);
|
||||
emailIntent.setAction(Intent.ACTION_SEND);
|
||||
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"someone@example.com"});
|
||||
emailIntent.setType("text/plain");
|
||||
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
emailIntent.putExtra(Intent.EXTRA_STREAM, sharedURI);
|
||||
Uri sharedURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", f);
|
||||
emailIntent.setAction(Intent.ACTION_SEND);
|
||||
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"someone@example.com"});
|
||||
emailIntent.setType("text/plain");
|
||||
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
emailIntent.putExtra(Intent.EXTRA_STREAM, sharedURI);
|
||||
|
||||
// to EXPLICITLY fire DC's sharing activity:
|
||||
// emailIntent.setComponent(new ComponentName("com.b44t.messenger.beta", "org.thoughtcrime.securesms.ShareActivity"));
|
||||
// to EXPLICITLY fire DC's sharing activity:
|
||||
// emailIntent.setComponent(new ComponentName("com.b44t.messenger.beta", "org.thoughtcrime.securesms.ShareActivity"));
|
||||
|
||||
startActivity(emailIntent);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}catch (ActivityNotFoundException e) {
|
||||
}
|
||||
*/
|
||||
startActivity(emailIntent);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}catch (ActivityNotFoundException e) {
|
||||
}
|
||||
*/
|
||||
|
||||
if(chatId == -1 && extraEmail != null && extraEmail.length > 0) {
|
||||
if (chatId == -1 && extraEmail != null && extraEmail.length > 0) {
|
||||
final String addr = extraEmail[0];
|
||||
int contactId = dcContext.lookupContactIdByAddr(addr);
|
||||
|
||||
if(contactId == 0) {
|
||||
if (contactId == 0) {
|
||||
contactId = dcContext.createContact(null, addr);
|
||||
}
|
||||
|
||||
@@ -290,9 +291,9 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity implement
|
||||
ShareUtil.setSharedUris(intent, resolvedExtras);
|
||||
|
||||
String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||
if (text==null) {
|
||||
if (text == null) {
|
||||
CharSequence cs = getIntent().getCharSequenceExtra(Intent.EXTRA_TEXT);
|
||||
if (cs!=null) {
|
||||
if (cs != null) {
|
||||
text = cs.toString();
|
||||
}
|
||||
}
|
||||
@@ -320,10 +321,9 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity implement
|
||||
}
|
||||
|
||||
private boolean hasFileScheme(Uri uri) {
|
||||
if (uri==null) {
|
||||
if (uri == null) {
|
||||
return false;
|
||||
}
|
||||
return "file".equals(uri.getScheme());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,30 +1,44 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
public class ShareLocationDialog {
|
||||
|
||||
public static void show(final Context context, final @NonNull ShareLocationDurationSelectionListener listener) {
|
||||
public static void show(
|
||||
final Context context, final @NonNull ShareLocationDurationSelectionListener listener) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.title_share_location);
|
||||
builder.setItems(R.array.share_location_durations, (dialog, which) -> {
|
||||
final int shareLocationUnit;
|
||||
builder.setItems(
|
||||
R.array.share_location_durations,
|
||||
(dialog, which) -> {
|
||||
final int shareLocationUnit;
|
||||
|
||||
switch (which) {
|
||||
default:
|
||||
case 0: shareLocationUnit = 5 * 60; break;
|
||||
case 1: shareLocationUnit = 30 * 60; break;
|
||||
case 2: shareLocationUnit = 60 * 60; break;
|
||||
case 3: shareLocationUnit = 2 * 60 * 60; break;
|
||||
case 4: shareLocationUnit = 6 * 60 * 60; break;
|
||||
case 5: shareLocationUnit = 12 * 60 * 60; break;
|
||||
}
|
||||
switch (which) {
|
||||
default:
|
||||
case 0:
|
||||
shareLocationUnit = 5 * 60;
|
||||
break;
|
||||
case 1:
|
||||
shareLocationUnit = 30 * 60;
|
||||
break;
|
||||
case 2:
|
||||
shareLocationUnit = 60 * 60;
|
||||
break;
|
||||
case 3:
|
||||
shareLocationUnit = 2 * 60 * 60;
|
||||
break;
|
||||
case 4:
|
||||
shareLocationUnit = 6 * 60 * 60;
|
||||
break;
|
||||
case 5:
|
||||
shareLocationUnit = 12 * 60 * 60;
|
||||
break;
|
||||
}
|
||||
|
||||
listener.onSelected(shareLocationUnit);
|
||||
});
|
||||
listener.onSelected(shareLocationUnit);
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
|
||||
builder.show();
|
||||
|
||||
@@ -7,21 +7,19 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.text.util.Linkify;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.b44t.messenger.DcContact;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import java.util.Locale;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.util.IntentUtils;
|
||||
import org.thoughtcrime.securesms.util.Prefs;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class StatsSending {
|
||||
/** @noinspection unused: We will start adding a device message once stats-sending is tested a bit */
|
||||
/**
|
||||
* @noinspection unused: We will start adding a device message once stats-sending is tested a bit
|
||||
*/
|
||||
public static void maybeAddStatsSendingDeviceMsg(Context context) {
|
||||
if (Prefs.getStatsDeviceMsgId(context) != 0) {
|
||||
return;
|
||||
@@ -33,15 +31,15 @@ public class StatsSending {
|
||||
DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
|
||||
msg.setText(context.getString(R.string.stats_device_message));
|
||||
int msgId = dcContext.addDeviceMsg("stats_device_message", msg);
|
||||
if (msgId!=0) {
|
||||
if (msgId != 0) {
|
||||
Prefs.setStatsDeviceMsgId(context, msgId);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isStatsSendingDeviceMsg(Context context, DcMsg msg) {
|
||||
return msg != null
|
||||
&& msg.getFromId() == DcContact.DC_CONTACT_ID_DEVICE
|
||||
&& msg.getId() == Prefs.getStatsDeviceMsgId(context);
|
||||
&& msg.getFromId() == DcContact.DC_CONTACT_ID_DEVICE
|
||||
&& msg.getId() == Prefs.getStatsDeviceMsgId(context);
|
||||
}
|
||||
|
||||
public static void statsDeviceMsgTapped(Activity activity) {
|
||||
@@ -52,17 +50,22 @@ public class StatsSending {
|
||||
}
|
||||
}
|
||||
|
||||
public static void showStatsConfirmationDialog(Activity activity, Runnable onConfigChangedListener) {
|
||||
AlertDialog d = new AlertDialog.Builder(activity)
|
||||
.setMessage(R.string.stats_confirmation_dialog)
|
||||
.setNegativeButton(R.string.cancel, (_d, i) -> {})
|
||||
.setPositiveButton(R.string.yes, (_d, i) -> {
|
||||
DcHelper.set(activity, DcHelper.CONFIG_STATS_SENDING, "1");
|
||||
onConfigChangedListener.run();
|
||||
showStatsThanksDialog(activity);
|
||||
})
|
||||
.setNeutralButton(R.string.more_info_desktop, (_d, i) -> openHelp(activity, "#statssending"))
|
||||
.create();
|
||||
public static void showStatsConfirmationDialog(
|
||||
Activity activity, Runnable onConfigChangedListener) {
|
||||
AlertDialog d =
|
||||
new AlertDialog.Builder(activity)
|
||||
.setMessage(R.string.stats_confirmation_dialog)
|
||||
.setNegativeButton(R.string.cancel, (_d, i) -> {})
|
||||
.setPositiveButton(
|
||||
R.string.yes,
|
||||
(_d, i) -> {
|
||||
DcHelper.set(activity, DcHelper.CONFIG_STATS_SENDING, "1");
|
||||
onConfigChangedListener.run();
|
||||
showStatsThanksDialog(activity);
|
||||
})
|
||||
.setNeutralButton(
|
||||
R.string.more_info_desktop, (_d, i) -> openHelp(activity, "#statssending"))
|
||||
.create();
|
||||
d.show();
|
||||
try {
|
||||
//noinspection DataFlowIssue
|
||||
@@ -76,25 +79,35 @@ public class StatsSending {
|
||||
private static void showStatsThanksDialog(Activity activity) {
|
||||
String stats_id = DcHelper.get(activity, DcHelper.CONFIG_STATS_ID);
|
||||
new AlertDialog.Builder(activity)
|
||||
.setMessage(R.string.stats_thanks)
|
||||
.setNeutralButton(R.string.no, (d, i) -> {})
|
||||
.setPositiveButton(R.string.yes, (d, i) -> {
|
||||
String ln = Locale.getDefault().getLanguage();
|
||||
IntentUtils.showInBrowser(activity, "https://cispa.qualtrics.com/jfe/form/SV_9YmhkpGa48KxfLg?id=" + stats_id + "&ln=" + ln);
|
||||
})
|
||||
.show();
|
||||
.setMessage(R.string.stats_thanks)
|
||||
.setNeutralButton(R.string.no, (d, i) -> {})
|
||||
.setPositiveButton(
|
||||
R.string.yes,
|
||||
(d, i) -> {
|
||||
String ln = Locale.getDefault().getLanguage();
|
||||
IntentUtils.showInBrowser(
|
||||
activity,
|
||||
"https://cispa.qualtrics.com/jfe/form/SV_9YmhkpGa48KxfLg?id="
|
||||
+ stats_id
|
||||
+ "&ln="
|
||||
+ ln);
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
public static void showStatsDisableDialog(Activity activity) {
|
||||
AlertDialog d = new AlertDialog.Builder(activity)
|
||||
.setMessage(R.string.stats_disable_dialog)
|
||||
.setNegativeButton(R.string.disable, (_d, i) -> {
|
||||
DcHelper.set(activity, DcHelper.CONFIG_STATS_SENDING, "0");
|
||||
})
|
||||
.setPositiveButton(R.string.stats_keep_sending, (_d, i) -> {
|
||||
})
|
||||
.setNeutralButton(R.string.more_info_desktop, (_d, i) -> openHelp(activity, "#statssending"))
|
||||
.create();
|
||||
AlertDialog d =
|
||||
new AlertDialog.Builder(activity)
|
||||
.setMessage(R.string.stats_disable_dialog)
|
||||
.setNegativeButton(
|
||||
R.string.disable,
|
||||
(_d, i) -> {
|
||||
DcHelper.set(activity, DcHelper.CONFIG_STATS_SENDING, "0");
|
||||
})
|
||||
.setPositiveButton(R.string.stats_keep_sending, (_d, i) -> {})
|
||||
.setNeutralButton(
|
||||
R.string.more_info_desktop, (_d, i) -> openHelp(activity, "#statssending"))
|
||||
.create();
|
||||
d.show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,18 +9,18 @@ public class TransportOption {
|
||||
NORMAL_MAIL
|
||||
}
|
||||
|
||||
private final int drawable;
|
||||
private final @NonNull String text;
|
||||
private final @NonNull String composeHint;
|
||||
private final int drawable;
|
||||
private final @NonNull String text;
|
||||
private final @NonNull String composeHint;
|
||||
|
||||
public TransportOption(@NonNull Type type,
|
||||
@DrawableRes int drawable,
|
||||
@NonNull String text,
|
||||
@NonNull String composeHint)
|
||||
{
|
||||
this.drawable = drawable;
|
||||
this.text = text;
|
||||
this.composeHint = composeHint;
|
||||
public TransportOption(
|
||||
@NonNull Type type,
|
||||
@DrawableRes int drawable,
|
||||
@NonNull String text,
|
||||
@NonNull String composeHint) {
|
||||
this.drawable = drawable;
|
||||
this.text = text;
|
||||
this.composeHint = composeHint;
|
||||
}
|
||||
|
||||
public @NonNull Type getType() {
|
||||
|
||||
@@ -3,26 +3,23 @@ package org.thoughtcrime.securesms;
|
||||
import static org.thoughtcrime.securesms.TransportOption.Type;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.util.guava.Optional;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import org.thoughtcrime.securesms.util.guava.Optional;
|
||||
|
||||
public class TransportOptions {
|
||||
private final List<OnTransportChangedListener> listeners = new LinkedList<>();
|
||||
private final Context context;
|
||||
private final List<TransportOption> enabledTransports;
|
||||
private final Context context;
|
||||
private final List<TransportOption> enabledTransports;
|
||||
|
||||
private Type defaultTransportType = Type.NORMAL_MAIL;
|
||||
private Optional<TransportOption> selectedOption = Optional.absent();
|
||||
private Type defaultTransportType = Type.NORMAL_MAIL;
|
||||
private Optional<TransportOption> selectedOption = Optional.absent();
|
||||
|
||||
public TransportOptions(Context context) {
|
||||
this.context = context;
|
||||
this.enabledTransports = initializeAvailableTransports();
|
||||
this.context = context;
|
||||
this.enabledTransports = initializeAvailableTransports();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
@@ -48,7 +45,7 @@ public class TransportOptions {
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelectedTransport(@Nullable TransportOption transportOption) {
|
||||
public void setSelectedTransport(@Nullable TransportOption transportOption) {
|
||||
this.selectedOption = Optional.fromNullable(transportOption);
|
||||
notifyTransportChangeListeners();
|
||||
}
|
||||
@@ -76,9 +73,12 @@ public class TransportOptions {
|
||||
private List<TransportOption> initializeAvailableTransports() {
|
||||
List<TransportOption> results = new LinkedList<>();
|
||||
|
||||
results.add(new TransportOption(Type.NORMAL_MAIL, R.drawable.ic_send_sms_white_24dp,
|
||||
context.getString(R.string.menu_send),
|
||||
context.getString(R.string.chat_input_placeholder)));
|
||||
results.add(
|
||||
new TransportOption(
|
||||
Type.NORMAL_MAIL,
|
||||
R.drawable.ic_send_sms_white_24dp,
|
||||
context.getString(R.string.menu_send),
|
||||
context.getString(R.string.chat_input_placeholder)));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,9 @@ import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.List;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class TransportOptionsAdapter extends BaseAdapter {
|
||||
|
||||
@@ -20,11 +17,10 @@ public class TransportOptionsAdapter extends BaseAdapter {
|
||||
|
||||
private List<TransportOption> enabledTransports;
|
||||
|
||||
public TransportOptionsAdapter(@NonNull Context context,
|
||||
@NonNull List<TransportOption> enabledTransports)
|
||||
{
|
||||
public TransportOptionsAdapter(
|
||||
@NonNull Context context, @NonNull List<TransportOption> enabledTransports) {
|
||||
super();
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.enabledTransports = enabledTransports;
|
||||
}
|
||||
|
||||
@@ -53,10 +49,10 @@ public class TransportOptionsAdapter extends BaseAdapter {
|
||||
convertView = inflater.inflate(R.layout.transport_selection_list_item, parent, false);
|
||||
}
|
||||
|
||||
TransportOption transport = (TransportOption) getItem(position);
|
||||
ImageView imageView = ViewUtil.findById(convertView, R.id.icon);
|
||||
TextView textView = ViewUtil.findById(convertView, R.id.text);
|
||||
TextView subtextView = ViewUtil.findById(convertView, R.id.subtext);
|
||||
TransportOption transport = (TransportOption) getItem(position);
|
||||
ImageView imageView = ViewUtil.findById(convertView, R.id.icon);
|
||||
TextView textView = ViewUtil.findById(convertView, R.id.text);
|
||||
TextView subtextView = ViewUtil.findById(convertView, R.id.subtext);
|
||||
|
||||
imageView.setImageResource(transport.getDrawable());
|
||||
textView.setText(transport.getDescription());
|
||||
|
||||
@@ -4,30 +4,32 @@ import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.ListPopupWindow;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class TransportOptionsPopup extends ListPopupWindow implements ListView.OnItemClickListener {
|
||||
|
||||
private final TransportOptionsAdapter adapter;
|
||||
private final SelectedListener listener;
|
||||
private final SelectedListener listener;
|
||||
|
||||
public TransportOptionsPopup(@NonNull Context context, @NonNull View anchor, @NonNull SelectedListener listener) {
|
||||
public TransportOptionsPopup(
|
||||
@NonNull Context context, @NonNull View anchor, @NonNull SelectedListener listener) {
|
||||
super(context);
|
||||
this.listener = listener;
|
||||
this.adapter = new TransportOptionsAdapter(context, new LinkedList<TransportOption>());
|
||||
this.adapter = new TransportOptionsAdapter(context, new LinkedList<TransportOption>());
|
||||
|
||||
setVerticalOffset(context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_yoff));
|
||||
setHorizontalOffset(context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_xoff));
|
||||
setVerticalOffset(
|
||||
context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_yoff));
|
||||
setHorizontalOffset(
|
||||
context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_xoff));
|
||||
setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
|
||||
setModal(true);
|
||||
setAnchorView(anchor);
|
||||
setAdapter(adapter);
|
||||
setContentWidth(context.getResources().getDimensionPixelSize(R.dimen.transport_selection_popup_width));
|
||||
setContentWidth(
|
||||
context.getResources().getDimensionPixelSize(R.dimen.transport_selection_popup_width));
|
||||
|
||||
setOnItemClickListener(this);
|
||||
}
|
||||
@@ -40,11 +42,10 @@ public class TransportOptionsPopup extends ListPopupWindow implements ListView.O
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
listener.onSelected((TransportOption)adapter.getItem(position));
|
||||
listener.onSelected((TransportOption) adapter.getItem(position));
|
||||
}
|
||||
|
||||
public interface SelectedListener {
|
||||
void onSelected(TransportOption option);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
@@ -21,44 +20,46 @@ import androidx.webkit.ProxyConfig;
|
||||
import androidx.webkit.ProxyController;
|
||||
import androidx.webkit.WebSettingsCompat;
|
||||
import androidx.webkit.WebViewFeature;
|
||||
|
||||
import chat.delta.rpc.types.SecurejoinSource;
|
||||
import java.net.IDN;
|
||||
import org.thoughtcrime.securesms.qr.QrCodeHandler;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.IntentUtils;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import chat.delta.rpc.types.SecurejoinSource;
|
||||
|
||||
import java.net.IDN;
|
||||
|
||||
public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
||||
implements SearchView.OnQueryTextListener,
|
||||
WebView.FindListener
|
||||
{
|
||||
implements SearchView.OnQueryTextListener, WebView.FindListener {
|
||||
private static final String TAG = WebViewActivity.class.getSimpleName();
|
||||
|
||||
protected WebView webView;
|
||||
|
||||
/** Return true the window content should display fullscreen/edge-to-edge ex. in the integrated maps app */
|
||||
protected boolean immersiveMode() { return false; }
|
||||
/**
|
||||
* Return true the window content should display fullscreen/edge-to-edge ex. in the integrated
|
||||
* maps app
|
||||
*/
|
||||
protected boolean immersiveMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean shouldAskToOpenLink() { return false; }
|
||||
protected boolean shouldAskToOpenLink() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void toggleFakeProxy(boolean enable) {
|
||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
|
||||
if (enable) {
|
||||
// Set proxy to non-routable address.
|
||||
ProxyConfig proxyConfig = new ProxyConfig.Builder()
|
||||
.removeImplicitRules()
|
||||
.addProxyRule("0.0.0.0")
|
||||
.build();
|
||||
ProxyController.getInstance().setProxyOverride(proxyConfig, Runnable::run, () -> Log.i(TAG, "Set WebView proxy."));
|
||||
ProxyConfig proxyConfig =
|
||||
new ProxyConfig.Builder().removeImplicitRules().addProxyRule("0.0.0.0").build();
|
||||
ProxyController.getInstance()
|
||||
.setProxyOverride(proxyConfig, Runnable::run, () -> Log.i(TAG, "Set WebView proxy."));
|
||||
} else {
|
||||
ProxyController.getInstance().clearProxyOverride(Runnable::run, () -> Log.i(TAG, "Cleared WebView proxy."));
|
||||
ProxyController.getInstance()
|
||||
.clearProxyOverride(Runnable::run, () -> Log.i(TAG, "Cleared WebView proxy."));
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Cannot " + (enable? "set": "clear") + " WebView proxy.");
|
||||
Log.w(TAG, "Cannot " + (enable ? "set" : "clear") + " WebView proxy.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,65 +73,70 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
webView = findViewById(R.id.webview);
|
||||
|
||||
if(immersiveMode()) {
|
||||
if (immersiveMode()) {
|
||||
// set a shadow in the status bar to make it more readable
|
||||
findViewById(R.id.status_bar_background).setBackgroundResource(R.drawable.search_toolbar_shadow);
|
||||
findViewById(R.id.status_bar_background)
|
||||
.setBackgroundResource(R.drawable.search_toolbar_shadow);
|
||||
} else {
|
||||
// add padding to avoid content hidden behind system bars
|
||||
ViewUtil.applyWindowInsets(findViewById(R.id.content_container), true, true, true, true, true, false);
|
||||
ViewUtil.applyWindowInsets(
|
||||
findViewById(R.id.content_container), true, true, true, true, true, false);
|
||||
}
|
||||
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
// IMPORTANT: this is will likely not be called inside iframes unless target=_blank is used in the anchor/link tag.
|
||||
// `shouldOverrideUrlLoading()` is called when the user clicks a URL,
|
||||
// returning `true` causes the WebView to abort loading the URL,
|
||||
// returning `false` causes the WebView to continue loading the URL as usual.
|
||||
// the method is not called for POST request nor for on-page-links.
|
||||
//
|
||||
// nb: from API 24, `shouldOverrideUrlLoading(String)` is deprecated and
|
||||
// `shouldOverrideUrlLoading(WebResourceRequest)` shall be used.
|
||||
// the new one has the same functionality, and the old one still exist,
|
||||
// so, to support all systems, for now, using the old one seems to be the simplest way.
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (url != null) {
|
||||
String schema = url.split(":")[0].toLowerCase();
|
||||
switch (schema) {
|
||||
case "http":
|
||||
case "https":
|
||||
case "gemini":
|
||||
case "tel":
|
||||
case "sms":
|
||||
case "mailto":
|
||||
case "openpgp4fpr":
|
||||
case "geo":
|
||||
case "dcaccount":
|
||||
case "dclogin":
|
||||
return openOnlineUrl(url);
|
||||
webView.setWebViewClient(
|
||||
new WebViewClient() {
|
||||
// IMPORTANT: this is will likely not be called inside iframes unless target=_blank is
|
||||
// used in the anchor/link tag.
|
||||
// `shouldOverrideUrlLoading()` is called when the user clicks a URL,
|
||||
// returning `true` causes the WebView to abort loading the URL,
|
||||
// returning `false` causes the WebView to continue loading the URL as usual.
|
||||
// the method is not called for POST request nor for on-page-links.
|
||||
//
|
||||
// nb: from API 24, `shouldOverrideUrlLoading(String)` is deprecated and
|
||||
// `shouldOverrideUrlLoading(WebResourceRequest)` shall be used.
|
||||
// the new one has the same functionality, and the old one still exist,
|
||||
// so, to support all systems, for now, using the old one seems to be the simplest way.
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (url != null) {
|
||||
String schema = url.split(":")[0].toLowerCase();
|
||||
switch (schema) {
|
||||
case "http":
|
||||
case "https":
|
||||
case "gemini":
|
||||
case "tel":
|
||||
case "sms":
|
||||
case "mailto":
|
||||
case "openpgp4fpr":
|
||||
case "geo":
|
||||
case "dcaccount":
|
||||
case "dclogin":
|
||||
return openOnlineUrl(url);
|
||||
}
|
||||
}
|
||||
return true; // returning `true` aborts loading
|
||||
}
|
||||
}
|
||||
return true; // returning `true` aborts loading
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||
WebResourceResponse res = interceptRequest(url);
|
||||
if (res!=null) {
|
||||
return res;
|
||||
}
|
||||
return super.shouldInterceptRequest(view, url);
|
||||
}
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||
WebResourceResponse res = interceptRequest(url);
|
||||
if (res != null) {
|
||||
return res;
|
||||
}
|
||||
return super.shouldInterceptRequest(view, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequiresApi(21)
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
|
||||
WebResourceResponse res = interceptRequest(request.getUrl().toString());
|
||||
if (res!=null) {
|
||||
return res;
|
||||
}
|
||||
return super.shouldInterceptRequest(view, request);
|
||||
}
|
||||
});
|
||||
@Override
|
||||
@RequiresApi(21)
|
||||
public WebResourceResponse shouldInterceptRequest(
|
||||
WebView view, WebResourceRequest request) {
|
||||
WebResourceResponse res = interceptRequest(request.getUrl().toString());
|
||||
if (res != null) {
|
||||
return res;
|
||||
}
|
||||
return super.shouldInterceptRequest(view, request);
|
||||
}
|
||||
});
|
||||
webView.setFindListener(this);
|
||||
|
||||
// disable "safe browsing" as this has privacy issues,
|
||||
@@ -143,10 +149,15 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
protected void setForceDark() {
|
||||
if (Build.VERSION.SDK_INT <= 32 && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
|
||||
// needed for older API (tested on android7) that do not set `color-scheme` without the following hint
|
||||
WebSettingsCompat.setForceDark(webView.getSettings(),
|
||||
DynamicTheme.isDarkTheme(this) ? WebSettingsCompat.FORCE_DARK_ON : WebSettingsCompat.FORCE_DARK_OFF);
|
||||
if (Build.VERSION.SDK_INT <= 32
|
||||
&& WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
|
||||
// needed for older API (tested on android7) that do not set `color-scheme` without the
|
||||
// following hint
|
||||
WebSettingsCompat.setForceDark(
|
||||
webView.getSettings(),
|
||||
DynamicTheme.isDarkTheme(this)
|
||||
? WebSettingsCompat.FORCE_DARK_ON
|
||||
: WebSettingsCompat.FORCE_DARK_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,29 +188,31 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
try {
|
||||
MenuItem searchItem = menu.findItem(R.id.menu_search_web_view);
|
||||
searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(final MenuItem item) {
|
||||
searchMenu = menu;
|
||||
WebViewActivity.this.lastQuery = "";
|
||||
WebViewActivity.this.makeSearchMenuVisible(menu, searchItem, true);
|
||||
return true;
|
||||
}
|
||||
searchItem.setOnActionExpandListener(
|
||||
new MenuItem.OnActionExpandListener() {
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(final MenuItem item) {
|
||||
searchMenu = menu;
|
||||
WebViewActivity.this.lastQuery = "";
|
||||
WebViewActivity.this.makeSearchMenuVisible(menu, searchItem, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(final MenuItem item) {
|
||||
WebViewActivity.this.makeSearchMenuVisible(menu, searchItem, false);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(final MenuItem item) {
|
||||
WebViewActivity.this.makeSearchMenuVisible(menu, searchItem, false);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
SearchView searchView = (SearchView) searchItem.getActionView();
|
||||
searchView.setOnQueryTextListener(this);
|
||||
searchView.setQueryHint(getString(R.string.search));
|
||||
searchView.setIconifiedByDefault(true);
|
||||
|
||||
// hide the [X] beside the search field - this is too much noise, search can be aborted eg. by "back"
|
||||
// hide the [X] beside the search field - this is too much noise, search can be aborted eg. by
|
||||
// "back"
|
||||
ImageView closeBtn = searchView.findViewById(R.id.search_close_btn);
|
||||
if (closeBtn!=null) {
|
||||
if (closeBtn != null) {
|
||||
closeBtn.setEnabled(false);
|
||||
closeBtn.setImageDrawable(null);
|
||||
}
|
||||
@@ -211,18 +224,17 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// search
|
||||
|
||||
private Menu searchMenu = null;
|
||||
private Menu searchMenu = null;
|
||||
private String lastQuery = "";
|
||||
private Toast lastToast = null;
|
||||
private Toast lastToast = null;
|
||||
|
||||
private void updateResultCounter(int curr, int total) {
|
||||
if (searchMenu!=null) {
|
||||
if (searchMenu != null) {
|
||||
MenuItem item = searchMenu.findItem(R.id.menu_search_counter);
|
||||
if (curr!=-1) {
|
||||
item.setTitle(String.format("%d/%d", total==0? 0 : curr+1, total));
|
||||
if (curr != -1) {
|
||||
item.setTitle(String.format("%d/%d", total == 0 ? 0 : curr + 1, total));
|
||||
item.setVisible(true);
|
||||
} else {
|
||||
item.setVisible(false);
|
||||
@@ -239,7 +251,7 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
||||
public boolean onQueryTextChange(String query) {
|
||||
String normQuery = query.trim();
|
||||
lastQuery = normQuery;
|
||||
if (lastToast!=null) {
|
||||
if (lastToast != null) {
|
||||
lastToast.cancel();
|
||||
lastToast = null;
|
||||
}
|
||||
@@ -248,10 +260,10 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindResultReceived (int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting)
|
||||
{
|
||||
public void onFindResultReceived(
|
||||
int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
|
||||
if (isDoneCounting) {
|
||||
if (numberOfMatches>0) {
|
||||
if (numberOfMatches > 0) {
|
||||
updateResultCounter(activeMatchOrdinal, numberOfMatches);
|
||||
} else {
|
||||
if (lastQuery.isEmpty()) {
|
||||
@@ -269,7 +281,6 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// other actions
|
||||
|
||||
@Override
|
||||
@@ -310,15 +321,17 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
if (shouldAskToOpenLink()) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.open_url_confirmation)
|
||||
.setMessage(IDN.toASCII(url))
|
||||
.setNeutralButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.open, (d, w) -> IntentUtils.showInBrowser(this, url))
|
||||
.setNegativeButton(R.string.global_menu_edit_copy_desktop, (d, w) -> {
|
||||
Util.writeTextToClipboard(this, url);
|
||||
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show();
|
||||
})
|
||||
.show();
|
||||
.setTitle(R.string.open_url_confirmation)
|
||||
.setMessage(IDN.toASCII(url))
|
||||
.setNeutralButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.open, (d, w) -> IntentUtils.showInBrowser(this, url))
|
||||
.setNegativeButton(
|
||||
R.string.global_menu_edit_copy_desktop,
|
||||
(d, w) -> {
|
||||
Util.writeTextToClipboard(this, url);
|
||||
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show();
|
||||
})
|
||||
.show();
|
||||
} else {
|
||||
IntentUtils.showInBrowser(this, url);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
@@ -33,23 +32,13 @@ import androidx.core.app.TaskStackBuilder;
|
||||
import androidx.core.content.pm.ShortcutInfoCompat;
|
||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.thoughtcrime.securesms.connect.AccountManager;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.util.IntentUtils;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Prefs;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -60,11 +49,16 @@ import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import org.json.JSONObject;
|
||||
import org.thoughtcrime.securesms.connect.AccountManager;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.util.IntentUtils;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
|
||||
public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcEventDelegate {
|
||||
public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcEventDelegate {
|
||||
private static final String TAG = WebxdcActivity.class.getSimpleName();
|
||||
private static final String EXTRA_ACCOUNT_ID = "accountId";
|
||||
private static final String EXTRA_APP_MSG_ID = "appMessageId";
|
||||
@@ -109,8 +103,9 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (msgId == 0) {
|
||||
Toast.makeText(context, "Cannot get maps.xdc, see log for details.", Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
Toast.makeText(context, "Cannot get maps.xdc, see log for details.", Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
dcContext.setConfigInt("ui.maps_version", mapsVersion);
|
||||
}
|
||||
@@ -125,13 +120,15 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
openWebxdcActivity(context, instance.getId(), false, href);
|
||||
}
|
||||
|
||||
public static void openWebxdcActivity(Context context, int msgId, boolean hideActionBar, String href) {
|
||||
public static void openWebxdcActivity(
|
||||
Context context, int msgId, boolean hideActionBar, String href) {
|
||||
if (!Util.isClickedRecently()) {
|
||||
context.startActivity(getWebxdcIntent(context, msgId, hideActionBar, href));
|
||||
}
|
||||
}
|
||||
|
||||
private static Intent getWebxdcIntent(Context context, int msgId, boolean hideActionBar, String href) {
|
||||
private static Intent getWebxdcIntent(
|
||||
Context context, int msgId, boolean hideActionBar, String href) {
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
Intent intent = new Intent(context, WebxdcActivity.class);
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
@@ -145,20 +142,23 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
private static Intent[] getWebxdcIntentWithParentStack(Context context, int msgId) {
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
|
||||
final Intent chatIntent = new Intent(context, ConversationActivity.class)
|
||||
.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcContext.getMsg(msgId).getChatId())
|
||||
.setAction(Intent.ACTION_VIEW);
|
||||
final Intent chatIntent =
|
||||
new Intent(context, ConversationActivity.class)
|
||||
.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcContext.getMsg(msgId).getChatId())
|
||||
.setAction(Intent.ACTION_VIEW);
|
||||
|
||||
final Intent webxdcIntent = getWebxdcIntent(context, msgId, false, "");
|
||||
|
||||
return TaskStackBuilder.create(context)
|
||||
.addNextIntentWithParentStack(chatIntent)
|
||||
.addNextIntent(webxdcIntent)
|
||||
.getIntents();
|
||||
.addNextIntentWithParentStack(chatIntent)
|
||||
.addNextIntent(webxdcIntent)
|
||||
.getIntents();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean immersiveMode() { return hideActionBar; }
|
||||
protected boolean immersiveMode() {
|
||||
return hideActionBar;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle state, boolean ready) {
|
||||
@@ -169,29 +169,36 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
rpc = DcHelper.getRpc(this);
|
||||
initTTS();
|
||||
|
||||
webView.setWebChromeClient(
|
||||
new WebChromeClient() {
|
||||
@Override
|
||||
@RequiresApi(21)
|
||||
public boolean onShowFileChooser(
|
||||
WebView webView,
|
||||
ValueCallback<Uri[]> filePathCallback,
|
||||
WebChromeClient.FileChooserParams fileChooserParams) {
|
||||
if (WebxdcActivity.this.filePathCallback != null) {
|
||||
WebxdcActivity.this.filePathCallback.onReceiveValue(null);
|
||||
}
|
||||
WebxdcActivity.this.filePathCallback = filePathCallback;
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
intent.putExtra(
|
||||
Intent.EXTRA_ALLOW_MULTIPLE,
|
||||
fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE);
|
||||
WebxdcActivity.this.startActivityForResult(
|
||||
Intent.createChooser(intent, getString(R.string.select)), REQUEST_CODE_FILE_PICKER);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
webView.setWebChromeClient(new WebChromeClient() {
|
||||
@Override
|
||||
@RequiresApi(21)
|
||||
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
|
||||
if (WebxdcActivity.this.filePathCallback != null) {
|
||||
WebxdcActivity.this.filePathCallback.onReceiveValue(null);
|
||||
}
|
||||
WebxdcActivity.this.filePathCallback = filePathCallback;
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE);
|
||||
WebxdcActivity.this.startActivityForResult(Intent.createChooser(intent, getString(R.string.select)), REQUEST_CODE_FILE_PICKER);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
DcEventCenter eventCenter = DcHelper.getEventCenter(WebxdcActivity.this.getApplicationContext());
|
||||
DcEventCenter eventCenter =
|
||||
DcHelper.getEventCenter(WebxdcActivity.this.getApplicationContext());
|
||||
eventCenter.addObserver(DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE, this);
|
||||
eventCenter.addObserver(DcContext.DC_EVENT_MSGS_CHANGED, this);
|
||||
eventCenter.addObserver(DcContext.DC_EVENT_WEBXDC_REALTIME_DATA, this);
|
||||
|
||||
|
||||
int appMessageId = b.getInt(EXTRA_APP_MSG_ID);
|
||||
int accountId = b.getInt(EXTRA_ACCOUNT_ID);
|
||||
this.dcContext = DcHelper.getContext(getApplicationContext());
|
||||
@@ -207,20 +214,23 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
return;
|
||||
}
|
||||
|
||||
// `msg_id` in the subdomain makes sure, different apps using same files do not share the same cache entry
|
||||
// `msg_id` in the subdomain makes sure, different apps using same files do not share the same
|
||||
// cache entry
|
||||
// (WebView may use a global cache shared across objects).
|
||||
// (a random-id would also work, but would need maintenance and does not add benefits as we regard the file-part interceptRequest() only,
|
||||
// (a random-id would also work, but would need maintenance and does not add benefits as we
|
||||
// regard the file-part interceptRequest() only,
|
||||
// also a random-id is not that useful for debugging)
|
||||
this.baseURL = "https://acc" + dcContext.getAccountId() + "-msg" + appMessageId + ".localhost";
|
||||
|
||||
final JSONObject info = this.dcAppMsg.getWebxdcInfo();
|
||||
internetAccess = JsonUtils.optBoolean(info, "internet_access");
|
||||
if ("landscape".equals(JsonUtils.optString(info, "orientation"))) {
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
} else {
|
||||
// enter fullscreen mode if necessary,
|
||||
// this is needed here because if the app is opened while already in landscape mode, onConfigurationChanged() is not triggered
|
||||
setScreenMode(getResources().getConfiguration());
|
||||
// enter fullscreen mode if necessary,
|
||||
// this is needed here because if the app is opened while already in landscape mode,
|
||||
// onConfigurationChanged() is not triggered
|
||||
setScreenMode(getResources().getConfiguration());
|
||||
}
|
||||
selfAddr = info.optString("self_addr");
|
||||
sendUpdateMaxSize = info.optInt("send_update_max_size");
|
||||
@@ -238,7 +248,9 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
webSettings.setAllowUniversalAccessFromFileURLs(false);
|
||||
webSettings.setDatabaseEnabled(true);
|
||||
webSettings.setDomStorageEnabled(true);
|
||||
webView.setNetworkAvailable(internetAccess); // this does not block network but sets `window.navigator.isOnline` in js land
|
||||
webView.setNetworkAvailable(
|
||||
internetAccess); // this does not block network but sets `window.navigator.isOnline` in js
|
||||
// land
|
||||
webView.addJavascriptInterface(new InternalJSApi(), "InternalJSApi");
|
||||
|
||||
String extraHref = b.getString(EXTRA_HREF, "");
|
||||
@@ -256,18 +268,21 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
|
||||
webView.loadUrl(this.baseURL + "/webxdc_bootstrap324567869.html?i=1&href=" + encodedHref);
|
||||
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
final DcChat chat = dcContext.getChat(dcAppMsg.getChatId());
|
||||
Util.runOnMain(() -> {
|
||||
updateTitleAndMenu(info, chat);
|
||||
});
|
||||
});
|
||||
Util.runOnAnyBackgroundThread(
|
||||
() -> {
|
||||
final DcChat chat = dcContext.getChat(dcAppMsg.getChatId());
|
||||
Util.runOnMain(
|
||||
() -> {
|
||||
updateTitleAndMenu(info, chat);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
DcHelper.getNotificationCenter(this).updateVisibleWebxdc(dcContext.getAccountId(), dcAppMsg.getId());
|
||||
DcHelper.getNotificationCenter(this)
|
||||
.updateVisibleWebxdc(dcContext.getAccountId(), dcAppMsg.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -322,18 +337,24 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
}
|
||||
|
||||
private void initTTS() {
|
||||
tts = new TextToSpeech(this, new TextToSpeech.OnInitListener() {
|
||||
@Override
|
||||
public void onInit(int status) {
|
||||
Log.i(TAG, "TTS Init Status: " + status);
|
||||
}
|
||||
});
|
||||
tts =
|
||||
new TextToSpeech(
|
||||
this,
|
||||
new TextToSpeech.OnInitListener() {
|
||||
@Override
|
||||
public void onInit(int status) {
|
||||
Log.i(TAG, "TTS Init Status: " + status);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setScreenMode(Configuration config) {
|
||||
// enter/exit fullscreen mode depending on orientation (landscape/portrait),
|
||||
// on tablets there is enough height so fullscreen mode is never enabled there
|
||||
boolean enable = config.orientation == Configuration.ORIENTATION_LANDSCAPE && !getResources().getBoolean(R.bool.isBigScreen);
|
||||
getWindow().getDecorView().setSystemUiVisibility(enable? View.SYSTEM_UI_FLAG_FULLSCREEN : 0);
|
||||
boolean enable =
|
||||
config.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
&& !getResources().getBoolean(R.bool.isBigScreen);
|
||||
getWindow().getDecorView().setSystemUiVisibility(enable ? View.SYSTEM_UI_FLAG_FULLSCREEN : 0);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
if (hideActionBar || enable) {
|
||||
@@ -345,11 +366,14 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldAskToOpenLink() { return true; }
|
||||
protected boolean shouldAskToOpenLink() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is usually only called when internetAccess == true or for mailto/openpgp4fpr scheme,
|
||||
// because when internetAccess == false, the page is loaded inside an iframe,
|
||||
// and WebViewClient.shouldOverrideUrlLoading is not called for HTTP(S) links inside the iframe unless target=_blank is used
|
||||
// and WebViewClient.shouldOverrideUrlLoading is not called for HTTP(S) links inside the iframe
|
||||
// unless target=_blank is used
|
||||
@Override
|
||||
protected boolean openOnlineUrl(String url) {
|
||||
Log.i(TAG, "openOnlineUrl: " + url);
|
||||
@@ -378,8 +402,10 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
} else if (path.equalsIgnoreCase("/webxdc_bootstrap324567869.html")) {
|
||||
InputStream targetStream = getResources().openRawResource(R.raw.webxdc_wrapper);
|
||||
res = new WebResourceResponse("text/html", "UTF-8", targetStream);
|
||||
} else if (path.equalsIgnoreCase("/sandboxed_iframe_rtcpeerconnection_check_5965668501706.html")) {
|
||||
InputStream targetStream = getResources().openRawResource(R.raw.sandboxed_iframe_rtcpeerconnection_check);
|
||||
} else if (path.equalsIgnoreCase(
|
||||
"/sandboxed_iframe_rtcpeerconnection_check_5965668501706.html")) {
|
||||
InputStream targetStream =
|
||||
getResources().openRawResource(R.raw.sandboxed_iframe_rtcpeerconnection_check);
|
||||
res = new WebResourceResponse("text/html", "UTF-8", targetStream);
|
||||
} else {
|
||||
byte[] blob = this.dcAppMsg.getWebxdcBlob(path);
|
||||
@@ -393,33 +419,41 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext);
|
||||
if (mimeType == null) {
|
||||
switch (ext) {
|
||||
case "js": mimeType = "text/javascript"; break;
|
||||
case "wasm": mimeType = "application/wasm"; break;
|
||||
default: mimeType = "application/octet-stream"; Log.i(TAG, "unknown mime type for " + rawUrl); break;
|
||||
case "js":
|
||||
mimeType = "text/javascript";
|
||||
break;
|
||||
case "wasm":
|
||||
mimeType = "application/wasm";
|
||||
break;
|
||||
default:
|
||||
mimeType = "application/octet-stream";
|
||||
Log.i(TAG, "unknown mime type for " + rawUrl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
String encoding = mimeType.startsWith("text/")? "UTF-8" : null;
|
||||
String encoding = mimeType.startsWith("text/") ? "UTF-8" : null;
|
||||
InputStream targetStream = new ByteArrayInputStream(blob);
|
||||
res = new WebResourceResponse(mimeType, encoding, targetStream);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
InputStream targetStream = new ByteArrayInputStream(("Webxdc Request Error: " + e.getMessage()).getBytes());
|
||||
InputStream targetStream =
|
||||
new ByteArrayInputStream(("Webxdc Request Error: " + e.getMessage()).getBytes());
|
||||
res = new WebResourceResponse("text/plain", "UTF-8", targetStream);
|
||||
}
|
||||
|
||||
if (!internetAccess) {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Content-Security-Policy",
|
||||
headers.put(
|
||||
"Content-Security-Policy",
|
||||
"default-src 'self'; "
|
||||
+ "style-src 'self' 'unsafe-inline' blob: ; "
|
||||
+ "font-src 'self' data: blob: ; "
|
||||
+ "script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: ; "
|
||||
+ "connect-src 'self' data: blob: ; "
|
||||
+ "img-src 'self' data: blob: ; "
|
||||
+ "media-src 'self' data: blob: ;"
|
||||
+ "webrtc 'block' ; "
|
||||
);
|
||||
+ "style-src 'self' 'unsafe-inline' blob: ; "
|
||||
+ "font-src 'self' data: blob: ; "
|
||||
+ "script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: ; "
|
||||
+ "connect-src 'self' data: blob: ; "
|
||||
+ "img-src 'self' data: blob: ; "
|
||||
+ "media-src 'self' data: blob: ;"
|
||||
+ "webrtc 'block' ; ");
|
||||
headers.put("X-DNS-Prefetch-Control", "off");
|
||||
res.setResponseHeaders(headers);
|
||||
}
|
||||
@@ -427,49 +461,59 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
}
|
||||
|
||||
private void callJavaScriptFunction(String func) {
|
||||
webView.evaluateJavascript("document.getElementById('frame').contentWindow." + func + ";", null);
|
||||
webView.evaluateJavascript(
|
||||
"document.getElementById('frame').contentWindow." + func + ";", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleEvent(@NonNull DcEvent event) {
|
||||
int eventId = event.getId();
|
||||
if ((eventId == DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE && event.getData1Int() == dcAppMsg.getId())) {
|
||||
if ((eventId == DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE
|
||||
&& event.getData1Int() == dcAppMsg.getId())) {
|
||||
Log.i(TAG, "handling status update event");
|
||||
callJavaScriptFunction("__webxdcUpdate()");
|
||||
} else if ((eventId == DcContext.DC_EVENT_WEBXDC_REALTIME_DATA && event.getData1Int() == dcAppMsg.getId())) {
|
||||
} else if ((eventId == DcContext.DC_EVENT_WEBXDC_REALTIME_DATA
|
||||
&& event.getData1Int() == dcAppMsg.getId())) {
|
||||
Log.i(TAG, "handling realtime data event");
|
||||
StringBuilder data = new StringBuilder();
|
||||
for (byte b : event.getData2Blob()) {
|
||||
data.append(((int) b) + ",");
|
||||
data.append(((int) b) + ",");
|
||||
}
|
||||
callJavaScriptFunction("__webxdcRealtimeData([" + data + "])");
|
||||
} else if ((eventId == DcContext.DC_EVENT_MSGS_CHANGED && event.getData2Int() == dcAppMsg.getId())) {
|
||||
this.dcAppMsg = this.dcContext.getMsg(event.getData2Int()); // msg changed, reload data from db
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
final JSONObject info = dcAppMsg.getWebxdcInfo();
|
||||
final DcChat chat = dcContext.getChat(dcAppMsg.getChatId());
|
||||
Util.runOnMain(() -> {
|
||||
updateTitleAndMenu(info, chat);
|
||||
});
|
||||
});
|
||||
} else if ((eventId == DcContext.DC_EVENT_MSGS_CHANGED
|
||||
&& event.getData2Int() == dcAppMsg.getId())) {
|
||||
this.dcAppMsg =
|
||||
this.dcContext.getMsg(event.getData2Int()); // msg changed, reload data from db
|
||||
Util.runOnAnyBackgroundThread(
|
||||
() -> {
|
||||
final JSONObject info = dcAppMsg.getWebxdcInfo();
|
||||
final DcChat chat = dcContext.getChat(dcAppMsg.getChatId());
|
||||
Util.runOnMain(
|
||||
() -> {
|
||||
updateTitleAndMenu(info, chat);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTitleAndMenu(JSONObject info, DcChat chat) {
|
||||
final String docName = JsonUtils.optString(info, "document");
|
||||
final String xdcName = JsonUtils.optString(info, "name");
|
||||
final String currSourceCodeUrl = JsonUtils.optString(info, "source_code_url");
|
||||
getSupportActionBar().setTitle((docName.isEmpty() ? xdcName : docName) + " – " + chat.getName());
|
||||
if (!sourceCodeUrl.equals(currSourceCodeUrl)) {
|
||||
sourceCodeUrl = currSourceCodeUrl;
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
final String docName = JsonUtils.optString(info, "document");
|
||||
final String xdcName = JsonUtils.optString(info, "name");
|
||||
final String currSourceCodeUrl = JsonUtils.optString(info, "source_code_url");
|
||||
getSupportActionBar()
|
||||
.setTitle((docName.isEmpty() ? xdcName : docName) + " – " + chat.getName());
|
||||
if (!sourceCodeUrl.equals(currSourceCodeUrl)) {
|
||||
sourceCodeUrl = currSourceCodeUrl;
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
private void showInChat() {
|
||||
Intent intent = new Intent(this, ConversationActivity.class);
|
||||
intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcAppMsg.getChatId());
|
||||
intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, DcMsg.getMessagePosition(dcAppMsg, dcContext));
|
||||
intent.putExtra(
|
||||
ConversationActivity.STARTING_POSITION_EXTRA,
|
||||
DcMsg.getMessagePosition(dcAppMsg, dcContext));
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@@ -487,17 +531,23 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
BitmapDrawable drawable = (BitmapDrawable) Drawable.createFromStream(is, "icon");
|
||||
Bitmap bitmap = drawable.getBitmap();
|
||||
|
||||
ShortcutInfoCompat shortcutInfoCompat = new ShortcutInfoCompat.Builder(context, "xdc-" + dcContext.getAccountId() + "-" + msgId)
|
||||
.setShortLabel(docName.isEmpty() ? xdcName : docName)
|
||||
.setIcon(IconCompat.createWithBitmap(bitmap)) // createWithAdaptiveBitmap() removes decorations but cuts out a too small circle and defamiliarize the icon too much
|
||||
.setIntents(getWebxdcIntentWithParentStack(context, msgId))
|
||||
.build();
|
||||
ShortcutInfoCompat shortcutInfoCompat =
|
||||
new ShortcutInfoCompat.Builder(context, "xdc-" + dcContext.getAccountId() + "-" + msgId)
|
||||
.setShortLabel(docName.isEmpty() ? xdcName : docName)
|
||||
.setIcon(
|
||||
IconCompat.createWithBitmap(
|
||||
bitmap)) // createWithAdaptiveBitmap() removes decorations but cuts out a too
|
||||
// small circle and defamiliarize the icon too much
|
||||
.setIntents(getWebxdcIntentWithParentStack(context, msgId))
|
||||
.build();
|
||||
|
||||
Toast.makeText(context, R.string.one_moment, Toast.LENGTH_SHORT).show();
|
||||
if (!ShortcutManagerCompat.requestPinShortcut(context, shortcutInfoCompat, null)) {
|
||||
Toast.makeText(context, "ErrAddToHomescreen: requestPinShortcut() failed", Toast.LENGTH_LONG).show();
|
||||
Toast.makeText(
|
||||
context, "ErrAddToHomescreen: requestPinShortcut() failed", Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
} catch(Exception e) {
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(context, "ErrAddToHomescreen: " + e, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
@@ -509,7 +559,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
try {
|
||||
if (data.getDataString() != null) {
|
||||
dataUris = new Uri[]{Uri.parse(data.getDataString())};
|
||||
dataUris = new Uri[] {Uri.parse(data.getDataString())};
|
||||
} else if (data.getClipData() != null) {
|
||||
final int numSelectedFiles = data.getClipData().getItemCount();
|
||||
dataUris = new Uri[numSelectedFiles];
|
||||
@@ -558,36 +608,49 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
return WebxdcActivity.this.selfAddr;
|
||||
}
|
||||
|
||||
/** @noinspection unused*/
|
||||
/**
|
||||
* @noinspection unused
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public String selfName() {
|
||||
return WebxdcActivity.this.dcContext.getName();
|
||||
}
|
||||
|
||||
/** @noinspection unused*/
|
||||
/**
|
||||
* @noinspection unused
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public boolean sendStatusUpdate(String payload) {
|
||||
Log.i(TAG, "sendStatusUpdate");
|
||||
if (!WebxdcActivity.this.dcContext.sendWebxdcStatusUpdate(WebxdcActivity.this.dcAppMsg.getId(), payload)) {
|
||||
DcChat dcChat = WebxdcActivity.this.dcContext.getChat(WebxdcActivity.this.dcAppMsg.getChatId());
|
||||
Toast.makeText(WebxdcActivity.this,
|
||||
dcChat.isContactRequest() ?
|
||||
WebxdcActivity.this.getString(R.string.accept_request_first) :
|
||||
WebxdcActivity.this.dcContext.getLastError(),
|
||||
Toast.LENGTH_LONG).show();
|
||||
if (!WebxdcActivity.this.dcContext.sendWebxdcStatusUpdate(
|
||||
WebxdcActivity.this.dcAppMsg.getId(), payload)) {
|
||||
DcChat dcChat =
|
||||
WebxdcActivity.this.dcContext.getChat(WebxdcActivity.this.dcAppMsg.getChatId());
|
||||
Toast.makeText(
|
||||
WebxdcActivity.this,
|
||||
dcChat.isContactRequest()
|
||||
? WebxdcActivity.this.getString(R.string.accept_request_first)
|
||||
: WebxdcActivity.this.dcContext.getLastError(),
|
||||
Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @noinspection unused*/
|
||||
/**
|
||||
* @noinspection unused
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public String getStatusUpdates(int lastKnownSerial) {
|
||||
Log.i(TAG, "getStatusUpdates");
|
||||
return WebxdcActivity.this.dcContext.getWebxdcStatusUpdates(WebxdcActivity.this.dcAppMsg.getId(), lastKnownSerial );
|
||||
return WebxdcActivity.this.dcContext.getWebxdcStatusUpdates(
|
||||
WebxdcActivity.this.dcAppMsg.getId(), lastKnownSerial);
|
||||
}
|
||||
|
||||
/** @noinspection unused*/
|
||||
/**
|
||||
* @noinspection unused
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public String sendToChat(String message) {
|
||||
Log.i(TAG, "sendToChat");
|
||||
@@ -601,24 +664,27 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
String name = null;
|
||||
String type = null;
|
||||
if (jsonObject.has("base64")) {
|
||||
data = Base64.decode(jsonObject.getString("base64"), Base64.NO_WRAP | Base64.NO_PADDING);
|
||||
name = jsonObject.getString("name");
|
||||
data = Base64.decode(jsonObject.getString("base64"), Base64.NO_WRAP | Base64.NO_PADDING);
|
||||
name = jsonObject.getString("name");
|
||||
}
|
||||
if (jsonObject.has("type")) {
|
||||
type = jsonObject.getString("type");
|
||||
type = jsonObject.getString("type");
|
||||
}
|
||||
if (jsonObject.has("text")) {
|
||||
text = jsonObject.getString("text");
|
||||
text = jsonObject.getString("text");
|
||||
}
|
||||
if (jsonObject.has("subject")) {
|
||||
subject = jsonObject.getString("subject");
|
||||
subject = jsonObject.getString("subject");
|
||||
}
|
||||
if (jsonObject.has("html")) {
|
||||
html = jsonObject.getString("html");
|
||||
html = jsonObject.getString("html");
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(text) && TextUtils.isEmpty(subject) && TextUtils.isEmpty(html) && TextUtils.isEmpty(name)) {
|
||||
return "provided file is invalid, you need to set both name and base64 content";
|
||||
if (TextUtils.isEmpty(text)
|
||||
&& TextUtils.isEmpty(subject)
|
||||
&& TextUtils.isEmpty(html)
|
||||
&& TextUtils.isEmpty(name)) {
|
||||
return "provided file is invalid, you need to set both name and base64 content";
|
||||
}
|
||||
|
||||
DcHelper.sendToChat(WebxdcActivity.this, data, type, name, html, subject, text);
|
||||
@@ -629,7 +695,9 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
}
|
||||
}
|
||||
|
||||
/** @noinspection unused*/
|
||||
/**
|
||||
* @noinspection unused
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void sendRealtimeAdvertisement() {
|
||||
int accountId = WebxdcActivity.this.dcContext.getAccountId();
|
||||
@@ -641,13 +709,17 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
}
|
||||
}
|
||||
|
||||
/** @noinspection unused*/
|
||||
/**
|
||||
* @noinspection unused
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void leaveRealtimeChannel() {
|
||||
WebxdcActivity.this.leaveRealtimeChannel();
|
||||
}
|
||||
|
||||
/** @noinspection unused*/
|
||||
/**
|
||||
* @noinspection unused
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void sendRealtimeData(String jsonData) {
|
||||
int accountId = WebxdcActivity.this.dcContext.getAccountId();
|
||||
@@ -665,6 +737,5 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
if (lang != null && !lang.isEmpty()) tts.setLanguage(Locale.forLanguageTag(lang));
|
||||
tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user