mirror of
https://github.com/ArcaneChat/android.git
synced 2026-07-03 14:05:24 +02:00
Compare commits
222 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 64752d3bae | |||
| 6145e0d2df | |||
| b365284743 | |||
| af62041c14 | |||
| 910dbf56fd | |||
| 4eca0dea4a | |||
| a76c17fd45 | |||
| b00aeec03a | |||
| ffe147fde8 | |||
| 02a5bb06a9 | |||
| a8997738bf | |||
| 202690d02e | |||
| 6d14bbdbc7 | |||
| 03a0b53eee | |||
| a044181a75 | |||
| e9ae9dc5bf | |||
| cbfa3b7e58 | |||
| bf57d3bd73 | |||
| 6200f354ca | |||
| c8dfb08dcb | |||
| 543e9d91e4 | |||
| dec5879919 | |||
| a1d21d8562 | |||
| 253ed877e5 | |||
| b809d24ab1 | |||
| 78aee0a487 | |||
| d16254c146 | |||
| 7ab3ef8453 | |||
| 4b695a3293 | |||
| da4c382c9a | |||
| 80973960f5 | |||
| 7be75f7008 | |||
| 6fa114c6c0 | |||
| 767b5f2bae | |||
| ed3a21b992 | |||
| 899c2b5647 | |||
| 61e616a53b | |||
| 3bf4504de9 | |||
| cd0740d895 | |||
| 9f99edc159 | |||
| 60568f23f5 | |||
| 6dc8bd7ba8 | |||
| ff4ecb3bda | |||
| c71e71359a | |||
| d5a09fa25e | |||
| f52c2be2c2 | |||
| 2233b93108 | |||
| d76eb3239a | |||
| a982bd2bbb | |||
| 9ef3edcc0e | |||
| de7c54b886 | |||
| 9c9f966597 | |||
| 12e77789d3 | |||
| bd640072ab | |||
| 0a9c46fbfc | |||
| 1cc4f11484 | |||
| 9386f2f9cb | |||
| 45d73d4604 | |||
| a5892330dd | |||
| 8a0e2d72a4 | |||
| 2629c65564 | |||
| 10694a6809 | |||
| aa3d177243 | |||
| 4d35d4edeb | |||
| bbaba3cd33 | |||
| d1f002a132 | |||
| 9ce9a91c95 | |||
| 7844e146b1 | |||
| eb997eca00 | |||
| 30124de2a8 | |||
| 1dcf7e4860 | |||
| 9ffc904ae5 | |||
| 2704749b44 | |||
| 1c174b5b70 | |||
| 8b5dd70d75 | |||
| f022316ad7 | |||
| 3386f5c5f7 | |||
| 57eead3a34 | |||
| 2868b51835 | |||
| 82118db71b | |||
| 14f55ca6b1 | |||
| cebfa12142 | |||
| 96c8c21b78 | |||
| 9ab1b1f3a7 | |||
| 94a0e426f8 | |||
| 006f8ae826 | |||
| 2889266522 | |||
| 5e6fccf143 | |||
| 3847e20d18 | |||
| fc69212a51 | |||
| 8999f54ba2 | |||
| f470e92300 | |||
| 3ac49e3e58 | |||
| 8dd9cfec5b | |||
| 0588214ee7 | |||
| 4ec49a031c | |||
| a31d7d6d3e | |||
| 64bbe9866d | |||
| 5d7ab84efc | |||
| 11f73a88e8 | |||
| dd0e847976 | |||
| 71ed333468 | |||
| 41d94ae3ee | |||
| b66bc1f863 | |||
| df7d80319c | |||
| 134145d166 | |||
| 688a103c10 | |||
| e2efa1f913 | |||
| aebd5c66f7 | |||
| 15c60c6b12 | |||
| f319ba2b83 | |||
| 93f12e7367 | |||
| 10acb07f82 | |||
| ee9a8dd53a | |||
| 0a55023bdb | |||
| fec9f8b4d3 | |||
| 13374df709 | |||
| 7fa04dc3c0 | |||
| 9d4e0e4e21 | |||
| 473c28ab07 | |||
| de47feac40 | |||
| 84894ff538 | |||
| f254c35749 | |||
| 1ff4e069ea | |||
| d574d33596 | |||
| f6756fc34b | |||
| 7c27eb47fc | |||
| 8d5a55c24c | |||
| 2e9aa79b02 | |||
| 1f9264225b | |||
| 18e145faaf | |||
| 47cf70120a | |||
| 86451a00ff | |||
| 34be7aab17 | |||
| 59139ed242 | |||
| 11f3964bdc | |||
| 7683408d18 | |||
| b74e793654 | |||
| 4bd74324d2 | |||
| 9a121b3039 | |||
| a9832c9c53 | |||
| 1e4c8bc291 | |||
| c45a47e53c | |||
| a07723d515 | |||
| 02d7a6fb1f | |||
| d62e8fa864 | |||
| a03fd337ef | |||
| 27c0f45430 | |||
| a0f1232261 | |||
| 5640d31080 | |||
| 1542fc67ca | |||
| a99a4ec85b | |||
| fa17b134cf | |||
| 6898a37704 | |||
| 45b6a5c565 | |||
| 7330e6ba39 | |||
| 8f405914a2 | |||
| 6d09a6f318 | |||
| b349a7e601 | |||
| 0d610895d2 | |||
| a65c677ecc | |||
| db93aeacc0 | |||
| 09ff0d888b | |||
| b9c5e741e8 | |||
| 620d86c903 | |||
| 14e31650e7 | |||
| 9848cb5847 | |||
| bf7db005ef | |||
| aa032dbb37 | |||
| 9e1b9775e2 | |||
| fcd16d9e32 | |||
| 2a4bd2a001 | |||
| eacd089c8f | |||
| 9ed355efd1 | |||
| d1bb25004d | |||
| 8d1b010d1b | |||
| 8a9b76af3a | |||
| 4bbd9428e3 | |||
| 83646003b6 | |||
| 947887f335 | |||
| 69631cd857 | |||
| 0722426425 | |||
| 98d54bbe6a | |||
| c1dc0cf127 | |||
| 44a1ce42f0 | |||
| a0c6cef0fa | |||
| f96e89956c | |||
| ce9ded9d8b | |||
| cb2d9f16ac | |||
| c2a13ae9dd | |||
| f8c2403e61 | |||
| 616b495426 | |||
| a22baa7fc3 | |||
| 0b9bb9ff76 | |||
| ef96192d8b | |||
| 13c34d9480 | |||
| 610f4cebc2 | |||
| d4d15eee7c | |||
| ee3886285b | |||
| a32c20473f | |||
| db956d93cf | |||
| 6a5a75131e | |||
| a676a47ed4 | |||
| 2067122d2c | |||
| 8a0fe892f4 | |||
| 29a6f4eb31 | |||
| f610592bff | |||
| 898008e2a1 | |||
| 9eaa3ca182 | |||
| c98818e5cf | |||
| 7643ecd90a | |||
| e4993bd302 | |||
| 32ee198128 | |||
| ab0d33b3c0 | |||
| 2c8b781817 | |||
| e59655c456 | |||
| 2ce7e5a3c7 | |||
| 79c7419b71 | |||
| 2efaf1df93 | |||
| 814ac7d976 | |||
| b0e5c1c634 | |||
| bae998f811 |
+28
-2
@@ -1,10 +1,36 @@
|
||||
# Delta Chat Android Changelog
|
||||
|
||||
## Unreleased
|
||||
## v2.43.0
|
||||
2026-02
|
||||
|
||||
* Improve switch speed when changing profiles
|
||||
* Allow to switch profile when sharing or forwarding
|
||||
* Display message views count for channel owners
|
||||
* Don't notify notification-to-all from in-chat apps if the chat is muted
|
||||
* Allow to see inbox quota for all relays in connectivity screen
|
||||
* Update to core 2.36.0
|
||||
* Truncate file names in the middle, not at the end; important information are more often at the end
|
||||
* Remove email address from user profile
|
||||
* Mark external links with " ↗" to make them clear
|
||||
* Make QR code larger on "Add Second Device" screen
|
||||
* Add indication for blocked contacts in user profile
|
||||
* Allow to start calls with video disabled
|
||||
* Show hint for empty contact search results
|
||||
* Add background playing for voice messages and other audio files
|
||||
* Allow scanning Invitation Code when creating a new profile
|
||||
* Add context menu in long-pressing relays items instead of showing buttons
|
||||
* Enhanced video player UI
|
||||
* Fix: Show dialog if pasted QR codes are invalid
|
||||
* Fix: Refresh chat list when returning from conversation if selected profile changed
|
||||
* Fix: Update menu when using "select all" in contact selection
|
||||
* Fix: Avoid empty profiles after using "add as second device" from welcome screen
|
||||
* Fix: Remove from group deselected members in the contact selection list
|
||||
* Fix multi-device seen messages synchronization when using multiple relays
|
||||
* Fix mailto handling
|
||||
* Fix layout problems inside in-chat apps
|
||||
* Fix real-time for in-chat apps that need it
|
||||
* Avoid crash when the app is minimized with profile switcher or reactions dialogs open
|
||||
* Remove "trash icon" option from contact selection list when adding members to group
|
||||
* Update to core 2.43.0
|
||||
|
||||
## v2.35.0
|
||||
2026-01
|
||||
|
||||
+8
-3
@@ -33,8 +33,8 @@ android {
|
||||
useLibrary 'org.apache.http.legacy'
|
||||
|
||||
defaultConfig {
|
||||
versionCode 30000736
|
||||
versionName "2.36.0"
|
||||
versionCode 30000737
|
||||
versionName "2.43.0"
|
||||
|
||||
applicationId "chat.delta.lite"
|
||||
multiDexEnabled true
|
||||
@@ -211,6 +211,8 @@ dependencies {
|
||||
implementation "io.noties.markwon:inline-parser:$markwon_version"
|
||||
implementation 'com.airbnb.android:lottie:4.2.2' // Lottie animations support.
|
||||
|
||||
def media3_version = "1.8.0" // 1.9.0 need minSdkVersion 23
|
||||
|
||||
implementation 'androidx.concurrent:concurrent-futures:1.3.0'
|
||||
implementation 'androidx.sharetarget:sharetarget:1.2.0'
|
||||
implementation 'androidx.webkit:webkit:1.14.0'
|
||||
@@ -231,8 +233,11 @@ dependencies {
|
||||
implementation 'androidx.work:work-runtime:2.9.1'
|
||||
implementation 'androidx.emoji2:emoji2-emojipicker:1.5.0'
|
||||
implementation 'com.google.guava:guava:31.1-android'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.19.1' // plays video and audio
|
||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.19.1' // FIXME: exoplayer dependencies kept for Video, but we shall migrate them at some point
|
||||
implementation 'com.google.android.exoplayer:exoplayer-ui:2.19.1'
|
||||
implementation "androidx.media3:media3-exoplayer:$media3_version"
|
||||
implementation "androidx.media3:media3-session:$media3_version"
|
||||
implementation "androidx.media3:media3-ui:$media3_version"
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
|
||||
implementation 'com.google.zxing:core:3.3.0' // fixed version to support SDK<24
|
||||
implementation ('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false } // QR Code scanner
|
||||
|
||||
+39
-3
@@ -153,6 +153,42 @@ static uint32_t* jintArray2uint32Pointer(JNIEnv* env, jintArray ja, int* ret_icn
|
||||
}
|
||||
|
||||
|
||||
/************************************************************
|
||||
* DcEventChannel
|
||||
************************************************************/
|
||||
|
||||
static dc_event_channel_t* get_dc_event_channel(JNIEnv *env, jobject obj)
|
||||
{
|
||||
static jfieldID fid = 0;
|
||||
if (fid==0) {
|
||||
jclass cls = (*env)->GetObjectClass(env, obj);
|
||||
fid = (*env)->GetFieldID(env, cls, "eventChannelCPtr", "J" /*Signature, J=long*/);
|
||||
}
|
||||
if (fid) {
|
||||
return (dc_event_channel_t*)(*env)->GetLongField(env, obj, fid);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jlong Java_com_b44t_messenger_DcEventChannel_createEventChannelCPtr(JNIEnv *env, jobject obj)
|
||||
{
|
||||
return (jlong)dc_event_channel_new();
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT void Java_com_b44t_messenger_DcEventChannel_unrefEventChannelCPtr(JNIEnv *env, jobject obj)
|
||||
{
|
||||
dc_event_channel_unref(get_dc_event_channel(env, obj));
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jlong Java_com_b44t_messenger_DcEventChannel_getEventEmitterCPtr(JNIEnv *env, jobject obj)
|
||||
{
|
||||
return (jlong)dc_event_channel_get_event_emitter(get_dc_event_channel(env, obj));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* DcAccounts
|
||||
******************************************************************************/
|
||||
@@ -172,11 +208,11 @@ static dc_accounts_t* get_dc_accounts(JNIEnv *env, jobject obj)
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jlong Java_com_b44t_messenger_DcAccounts_createAccountsCPtr(JNIEnv *env, jobject obj, jstring dir)
|
||||
JNIEXPORT jlong Java_com_b44t_messenger_DcAccounts_createAccountsCPtr(JNIEnv *env, jobject obj, jstring dir, jobject chanObj)
|
||||
{
|
||||
CHAR_REF(dir);
|
||||
int writable = 1;
|
||||
jlong accountsCPtr = (jlong)dc_accounts_new(dirPtr, writable);
|
||||
jlong accountsCPtr = (jlong)dc_accounts_new_with_event_channel(dirPtr, writable, get_dc_event_channel(env, chanObj));
|
||||
CHAR_UNREF(dir);
|
||||
return accountsCPtr;
|
||||
}
|
||||
@@ -1957,7 +1993,7 @@ JNIEXPORT jstring Java_com_b44t_messenger_DcBackupProvider_getQr(JNIEnv *env, jo
|
||||
|
||||
JNIEXPORT jstring Java_com_b44t_messenger_DcBackupProvider_getQrSvg(JNIEnv *env, jobject obj)
|
||||
{
|
||||
char* temp = dc_backup_provider_get_qr_svg(get_dc_backup_provider(env, obj));
|
||||
char* temp = dc_create_qr_svg(dc_backup_provider_get_qr(get_dc_backup_provider(env, obj)));
|
||||
jstring ret = JSTRING_NEW(temp);
|
||||
dc_str_unref(temp);
|
||||
return ret;
|
||||
|
||||
+1
-1
Submodule jni/deltachat-core-rust updated: 42f35b47cb...7ad67cfa56
@@ -0,0 +1,17 @@
|
||||
<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>
|
||||
</vector>
|
||||
@@ -0,0 +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"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +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"/>
|
||||
</adaptive-icon>
|
||||
@@ -38,6 +38,7 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||
|
||||
<!-- force compiling emojipicker on sdk<21; runtime checks are required then -->
|
||||
<uses-sdk tools:overrideLibrary="androidx.emoji2.emojipicker"/>
|
||||
@@ -54,7 +55,6 @@
|
||||
android:requestLegacyExternalStorage="true"
|
||||
tools:targetApi="TIRAMISU"
|
||||
android:hasFragileUserData="true"
|
||||
android:enableOnBackInvokedCallback="false"
|
||||
>
|
||||
|
||||
<!-- android car support, see https://developer.android.com/training/auto/start/,
|
||||
@@ -93,6 +93,7 @@
|
||||
<intent-filter>
|
||||
<data android:scheme="mailto"/>
|
||||
<action android:name="android.intent.action.SENDTO"/>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
</intent-filter>
|
||||
@@ -220,7 +221,7 @@
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".WelcomeActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/TextSecure.LightNoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
@@ -391,6 +392,15 @@
|
||||
android:name=".service.FetchForegroundService"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
|
||||
<service
|
||||
android:name=".service.AudioPlaybackService"
|
||||
android:foregroundServiceType="mediaPlayback"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.media3.session.MediaSessionService"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver android:name=".notifications.MarkReadReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1188,6 +1188,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
|
||||
<li>
|
||||
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
|
||||
establishing end-to-end encryption between contacts and all members of a group chat.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption and forward secrecy.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
|
||||
@@ -1372,12 +1376,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
|
||||
<p>Servers can therefore only see:</p>
|
||||
|
||||
<ul>
|
||||
<li>the sender and receiver addresses</li>
|
||||
<li>and the message size.</li>
|
||||
<li>Sender and receiver addresses, randomly generated by default</li>
|
||||
<li>Message size</li>
|
||||
</ul>
|
||||
|
||||
<p>By default, the addresses are randomly generated.</p>
|
||||
|
||||
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
|
||||
|
||||
<h3 id="device-seizure">
|
||||
@@ -1453,7 +1455,7 @@ but an implementation has not been agreed as a priority yet.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
|
||||
This means that if your private decryption key is leaked,
|
||||
@@ -1464,12 +1466,9 @@ Otherwise, someone obtaining your decryption keys
|
||||
is typically also able to get all your non-deleted messages
|
||||
and doesn’t even need to decrypt any previously collected messages.</p>
|
||||
|
||||
<p>We designed a Forward Secrecy approach that withstood
|
||||
initial examination from some cryptographers and implementation experts
|
||||
but is pending a more formal write up
|
||||
to ascertain it reliably works in federated messaging and with multi-device usage,
|
||||
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
|
||||
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will provide reliable deletion (forward secrecy) through automatic key rotation.
|
||||
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="pqc">
|
||||
|
||||
@@ -1479,12 +1478,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
|
||||
in collaboration with other OpenPGP implementers.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption to protect against quantum computer attacks.
|
||||
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="how-can-i-manually-check-encryption-information">
|
||||
|
||||
@@ -1663,52 +1663,38 @@ ordered chronologically:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
|
||||
and implementation of verified groups and setup contact protocols
|
||||
in 2017 and 2018 and also helped to integrate end-to-end Encryption
|
||||
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
|
||||
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
|
||||
and released a first Desktop app beta version, and which moreover
|
||||
moored our feature developments in UX research in human rights contexts,
|
||||
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
The second 2019/2020 grant (~$300K) helped us to
|
||||
release Delta/iOS versions, to convert our core library to Rust, and
|
||||
to provide new features for all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2021 we received further EU funding for two Next-Generation-Internet
|
||||
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
|
||||
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
|
||||
This funding supported our long-running goals to make Delta Chat more usable
|
||||
and compatible with a wide range of email servers world-wide, and more resilient and secure
|
||||
in places often affected by internet censorship and shutdowns.</p>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>2023-2024 we successfully completed the OTF-funded
|
||||
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
|
||||
allowing us to introduce guaranteed encryption,
|
||||
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
|
||||
and providing “instant onboarding” in all apps released from April 2024 on.</p>
|
||||
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
|
||||
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
|
||||
and released a first Desktop app beta version, and which moreover
|
||||
moored our feature developments in UX research in human rights contexts,
|
||||
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
The second 2019/2020 grant (~$300K) helped us to
|
||||
release Delta/iOS versions, to convert our core library to Rust, and
|
||||
to provide new features for all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
|
||||
and implementation of verified groups and setup contact protocols
|
||||
in 2017 and 2018 and also helped to integrate end-to-end Encryption
|
||||
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Sometimes we receive one-time donations from private individuals.
|
||||
|
||||
@@ -1045,31 +1045,31 @@ für die <a href="https://chatmail.at/clients">Chatmail-Clients</a> umfasst, von
|
||||
|
||||
</h3>
|
||||
|
||||
<p>We would like to improve Delta Chat with your help,
|
||||
which is why Delta Chat for Android asks whether you want
|
||||
to send anonymous usage statistics.</p>
|
||||
<p>Wir möchten Delta Chat mit deiner Hilfe verbessern.
|
||||
Deshalb fragt Delta Chat für Android, ob du
|
||||
anonyme Nutzungsstatistiken senden möchtest.</p>
|
||||
|
||||
<p>You can turn it on and off at
|
||||
<strong>Settings → Advanced → Send statistics to Delta Chat’s developers</strong>.</p>
|
||||
<p>Du kannst dies unter
|
||||
<strong>Einstellungen → Erweitert → Statistik an Delta Chat Entwickler senden</strong> ein- und ausschalten.</p>
|
||||
|
||||
<p>When you turn it on,
|
||||
weekly statistics will be automatically sent to a bot.</p>
|
||||
<p>Wenn eingeschaltet,
|
||||
werden wöchentlich Statistiken automatisch an einen Bot gesendet.</p>
|
||||
|
||||
<p>We are interested e.g. in statistics like:</p>
|
||||
<p>Wir sind beispielsweise an folgenden Statistiken interessiert:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>How many contacts are introduced by personally scanning a QR code?</p>
|
||||
<p>Wie viele Kontakte werden durch das persönliche Scannen eines QR-Codes hergestellt?</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Which versions of Delta Chat are being used?</p>
|
||||
<p>Welche Versionen von Delta Chat werden verwendet?</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>What errors occur for users?</p>
|
||||
<p>Welche Fehler treten bei Benutzern auf?</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>We will <em>not</em> collect any personally identifiable information about you.</p>
|
||||
<p>Wir werden <em>keinerlei</em> personenbezogene Daten über dich sammeln.</p>
|
||||
|
||||
<h3 id="ich-bin-an-technischen-details-interessiert-gibt-es-hierzu-weitere-infos">
|
||||
|
||||
@@ -1107,6 +1107,10 @@ zum Austausch von Verschlüsselungsinformationen durch Scannen von QR-Codes oder
|
||||
<li>
|
||||
<p><a href="https://autocrypt.org">Autocrypt</a> wird verwendet, um automatisch eine Ende-zu-Ende-Verschlüsselung zwischen Kontakten und allen Mitgliedern einer Gruppe herzustellen.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, dessen vollständige Implementierung für 2026 geplant ist,
|
||||
wir post-quantum-resistente Verschlüsselung und Forward Secrecy einführen.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Teilen eines Kontakts im Chat</a>
|
||||
ermöglicht es den Empfängern, eine Ende-zu-Ende-Verschlüsselung mit dem Kontakt zu verwenden.</p>
|
||||
@@ -1280,15 +1284,13 @@ selbst wenn die Nachricht nicht Ende-zu-Ende-verschlüsselt war.</p>
|
||||
speichern Delta-Chat-Apps keine Metadaten über Kontakte oder Gruppen auf Servern. Auch nicht in verschlüsselter Form.
|
||||
Stattdessen werden alle Gruppen-Metadaten durchgängig verschlüsselt und ausschließlich auf den Endgeräten der Nutzer gespeichert.</p>
|
||||
|
||||
<p>Servers can therefore only see:</p>
|
||||
<p>Server können daher nur das folgende sehen:</p>
|
||||
|
||||
<ul>
|
||||
<li>the sender and receiver addresses</li>
|
||||
<li>and the message size.</li>
|
||||
<li>Absender- und Empfängeradressen, standardmäßig zufällig generiert</li>
|
||||
<li>Größe der Nachricht</li>
|
||||
</ul>
|
||||
|
||||
<p>By default, the addresses are randomly generated.</p>
|
||||
|
||||
<p>Alle anderen Metadaten zu Nachrichten, Kontakten und Gruppen befinden sich im Ende-zu-Ende-verschlüsselten Teil der Nachrichten.</p>
|
||||
|
||||
<h3 id="device-seizure">
|
||||
@@ -1299,15 +1301,15 @@ Stattdessen werden alle Gruppen-Metadaten durchgängig verschlüsselt und aussch
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Both for protecting against metadata-collecting servers
|
||||
as well as against the threat of device seizure
|
||||
we recommend to use a <a href="https://chatmail.at/relays">chatmail relay</a>
|
||||
to create chat profiles using random addresses for transport.
|
||||
Note that Delta Chat apps on all platforms support multiple profiles
|
||||
so you can easily use situation-specific profiles next to your “main” profile
|
||||
with the knowledge that all their data, along with all metadata, will be deleted.
|
||||
Moreover, if a device is seized then chat contacts using short-lived profiles
|
||||
can not be identified easily.</p>
|
||||
<p>Sowohl zum Schutz vor Servern, die Metadaten sammeln,
|
||||
als auch als Schutz bei Beschlagnahmung von Geräten
|
||||
empfehlen wir die Verwendung eines <a href="https://chatmail.at/relays">Chatmail-Relays</a>,
|
||||
um Chat-Profile mit zufälligen Adressen für den Transport zu erstellen.
|
||||
Beachte, dass Delta-Chat-Apps mehrere Profile unterstützen,
|
||||
sodass du neben deinem „Hauptprofil” ganz einfach situationsspezifische Profile verwenden kannst,
|
||||
mit der Gewissheit, dass alle Daten sowie alle Metadaten gelöscht werden.
|
||||
Darüber hinaus können Chat-Kontakte, die kurzlebige Profile verwenden,
|
||||
im Falle einer Beschlagnahmung des Geräts nicht ohne Weiteres identifiziert werden.</p>
|
||||
|
||||
<h3 id="wer-sieht-meine-ip-adresse">
|
||||
|
||||
@@ -1317,7 +1319,7 @@ can not be identified easily.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Das verwendete <a href="#relays">Rekay</a> muss Ihre IP-Adresse kennen,
|
||||
<p>Das verwendete <a href="#relays">Relay</a> muss Ihre IP-Adresse kennen,
|
||||
sowie manchmal auch die Geräte Ihrer Kontakte, wenn Sie einen <a href="#experiments">Anruf</a> tätigen
|
||||
oder gemeinsam <a href="#webxdc">Apps</a> verwenden.</p>
|
||||
|
||||
@@ -1350,11 +1352,10 @@ um seine Serverinfrastruktur darüber im Unklaren zu lassen, wer eine Nachricht
|
||||
Dies ist besonders wichtig, weil der Signal-Server die Handynummer jedes Kontos kennt,
|
||||
die in der Regel mit einer Passidentität verbunden ist.</p>
|
||||
|
||||
<p>Even if <a href="https://chatmail.at/relays">chatmail relays</a>
|
||||
do not ask for any private data (including no phone numbers),
|
||||
it might still be worthwhile to protect relational metadata between addresses.
|
||||
We don’t foresee bigger problems in using random throw-away addresses for sealed sending
|
||||
but an implementation has not been agreed as a priority yet.</p>
|
||||
<p>Auch wenn <a href="https://chatmail.at/relays">Chatmail-Relays</a>
|
||||
keine privaten Daten (einschließlich Telefonnummern) abfragen,
|
||||
könnte es dennoch sinnvoll sein, Metadaten zwischen Adressen zu schützen.
|
||||
Wir sehen keine größeren Probleme bei der Verwendung von zufälligen Wegwerfadressen für aber eine Umsetzung wurde noch nicht als priorisiert.</p>
|
||||
|
||||
<h3 id="pfs">
|
||||
|
||||
@@ -1364,23 +1365,20 @@ but an implementation has not been agreed as a priority yet.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Nein, noch nicht.</p>
|
||||
<p>Noch nicht, aber es kommt mit <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
|
||||
This means that if your private decryption key is leaked,
|
||||
and someone has collected your prior in-transit messages,
|
||||
they will be able to decrypt and read them using the leaked decryption key.
|
||||
Note that Forward Secrecy only increases security if you delete messages.
|
||||
Otherwise, someone obtaining your decryption keys
|
||||
is typically also able to get all your non-deleted messages
|
||||
and doesn’t even need to decrypt any previously collected messages.</p>
|
||||
<p>Delta Chat unterstützt derzeit keine Perfect Forward Secrecy (PFS).
|
||||
Das bedeutet, dass, wenn Ihr privater Schlüssel offengelegt wird
|
||||
und jemand Ihre früheren Nachrichten während der Übertragung gesammelt hat,
|
||||
diese mit dem offengelegten Schlüssel entschlüsselt und gelesen werden können.
|
||||
Beachten Sie, dass Forward Secrecy die Sicherheit nur erhöht, wenn du Nachrichten löschst.
|
||||
Andernfalls kann jemand, der deinen Schlüssel erhält,
|
||||
in der Regel auch alle deine nicht gelöschten Nachrichten abrufen
|
||||
und muss zuvor gesammelte Nachrichten nicht einmal entschlüsseln.</p>
|
||||
|
||||
<p>We designed a Forward Secrecy approach that withstood
|
||||
initial examination from some cryptographers and implementation experts
|
||||
but is pending a more formal write up
|
||||
to ascertain it reliably works in federated messaging and with multi-device usage,
|
||||
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
|
||||
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, dessen vollständige Implementierung für 2026 geplant ist,
|
||||
wird durch automatische Schlüsselrotation eine zuverlässige Löschung (Forward Secrecy) gewährleisten.
|
||||
Dieser Ansatz ist im Entwurf <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> festgelegt.</p>
|
||||
|
||||
<h3 id="pqc">
|
||||
|
||||
@@ -1390,11 +1388,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Nein, noch nicht.</p>
|
||||
<p>Noch nicht, aber es kommt mit <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat verwendet die Rust OpenPGP-Bibliothek <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
die den neuesten <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP Entwurf</a> unterstützt.
|
||||
Wir beabsichtigen, PQC-Unterstützung zum <a href="https://github.com/chatmail/core">chatmail core</a> hinzuzufügen, sobald der Entwurf bei der IETF in Zusammenarbeit mit anderen OpenPGP-Implementierern fertiggestellt ist.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, dessen vollständige Implementierung für 2026 geplant ist,
|
||||
wird eine post-quantum-resistente Verschlüsselung zum Schutz vor Angriffen durch Quantencomputer bieten.
|
||||
Delta Chat verwendet die Rust-OpenPGP-Bibliothek <a href="https://github.com/rpgp/rpgp">rPGP</a>,
|
||||
die den neuesten <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Kryptografie OpenPGP Entfurf</a> unterstützt.
|
||||
Die Implementierung ist in <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> festgelegt.</p>
|
||||
|
||||
<h3 id="wie-kann-ich-die-verschlüsselung-manuell-überprüfen">
|
||||
|
||||
@@ -1420,12 +1420,11 @@ ist die Verbindung sicher.</p>
|
||||
|
||||
<p>Nein.</p>
|
||||
|
||||
<p>Delta Chat generates secure OpenPGP keys according to the Autocrypt specification 1.1.
|
||||
We do not recommend or offer users to perform manual key management.
|
||||
We want to ensure that security audits can focus on a few proven cryptographic algorithms
|
||||
instead of the full breadth of possible algorithms allowed with OpenPGP.
|
||||
If you want to extract your OpenPGP key, there only is an expert method:
|
||||
you need to look it up in the “keypairs” SQLite table of a profile backup tar-file.</p>
|
||||
<p>Delta Chat generiert sichere OpenPGP-Schlüssel gemäß der Autocrypt-Spezifikation 1.1.
|
||||
Wir bieten Benutzern keine manuelle Schlüsselverwaltung an, noch empfehlen diese.
|
||||
Wir wollen sicherstellen, dass sich Sicherheitsüberprüfungen auf einige wenige bewährte kryptografische Algorithmen konzentrieren können,
|
||||
anstatt auf die gesamte Bandbreite der mit OpenPGP zulässigen Algorithmen.
|
||||
Wenn Sie Ihren OpenPGP-Schlüssel extrahieren möchten, gibt es nur eine Methode für Experten: Sie müssen ihn in der SQLite-Tabelle „keypairs” des Backups nachschlagen.</p>
|
||||
|
||||
<h3 id="security-audits">
|
||||
|
||||
@@ -1487,10 +1486,10 @@ Es wurden keine kritischen Probleme gefunden, aber zwei Probleme mit hohem Schwe
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Some features require certain permissions,
|
||||
e.g. you need to grant camera permission if you want to <a href="#howtoe2ee">scan an invite QR code</a>.</p>
|
||||
<p>Einige Features erfordern bestimmte Berechtigungen.
|
||||
So muss z.B. der Kamerazugriff gewährt werden, wenn du einen <a href="#howtoe2ee">QR-Code scannen</a> möchtest.</p>
|
||||
|
||||
<p>See <a href="https://delta.chat/en/gdpr#24-app-permissions">Privacy Policy</a> for a detailed overview.</p>
|
||||
<p>Siehe <a href="https://delta.chat/de/gdpr#24-berechtigungen-der-app">Datenschutzhinweise</a> für eine detaillierte Übersicht.</p>
|
||||
|
||||
<h3 id="wo-können-meine-freunde-delta-chat-finden">
|
||||
|
||||
@@ -1534,29 +1533,6 @@ Wir nutzen vielmehr öffentliche Finanzierungsquellen, die bisher aus der EU und
|
||||
<p>Konkret wurden die Delta-Chat-Entwicklungen bisher aus diesen Quellen finanziert:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Das EU-Projekt <a href="https://nextleap.eu">NEXTLEAP</a> finanzierte 2017 und 2018 die Entwicklung und Implementierung von “Verifizierten Gruppen” und “Setup Kontakt” und half auch bei der Integration der Ende-zu-Ende-Verschlüsselung durch <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Der <a href="https://opentechfund.org">Open Technology Fund</a> hat Delta Chat erstmals 2018/2019 bezuschusst; mit dieser Förderung (~$200K) wurden hauptsächlich die Android-App verbessert sowie das Release der Desktop-App in einer Betaversion ermöglicht. Basierend auf Nutzererfahrungen im Menschenrechtskontext wurden zudem verschiedene Funktionen entwickelt, siehe unseren Bericht <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
Die zweite Förderung 2019/2020 (~$300K) half uns bei der Erstellung der iOS-Version, unsere Kernbibliothek in die Programmiersprache “Rust” zu konvertieren und neue Funktionen für alle Plattformen bereitzustellen.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Die <a href="https://nlnet.nl/">NLnet-Stiftung</a> bewilligte 2019/2020 46K EUR für die Fertigstellung von Rust-/Python-Bindungs und die Einrichtung eines Chat-Bot-Ökosystems.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Im Jahr 2021 erhielten wir weitere EU-Mittel für zwei “Next-Generation-Internet”-Anträge, nämlich für <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - E-Mail-Provider-Portabilitätsverzeichnis</a> (~97K EUR) und <a href="https://nlnet.nl/project/EmailPorting/">AEAP - E-Mail-Adressportierung</a> (~90K EUR). Ziel sind bessere Unterstützung von Mehrfachkonten, verbesserten QR-Code-Kontakt- und -Gruppen-Setups sowie Netzwerkverbesserungen auf allen Plattformen.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Von Ende 2021 bis März 2023 erhielten wir eine <em>Internet-Freedom</em>-Finanzierung (500K USD) vom U.S. Bureau of Democracy, Human Rights and Labor (DRL). Diese Finanzierung unterstützte unsere langjährigen Ziele, Delta Chat benutzerfreundlicher und kompatibel mit einer breiten Palette von E-Mail-Servern weltweit zu machen, sowie widerstandsfähiger und sicherer an Orten, die häufig von Internetzensur und Abschaltungen betroffen sind.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>2023-2024 schlossen wir erfolgreich das vom OTF finanzierte
|
||||
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure-Chatmail-Projekt</a> ab.
|
||||
Dieses fügt “Garantierte Verschlüsselung”,
|
||||
das <a href="https://delta.chat/chatmail">Chatmail-Server-Netzwerk</a>
|
||||
und „Instant Onboarding“ allen ab April 2024 veröffentlichten Anwendungen hinzu.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>2023 und 2024 wurden wir in das Next-Generation-Internet-Programm (NGI)
|
||||
für unsere Arbeit an <a href="https://nlnet.nl/project/WebXDC-Push/">Webxdc-PUSH</a> aufgenommen,
|
||||
@@ -1567,6 +1543,19 @@ zusammen mit Kooperationspartnern, die an
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
Alle diese Projekte sind teilweise abgeschlossen oder sollen Anfang 2025 abgeschlossen werden.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Im Jahr 2021 erhielten wir weitere EU-Mittel für zwei “Next-Generation-Internet”-Anträge, nämlich für <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - E-Mail-Provider-Portabilitätsverzeichnis</a> (~97K EUR) und <a href="https://nlnet.nl/project/EmailPorting/">AEAP - E-Mail-Adressportierung</a> (~90K EUR). Ziel sind bessere Unterstützung von Mehrfachkonten, verbesserten QR-Code-Kontakt- und -Gruppen-Setups sowie Netzwerkverbesserungen auf allen Plattformen.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Die <a href="https://nlnet.nl/">NLnet-Stiftung</a> bewilligte 2019/2020 46K EUR für die Fertigstellung von Rust-/Python-Bindungs und die Einrichtung eines Chat-Bot-Ökosystems.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Der <a href="https://opentechfund.org">Open Technology Fund</a> hat Delta Chat erstmals 2018/2019 bezuschusst; mit dieser Förderung (~$200K) wurden hauptsächlich die Android-App verbessert sowie das Release der Desktop-App in einer Betaversion ermöglicht. Basierend auf Nutzererfahrungen im Menschenrechtskontext wurden zudem verschiedene Funktionen entwickelt, siehe unseren Bericht <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
Die zweite Förderung 2019/2020 (~$300K) half uns bei der Erstellung der iOS-Version, unsere Kernbibliothek in die Programmiersprache “Rust” zu konvertieren und neue Funktionen für alle Plattformen bereitzustellen.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Das EU-Projekt <a href="https://nextleap.eu">NEXTLEAP</a> finanzierte 2017 und 2018 die Entwicklung und Implementierung von “Verifizierten Gruppen” und “Setup Kontakt” und half auch bei der Integration der Ende-zu-Ende-Verschlüsselung durch <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Manchmal erhalten wir einmalige Spenden von Privatpersonen, wofür wir sehr dankbar sind. Im Jahr 2021 hat uns zum Beispiel eine großzügige Privatperson 4000 EUR überwiesen mit dem Betreff “Weiter so!” 💜 Wir verwenden dieses Geld zur Finanzierung von Entwicklungstreffen oder zur Deckung von Ad-hoc-Ausgaben, die nicht ohne weiteres vorhersehbar sind oder nicht aus öffentlichen Fördermitteln erstattet werden können.
|
||||
Der Erhalt von Spenden hilft uns auch, unabhängiger und langfristig lebensfähig zu werden, als Gemeinschaft.</p>
|
||||
|
||||
@@ -1188,6 +1188,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
|
||||
<li>
|
||||
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
|
||||
establishing end-to-end encryption between contacts and all members of a group chat.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption and forward secrecy.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
|
||||
@@ -1372,12 +1376,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
|
||||
<p>Servers can therefore only see:</p>
|
||||
|
||||
<ul>
|
||||
<li>the sender and receiver addresses</li>
|
||||
<li>and the message size.</li>
|
||||
<li>Sender and receiver addresses, randomly generated by default</li>
|
||||
<li>Message size</li>
|
||||
</ul>
|
||||
|
||||
<p>By default, the addresses are randomly generated.</p>
|
||||
|
||||
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
|
||||
|
||||
<h3 id="device-seizure">
|
||||
@@ -1453,7 +1455,7 @@ but an implementation has not been agreed as a priority yet.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
|
||||
This means that if your private decryption key is leaked,
|
||||
@@ -1464,12 +1466,9 @@ Otherwise, someone obtaining your decryption keys
|
||||
is typically also able to get all your non-deleted messages
|
||||
and doesn’t even need to decrypt any previously collected messages.</p>
|
||||
|
||||
<p>We designed a Forward Secrecy approach that withstood
|
||||
initial examination from some cryptographers and implementation experts
|
||||
but is pending a more formal write up
|
||||
to ascertain it reliably works in federated messaging and with multi-device usage,
|
||||
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
|
||||
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will provide reliable deletion (forward secrecy) through automatic key rotation.
|
||||
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="pqc">
|
||||
|
||||
@@ -1479,12 +1478,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
|
||||
in collaboration with other OpenPGP implementers.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption to protect against quantum computer attacks.
|
||||
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="how-can-i-manually-check-encryption-information">
|
||||
|
||||
@@ -1663,52 +1663,38 @@ ordered chronologically:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
|
||||
and implementation of verified groups and setup contact protocols
|
||||
in 2017 and 2018 and also helped to integrate end-to-end Encryption
|
||||
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
|
||||
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
|
||||
and released a first Desktop app beta version, and which moreover
|
||||
moored our feature developments in UX research in human rights contexts,
|
||||
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
The second 2019/2020 grant (~$300K) helped us to
|
||||
release Delta/iOS versions, to convert our core library to Rust, and
|
||||
to provide new features for all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2021 we received further EU funding for two Next-Generation-Internet
|
||||
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
|
||||
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
|
||||
This funding supported our long-running goals to make Delta Chat more usable
|
||||
and compatible with a wide range of email servers world-wide, and more resilient and secure
|
||||
in places often affected by internet censorship and shutdowns.</p>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>2023-2024 we successfully completed the OTF-funded
|
||||
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
|
||||
allowing us to introduce guaranteed encryption,
|
||||
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
|
||||
and providing “instant onboarding” in all apps released from April 2024 on.</p>
|
||||
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
|
||||
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
|
||||
and released a first Desktop app beta version, and which moreover
|
||||
moored our feature developments in UX research in human rights contexts,
|
||||
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
The second 2019/2020 grant (~$300K) helped us to
|
||||
release Delta/iOS versions, to convert our core library to Rust, and
|
||||
to provide new features for all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
|
||||
and implementation of verified groups and setup contact protocols
|
||||
in 2017 and 2018 and also helped to integrate end-to-end Encryption
|
||||
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Sometimes we receive one-time donations from private individuals.
|
||||
|
||||
@@ -1184,6 +1184,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
|
||||
<li>
|
||||
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
|
||||
establishing end-to-end encryption between contacts and all members of a group chat.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption and forward secrecy.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
|
||||
@@ -1365,12 +1369,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
|
||||
<p>Servers can therefore only see:</p>
|
||||
|
||||
<ul>
|
||||
<li>the sender and receiver addresses</li>
|
||||
<li>and the message size.</li>
|
||||
<li>Sender and receiver addresses, randomly generated by default</li>
|
||||
<li>Message size</li>
|
||||
</ul>
|
||||
|
||||
<p>By default, the addresses are randomly generated.</p>
|
||||
|
||||
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
|
||||
|
||||
<h3 id="device-seizure">
|
||||
@@ -1446,7 +1448,7 @@ but an implementation has not been agreed as a priority yet.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
|
||||
This means that if your private decryption key is leaked,
|
||||
@@ -1457,12 +1459,9 @@ Otherwise, someone obtaining your decryption keys
|
||||
is typically also able to get all your non-deleted messages
|
||||
and doesn’t even need to decrypt any previously collected messages.</p>
|
||||
|
||||
<p>We designed a Forward Secrecy approach that withstood
|
||||
initial examination from some cryptographers and implementation experts
|
||||
but is pending a more formal write up
|
||||
to ascertain it reliably works in federated messaging and with multi-device usage,
|
||||
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
|
||||
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will provide reliable deletion (forward secrecy) through automatic key rotation.
|
||||
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="pqc">
|
||||
|
||||
@@ -1472,12 +1471,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
|
||||
in collaboration with other OpenPGP implementers.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption to protect against quantum computer attacks.
|
||||
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="how-can-i-manually-check-encryption-information">
|
||||
|
||||
@@ -1652,52 +1652,38 @@ ordered chronologically:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>El proyecto de la UE <a href="https://nextleap.eu">NEXTLEAP</a> financió la investigación
|
||||
e implementación de grupos verificados y protocolos de contacto
|
||||
en 2017 y 2018 y también ayudó a integrar el cifrado de extremo a extremo
|
||||
a través de <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
|
||||
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
|
||||
and released a first Desktop app beta version, and which moreover
|
||||
moored our feature developments in UX research in human rights contexts,
|
||||
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
The second 2019/2020 grant (~$300K) helped us to
|
||||
release Delta/iOS versions, to convert our core library to Rust, and
|
||||
to provide new features for all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2021 we received further EU funding for two Next-Generation-Internet
|
||||
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
|
||||
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
|
||||
This funding supported our long-running goals to make Delta Chat more usable
|
||||
and compatible with a wide range of email servers world-wide, and more resilient and secure
|
||||
in places often affected by internet censorship and shutdowns.</p>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>2023-2024 we successfully completed the OTF-funded
|
||||
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
|
||||
allowing us to introduce guaranteed encryption,
|
||||
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
|
||||
and providing “instant onboarding” in all apps released from April 2024 on.</p>
|
||||
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
|
||||
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
|
||||
and released a first Desktop app beta version, and which moreover
|
||||
moored our feature developments in UX research in human rights contexts,
|
||||
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
The second 2019/2020 grant (~$300K) helped us to
|
||||
release Delta/iOS versions, to convert our core library to Rust, and
|
||||
to provide new features for all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
<p>El proyecto de la UE <a href="https://nextleap.eu">NEXTLEAP</a> financió la investigación
|
||||
e implementación de grupos verificados y protocolos de contacto
|
||||
en 2017 y 2018 y también ayudó a integrar el cifrado de extremo a extremo
|
||||
a través de <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Sometimes we receive one-time donations from private individuals.
|
||||
|
||||
@@ -1175,6 +1175,9 @@ to exchange encryption setup information through QR-code scanning or “invite l
|
||||
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
|
||||
establishing end-to-end encryption between contacts and all members of a group chat.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, qui est prévu pour 2026, amènera un chiffrement avec résistance post-quantique ainsi que la confidentialité persistante (“forward secrecy”).</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
|
||||
chat</a>
|
||||
@@ -1358,12 +1361,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
|
||||
<p>Servers can therefore only see:</p>
|
||||
|
||||
<ul>
|
||||
<li>the sender and receiver addresses</li>
|
||||
<li>and the message size.</li>
|
||||
<li>Sender and receiver addresses, randomly generated by default</li>
|
||||
<li>Message size</li>
|
||||
</ul>
|
||||
|
||||
<p>By default, the addresses are randomly generated.</p>
|
||||
|
||||
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
|
||||
|
||||
<h3 id="device-seizure">
|
||||
@@ -1418,7 +1419,7 @@ For example, tapping a link exposes IP Addresses to unknown parties and is the b
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Non, pas encore.</p>
|
||||
|
||||
<p>The Signal messenger introduced <a href="https://signal.org/blog/sealed-sender/">“Sealed Sender” in 2018</a>
|
||||
to keep their server infrastructure ignorant of who is sending a message to a set of recipients.
|
||||
@@ -1439,7 +1440,7 @@ but an implementation has not been agreed as a priority yet.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Pas encore mais cela arrive avec <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
|
||||
This means that if your private decryption key is leaked,
|
||||
@@ -1450,12 +1451,8 @@ Otherwise, someone obtaining your decryption keys
|
||||
is typically also able to get all your non-deleted messages
|
||||
and doesn’t even need to decrypt any previously collected messages.</p>
|
||||
|
||||
<p>We designed a Forward Secrecy approach that withstood
|
||||
initial examination from some cryptographers and implementation experts
|
||||
but is pending a more formal write up
|
||||
to ascertain it reliably works in federated messaging and with multi-device usage,
|
||||
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
|
||||
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, prévu pour 2026, permettra la suppression fiable (forward secrecy) grâce à une rotation automatique des clefs.
|
||||
Cette approche est détaillée dans le brouillon du <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">certificat Autocrypt v2 OpenPGP</a>.</p>
|
||||
|
||||
<h3 id="pqc">
|
||||
|
||||
@@ -1465,12 +1462,11 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Pas encore mais cela arrive avec <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
|
||||
in collaboration with other OpenPGP implementers.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, prévu pour 2026, amènera un chiffrement avec résistance post-quantique pour protéger contre les attaques effectuées par des ordinateurs quantiques.
|
||||
Delta Chat utilise la librairie Rust OpenPGP <a href="https://github.com/rpgp/rpgp">rPGP</a> qui supporte les dernières <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
L’implémentation est détaillée dans le brouillon du <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">certificat Autocrypt v2 OpenPGP</a>.</p>
|
||||
|
||||
<h3 id="how-can-i-manually-check-encryption-information">
|
||||
|
||||
@@ -1628,47 +1624,38 @@ ordered chronologically:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
|
||||
and implementation of verified groups and setup contact protocols
|
||||
in 2017 and 2018 and also helped to integrate end-to-end Encryption
|
||||
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>L’association <a href="https://opentechfund.org">Open Technology Fund</a> nous a octroyé une première subvention en 2018/2019 (de 200.000$ environ), grâce à laquelle nous avons pu apporter des améliorations majeures à l’application Android et publier une première version Beta de l’application de bureau. Elle nous a aussi permis d’ancrer notre recherche UX de développement de fonctionnalités dans des contextes de droits humains. <br />
|
||||
À ce sujet, vous pouvez consulter notre rapport en anglais <a href="https://delta.chat/en/2019-07-19-uxreport">“Needfinding and UX report”</a>.
|
||||
La seconde subvention de 2019/2020 (environ 300.000$) nous a permis de publier des version iOS de Delta Chat, de convertir notre bibliothèque principale en Rust et de créer de nouvelles fonctionnalités pour toutes les plateformes.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2021 we received further EU funding for two Next-Generation-Internet
|
||||
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
|
||||
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
|
||||
This funding supported our long-running goals to make Delta Chat more usable
|
||||
and compatible with a wide range of email servers world-wide, and more resilient and secure
|
||||
in places often affected by internet censorship and shutdowns.</p>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>2023-2024 we successfully completed the OTF-funded
|
||||
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
|
||||
allowing us to introduce guaranteed encryption,
|
||||
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
|
||||
and providing “instant onboarding” in all apps released from April 2024 on.</p>
|
||||
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
|
||||
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
|
||||
and released a first Desktop app beta version, and which moreover
|
||||
moored our feature developments in UX research in human rights contexts,
|
||||
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
The second 2019/2020 grant (~$300K) helped us to
|
||||
release Delta/iOS versions, to convert our core library to Rust, and
|
||||
to provide new features for all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
|
||||
and implementation of verified groups and setup contact protocols
|
||||
in 2017 and 2018 and also helped to integrate end-to-end Encryption
|
||||
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Nous recevons parfois des dons ponctuels de la part de personnes privées.
|
||||
|
||||
@@ -6,6 +6,10 @@ a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a[href^='http']::after {
|
||||
content: " ↗";
|
||||
}
|
||||
|
||||
h2, h3, h4 {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
@@ -1188,6 +1188,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
|
||||
<li>
|
||||
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
|
||||
establishing end-to-end encryption between contacts and all members of a group chat.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption and forward secrecy.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
|
||||
@@ -1372,12 +1376,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
|
||||
<p>Servers can therefore only see:</p>
|
||||
|
||||
<ul>
|
||||
<li>the sender and receiver addresses</li>
|
||||
<li>and the message size.</li>
|
||||
<li>Sender and receiver addresses, randomly generated by default</li>
|
||||
<li>Message size</li>
|
||||
</ul>
|
||||
|
||||
<p>By default, the addresses are randomly generated.</p>
|
||||
|
||||
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
|
||||
|
||||
<h3 id="device-seizure">
|
||||
@@ -1453,7 +1455,7 @@ but an implementation has not been agreed as a priority yet.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
|
||||
This means that if your private decryption key is leaked,
|
||||
@@ -1464,12 +1466,9 @@ Otherwise, someone obtaining your decryption keys
|
||||
is typically also able to get all your non-deleted messages
|
||||
and doesn’t even need to decrypt any previously collected messages.</p>
|
||||
|
||||
<p>We designed a Forward Secrecy approach that withstood
|
||||
initial examination from some cryptographers and implementation experts
|
||||
but is pending a more formal write up
|
||||
to ascertain it reliably works in federated messaging and with multi-device usage,
|
||||
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
|
||||
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will provide reliable deletion (forward secrecy) through automatic key rotation.
|
||||
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="pqc">
|
||||
|
||||
@@ -1479,12 +1478,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
|
||||
in collaboration with other OpenPGP implementers.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption to protect against quantum computer attacks.
|
||||
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="how-can-i-manually-check-encryption-information">
|
||||
|
||||
@@ -1663,52 +1663,38 @@ ordered chronologically:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
|
||||
and implementation of verified groups and setup contact protocols
|
||||
in 2017 and 2018 and also helped to integrate end-to-end Encryption
|
||||
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
|
||||
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
|
||||
and released a first Desktop app beta version, and which moreover
|
||||
moored our feature developments in UX research in human rights contexts,
|
||||
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
The second 2019/2020 grant (~$300K) helped us to
|
||||
release Delta/iOS versions, to convert our core library to Rust, and
|
||||
to provide new features for all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2021 we received further EU funding for two Next-Generation-Internet
|
||||
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
|
||||
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
|
||||
This funding supported our long-running goals to make Delta Chat more usable
|
||||
and compatible with a wide range of email servers world-wide, and more resilient and secure
|
||||
in places often affected by internet censorship and shutdowns.</p>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>2023-2024 we successfully completed the OTF-funded
|
||||
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
|
||||
allowing us to introduce guaranteed encryption,
|
||||
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
|
||||
and providing “instant onboarding” in all apps released from April 2024 on.</p>
|
||||
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
|
||||
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
|
||||
and released a first Desktop app beta version, and which moreover
|
||||
moored our feature developments in UX research in human rights contexts,
|
||||
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
The second 2019/2020 grant (~$300K) helped us to
|
||||
release Delta/iOS versions, to convert our core library to Rust, and
|
||||
to provide new features for all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
|
||||
and implementation of verified groups and setup contact protocols
|
||||
in 2017 and 2018 and also helped to integrate end-to-end Encryption
|
||||
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Terkadang kami menerima sumbangan satu kali dari perorangan.
|
||||
|
||||
@@ -1173,6 +1173,10 @@ per scambiare informazioni sulla configurazione della crittografia tramite la sc
|
||||
<li>
|
||||
<p><a href="https://autocrypt.org">Autocrypt</a> viene utilizzato per stabilire
|
||||
automaticamente la crittografia end-to-end tra i contatti e tutti i membri di una chat di gruppo.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, la cui piena implementazione è prevista per il 2026,
|
||||
introdurrà una crittografia post-quantistica resistente e una segretezza avanzata.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Condivisione di un contatto con una
|
||||
@@ -1436,7 +1440,7 @@ ma un’implementazione non è stata ancora concordata come priorità.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, non ancora.</p>
|
||||
<p>Non ancora, ma arriverà con <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat al momento non supporta la tecnologia Perfect Forward Secrecy (PFS).
|
||||
Ciò significa che se la tua chiave di decrittazione privata viene divulgata
|
||||
@@ -1447,12 +1451,9 @@ In caso contrario, chi ottiene le tue chiavi di decrittazione
|
||||
in genere è in grado di ottenere anche tutti i tuoi messaggi non eliminati
|
||||
e non ha nemmeno bisogno di decifrare i messaggi raccolti in precedenza.</p>
|
||||
|
||||
<p>Abbiamo progettato un approccio Forward Secrecy che ha superato
|
||||
l’esame iniziale di alcuni crittografi ed esperti di implementazione
|
||||
ma è in attesa di una stesura più formale
|
||||
per accertarne l’affidabilità nella messaggistica federata e nell’utilizzo su più dispositivi,
|
||||
prima di poter essere implementato in <a href="https://github.com/chatmail/core">chatmail core</a>,
|
||||
che lo renderebbe disponibile in tutti i <a href="https://chatmail.at/clients">clients di chatmail</a>.</p>
|
||||
<p><a href="https://autocrypt2.org">autocrypt v2</a>, la cui piena implementazione è prevista per il 2026,
|
||||
garantirà un’eliminazione affidabile (segretezza in avanti) tramite rotazione automatica delle chiavi.
|
||||
Questo approccio è specificato nella bozza dei <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">certificati OpenPGP di Autocrypt v2</a>.</p>
|
||||
|
||||
<h3 id="pqc">
|
||||
|
||||
@@ -1462,12 +1463,13 @@ che lo renderebbe disponibile in tutti i <a href="https://chatmail.at/clients">c
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, non ancora.</p>
|
||||
<p>Non ancora, ma arriverà con <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat utilizza la libreria Rust OpenPGP <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, la cui piena implementazione è prevista per il 2026,
|
||||
offrirà una crittografia post-quantistica resistente per proteggere dagli attacchi ai computer quantistici.
|
||||
Delta Chat utilizza la libreria Rust OpenPGP <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
che supporta l’ultima <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">bozza IETF Post-Quantum-Cryptography OpenPGP</a>.
|
||||
Il nostro obiettivo è aggiungere il supporto PQC nel <a href="https://github.com/chatmail/core">core di chatmail</a> dopo che la bozza sarà stata finalizzata dall’IETF
|
||||
in collaborazione con altri implementatori di OpenPGP.</p>
|
||||
L’implementazione è specificata nella bozza dei <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Certificati OpenPGP Autocrypt v2</a>.</p>
|
||||
|
||||
<h3 id="come-posso-controllare-manualmente-le-informazioni-di-crittografia">
|
||||
|
||||
@@ -1647,10 +1649,21 @@ ordinate cronologicamente:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Il progetto UE <a href="https://nextleap.eu">NEXTLEAP</a> ha finanziato la ricerca
|
||||
e implementazione di gruppi verificati e impostazione di protocolli di contatto
|
||||
nel 2017 e nel 2018 e ha anche contribuito a integrare la crittografia end-to-end
|
||||
tramite <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
<p>Nel 2023 e nel 2024 siamo stati accettati nel programma Next Generation Internet (NGI)
|
||||
per il nostro lavoro in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
insieme ai partner di collaborazione che lavorano su
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> e
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
Tutti questi progetti sono parzialmente completati o saranno completati all’inizio del 2025.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Nel 2021 abbiamo ricevuto ulteriori finanziamenti dall’UE per due proposte di Next-Generation-Internet, ovvero per <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - directory di portabilità dei provider di posta elettronica</a> (~97.000 EUR) e <a href="https://nlnet.nl/project/EmailPorting/">AEAP - portabilità degli indirizzi email</a> (~90.000 EUR), che hanno portato a un migliore supporto multi-profilo, a un miglioramento delle impostazioni di contatto e di gruppo tramite codice QR e a numerosi miglioramenti di rete su tutte le piattaforme.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>La <a href="https://nlnet.nl/">fondazione NLnet</a> ha concesso nel 2019/2020 46.000 EUR per
|
||||
completando i collegamenti Rust/Python e avviando un ecosistema Chat-bot.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>L’<a href="https://opentechfund.org">Open Technology Fund</a> ci ha dato una
|
||||
@@ -1663,33 +1676,10 @@ rilasciare nelle versioni Delta/iOS, per convertire la nostra libreria principal
|
||||
per fornire nuove funzionalità per tutte le piattaforme.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>La <a href="https://nlnet.nl/">fondazione NLnet</a> ha concesso nel 2019/2020 46.000 EUR per
|
||||
completando i collegamenti Rust/Python e avviando un ecosistema Chat-bot.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Nel 2021 abbiamo ricevuto ulteriori finanziamenti dall’UE per due proposte di Next-Generation-Internet, ovvero per <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - directory di portabilità dei provider di posta elettronica</a> (~97.000 EUR) e <a href="https://nlnet.nl/project/EmailPorting/">AEAP - portabilità degli indirizzi email</a> (~90.000 EUR), che hanno portato a un migliore supporto multi-profilo, a un miglioramento delle impostazioni di contatto e di gruppo tramite codice QR e a numerosi miglioramenti di rete su tutte le piattaforme.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Da fine 2021 a marzo 2023 abbiamo ricevuto un finanziamento <em>Internet Freedom</em> (500.000 USD) dall’Ufficio per la Democrazia, i Diritti Umani e il Lavoro (DRL) degli Stati Uniti.
|
||||
Questo finanziamento ha supportato i nostri obiettivi a lungo termine: rendere Delta Chat più utilizzabile
|
||||
e compatibile con un’ampia gamma di server email in tutto il mondo, e più resiliente e sicura
|
||||
in luoghi spesso colpiti da censura e blocchi di Internet.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>2023-2024 abbiamo completato con successo il progetto <a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Chatmail Sicuro</a> finanziato da OTF,
|
||||
consentendoci di introdurre la crittografia garantita,
|
||||
creando una <a href="https://delta.chat/chatmail">rete di server di chatmail</a>
|
||||
e fornendo “inserimento immediato” in tutte le app rilasciate da aprile 2024 in poi.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Nel 2023 e nel 2024 siamo stati accettati nel programma Next Generation Internet (NGI)
|
||||
per il nostro lavoro in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
insieme ai partner di collaborazione che lavorano su
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> e
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
Tutti questi progetti sono parzialmente completati o saranno completati all’inizio del 2025.</p>
|
||||
<p>Il progetto UE <a href="https://nextleap.eu">NEXTLEAP</a> ha finanziato la ricerca
|
||||
e implementazione di gruppi verificati e impostazione di protocolli di contatto
|
||||
nel 2017 e nel 2018 e ha anche contribuito a integrare la crittografia end-to-end
|
||||
tramite <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>A volte riceviamo donazioni una tantum da privati.
|
||||
|
||||
@@ -1182,6 +1182,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
|
||||
<li>
|
||||
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
|
||||
establishing end-to-end encryption between contacts and all members of a group chat.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption and forward secrecy.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
|
||||
@@ -1366,12 +1370,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
|
||||
<p>Servers can therefore only see:</p>
|
||||
|
||||
<ul>
|
||||
<li>the sender and receiver addresses</li>
|
||||
<li>and the message size.</li>
|
||||
<li>Sender and receiver addresses, randomly generated by default</li>
|
||||
<li>Message size</li>
|
||||
</ul>
|
||||
|
||||
<p>By default, the addresses are randomly generated.</p>
|
||||
|
||||
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
|
||||
|
||||
<h3 id="device-seizure">
|
||||
@@ -1447,7 +1449,7 @@ but an implementation has not been agreed as a priority yet.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
|
||||
This means that if your private decryption key is leaked,
|
||||
@@ -1458,12 +1460,9 @@ Otherwise, someone obtaining your decryption keys
|
||||
is typically also able to get all your non-deleted messages
|
||||
and doesn’t even need to decrypt any previously collected messages.</p>
|
||||
|
||||
<p>We designed a Forward Secrecy approach that withstood
|
||||
initial examination from some cryptographers and implementation experts
|
||||
but is pending a more formal write up
|
||||
to ascertain it reliably works in federated messaging and with multi-device usage,
|
||||
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
|
||||
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will provide reliable deletion (forward secrecy) through automatic key rotation.
|
||||
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="pqc">
|
||||
|
||||
@@ -1473,12 +1472,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
|
||||
in collaboration with other OpenPGP implementers.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption to protect against quantum computer attacks.
|
||||
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="how-can-i-manually-check-encryption-information">
|
||||
|
||||
@@ -1657,52 +1657,38 @@ ordered chronologically:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
|
||||
and implementation of verified groups and setup contact protocols
|
||||
in 2017 and 2018 and also helped to integrate end-to-end Encryption
|
||||
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://opentechfund.org">Open Technology Fund</a> heeft twee subsidies toegekend.
|
||||
De eerste subsidie, voor 2018/2019, ter waarde van ong. $200,000, heeft enorm geholpen om de Android-app
|
||||
te verbeteren en een bètaversie van de computerclient vrij te geven.
|
||||
Verder hebben we onderzoek kunnen doen naar het uiterlijk in relatie tot mensenrechten -
|
||||
bekijk onze conclusie hier: <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
De tweede subsidie, voor 2019/2020, ter waarde van ong. $300,000, loopt nog en ondersteunt ons bij het
|
||||
vrijgeven van de iOS-client, het overzetten van de code van de kernbibliotheek naar Rust en
|
||||
het implementeren van nieuwe functies op alle platformen.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2021 we received further EU funding for two Next-Generation-Internet
|
||||
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
|
||||
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
|
||||
This funding supported our long-running goals to make Delta Chat more usable
|
||||
and compatible with a wide range of email servers world-wide, and more resilient and secure
|
||||
in places often affected by internet censorship and shutdowns.</p>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>2023-2024 we successfully completed the OTF-funded
|
||||
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
|
||||
allowing us to introduce guaranteed encryption,
|
||||
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
|
||||
and providing “instant onboarding” in all apps released from April 2024 on.</p>
|
||||
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
|
||||
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
|
||||
and released a first Desktop app beta version, and which moreover
|
||||
moored our feature developments in UX research in human rights contexts,
|
||||
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
The second 2019/2020 grant (~$300K) helped us to
|
||||
release Delta/iOS versions, to convert our core library to Rust, and
|
||||
to provide new features for all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
|
||||
and implementation of verified groups and setup contact protocols
|
||||
in 2017 and 2018 and also helped to integrate end-to-end Encryption
|
||||
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Soms ontvangen we eenmalige donaties van privépersonen, waar we
|
||||
|
||||
@@ -105,8 +105,7 @@
|
||||
|
||||
</h2>
|
||||
|
||||
<p>Delta Chat is a reliable, decentralized and secure instant messaging app,
|
||||
available for mobile and desktop platforms.</p>
|
||||
<p>Delta Chat to niezawodna, zdecentralizowana i bezpieczna aplikacja do błyskawicznego przesyłania wiadomości, dostępna na platformy mobilne i stacjonarne.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
@@ -1103,6 +1102,10 @@ weekly statistics will be automatically sent to a bot.</p>
|
||||
<li>
|
||||
<p><a href="https://autocrypt.org">Autocrypt</a> służy do automatycznego ustanawiania szyfrowania typu end-to-end między kontaktami a wszystkimi członkami czatu grupowego.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption and forward secrecy.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Udostępnienie kontaktu na czacie</a> umożliwia odbiorcom korzystanie z szyfrowania typu end-to-end z tym kontaktem.</p>
|
||||
</li>
|
||||
@@ -1237,12 +1240,10 @@ even if the message was not end-to-end encrypted.</p>
|
||||
<p>Servers can therefore only see:</p>
|
||||
|
||||
<ul>
|
||||
<li>the sender and receiver addresses</li>
|
||||
<li>and the message size.</li>
|
||||
<li>Sender and receiver addresses, randomly generated by default</li>
|
||||
<li>Message size</li>
|
||||
</ul>
|
||||
|
||||
<p>By default, the addresses are randomly generated.</p>
|
||||
|
||||
<p>Wszystkie pozostałe metadane dotyczące wiadomości, kontaktów i grup znajdują się w zaszyfrowanej metodą end-to-end części wiadomości.</p>
|
||||
|
||||
<h3 id="device-seizure">
|
||||
@@ -1315,11 +1316,13 @@ but an implementation has not been agreed as a priority yet.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Nie, jeszcze nie.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat obecnie nie obsługuje mechanizmu Perfect Forward Secrecy (PFS). Oznacza to, że jeśli twój prywatny klucz deszyfrujący zostanie ujawniony, a ktoś zdobędzie twoje wcześniejsze wiadomości w trakcie transmisji, będzie mógł je odszyfrować i odczytać za pomocą ujawnionego klucza deszyfrującego. Należy pamiętać, że mechanizm Forward Secrecy zwiększa bezpieczeństwo tylko w przypadku usuwania wiadomości. W przeciwnym razie osoba, która uzyska twoje klucze deszyfrujące, zazwyczaj będzie mogła uzyskać dostęp do wszystkich nieusuniętych wiadomości i nie będzie musiała odszyfrowywać żadnych wcześniej zebranych wiadomości.</p>
|
||||
|
||||
<p>Opracowaliśmy metodę Forward Secrecy, która przeszła wstępną analizę niektórych kryptografów i ekspertów ds. wdrożeń, ale oczekuje na bardziej formalne opracowanie, które potwierdzi jej niezawodne działanie w federacyjnym przesyłaniu wiadomości i w przypadku korzystania z wielu urządzeń, zanim zostanie zaimplementowana w <a href="https://github.com/chatmail/core">rdzeniu chatmail</a>, co uczyniłoby ją dostępną we wszystkich <a href="https://chatmail.at/clients">klientach chatmail</a>.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will provide reliable deletion (forward secrecy) through automatic key rotation.
|
||||
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="pqc">
|
||||
|
||||
@@ -1329,9 +1332,13 @@ but an implementation has not been agreed as a priority yet.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Nie, jeszcze nie.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat korzysta z biblioteki Rust OpenPGP <a href="https://github.com/rpgp/rpgp">rPGP</a>, która obsługuje najnowszy <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">projekt OpenPGP IETF Post-Quantum-Cryptography</a>. Planujemy dodać obsługę PQC do <a href="https://github.com/chatmail/core">rdzenia chatmail</a> po sfinalizowaniu projektu w IETF we współpracy z innymi implementatorami OpenPGP.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption to protect against quantum computer attacks.
|
||||
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="jak-mogę-ręcznie-sprawdzić-informacje-o-szyfrowaniu">
|
||||
|
||||
@@ -1464,31 +1471,20 @@ Raczej korzystamy z publicznych źródeł finansowania, jak dotąd pochodzących
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Unijny projekt <a href="https://nextleap.eu">NEXTLEAP</a> sfinansował badania i wdrożenie zweryfikowanych grup i ustawień protokołów kontaktowych w latach 2017 i 2018, a także pomógł zintegrować szyfrowanie end-to-end poprzez <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
<p>W latach 2023 i 2024 zostaliśmy przyjęci do programu Next Generation Internet (NGI) za naszą pracę w <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>, wraz z partnerami współpracującymi pracującymi nad <a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>, <a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>, <a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> i <a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>. Wszystkie te projekty są częściowo ukończone lub zostaną ukończone na początku 2025 r.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>W 2021 r. otrzymaliśmy kolejne dofinansowanie z UE na dwie propozycje dotyczące Internetu nowej generacji, a mianowicie na <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD – katalog przenośności dostawcy poczty e-mail</a> ( ~97 tys. EUR) i <a href="https://nlnet.nl/project/EmailPorting/">AEAP – przenoszenie adresu e-mail</a> (~90 tys. EUR), co zaowocowało lepszą obsługą wielu kont, ulepszonymi kontaktami i ustawieniami grup za pomocą kodów QR oraz wieloma ulepszeniami sieciowymi na wszystkich platformach.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://nlnet.nl/">Fundacja NLnet</a> przekazała w latach 2019/2020 kwotę 46 tys. EUR na wykonanie wiązań Rust/Python i uruchomienie ekosystemu Chat-bot.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://opentechfund.org">Open Technology Fund</a> przyznał nam pierwszy grant w 2018/2019 (~200 000 $), dzięki któremu znacznie ulepszyliśmy aplikację na Androida i wydaliśmy pierwszą wersję beta aplikacji na komputery stacjonarne, a także ugruntował rozwój naszych funkcji w badaniach UX w kontekście praw człowieka, zobacz nasz końcowy raport <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX</a>.
|
||||
Druga dotacja w 2019/2020 (~300 000 4) pomogła nam wydać wersje Delta/iOS, przekonwertować naszą podstawową bibliotekę na Rust i zapewnić nowe funkcje dla wszystkich platform.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://nlnet.nl/">Fundacja NLnet</a> przekazała w latach 2019/2020 kwotę 46 tys. EUR na wykonanie wiązań Rust/Python i uruchomienie ekosystemu Chat-bot.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2021 we received further EU funding for two Next-Generation-Internet
|
||||
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
|
||||
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
|
||||
This funding supported our long-running goals to make Delta Chat more usable
|
||||
and compatible with a wide range of email servers world-wide, and more resilient and secure
|
||||
in places often affected by internet censorship and shutdowns.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>W latach 2023-2024 pomyślnie ukończyliśmy finansowany przez OTF <a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">projekt Secure Chatmail</a>, co pozwoliło nam wprowadzić gwarantowane szyfrowanie, stworzyć <a href="https://delta.chat/chatmail">sieć serwerów chatmail</a> i zapewnić „natychmiastowe wdrażanie” we wszystkich aplikacjach wydanych od kwietnia 2024 r.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>W latach 2023 i 2024 zostaliśmy przyjęci do programu Next Generation Internet (NGI) za naszą pracę w <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>, wraz z partnerami współpracującymi pracującymi nad <a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>, <a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>, <a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> i <a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>. Wszystkie te projekty są częściowo ukończone lub zostaną ukończone na początku 2025 r.</p>
|
||||
<p>Unijny projekt <a href="https://nextleap.eu">NEXTLEAP</a> sfinansował badania i wdrożenie zweryfikowanych grup i ustawień protokołów kontaktowych w latach 2017 i 2018, a także pomógł zintegrować szyfrowanie end-to-end poprzez <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Czasami otrzymujemy jednorazowe darowizny od osób prywatnych. Na przykład w 2021 roku pewna hojna osoba przekazała nam 4K EUR w formie przelewu bankowego tytułem “kontynuujcie dobry rozwój!”. 💜 Takie pieniądze przeznaczamy na finansowanie spotkań rozwojowych lub na doraźne wydatki, których nie da się łatwo przewidzieć lub zrefundować z publicznych dotacji. Otrzymywanie większej ilości darowizn pomaga nam również stać się bardziej niezależnymi i długoterminowo rentownymi jako społeczność współpracowników.</p>
|
||||
|
||||
+253
-268
@@ -2,36 +2,36 @@
|
||||
<html lang="pt"><head><meta charset="UTF-8" /><meta name="viewport" content="initial-scale=1.0" /><link rel="stylesheet" href="../help.css" /></head><body><ul id="top">
|
||||
<li><a href="#o-que-é-o-delta-chat">O que é o Delta Chat</a>
|
||||
<ul>
|
||||
<li><a href="#howtoe2ee">How can I find people to chat with?</a></li>
|
||||
<li><a href="#why-is-a-chat-marked-as-request">Why is a chat marked as “Request”?</a></li>
|
||||
<li><a href="#how-can-i-put-two-of-my-friends-in-contact-with-each-other">How can I put two of my friends in contact with each other?</a></li>
|
||||
<li><a href="#howtoe2ee">Como posso encontrar pessoas para conversar?</a></li>
|
||||
<li><a href="#por-que-um-chat-está-marcado-como-solicitação">Por que um chat está marcado como “Solicitação”?</a></li>
|
||||
<li><a href="#como-posso-colocar-dois-dos-meus-amigos-em-contato-um-com-o-outro">Como posso colocar dois dos meus amigos em contato um com o outro?</a></li>
|
||||
<li><a href="#dá-para-mandar-imagens-vídeos-e-outros-anexos-pelo-delta-chat">Dá para mandar imagens, vídeos e outros anexos pelo Delta Chat?</a></li>
|
||||
<li><a href="#multiple-accounts">What are profiles? How can I switch between them?</a></li>
|
||||
<li><a href="#multiple-accounts">O que são perfis? Como posso alternar entre eles?</a></li>
|
||||
<li><a href="#quem-consegue-ver-a-imagem-do-meu-perfil">Quem consegue ver a imagem do meu perfil?</a></li>
|
||||
<li><a href="#signature">Can I set a Bio/Status with Delta Chat?</a></li>
|
||||
<li><a href="#what-do-pinning-muting-and-archiving-mean">What do Pinning, Muting and Archiving mean?</a></li>
|
||||
<li><a href="#save">How do “Saved Messages” work?</a></li>
|
||||
<li><a href="#what-does-the-green-dot-mean">What does the green dot mean?</a></li>
|
||||
<li><a href="#signature">Posso definir uma Bio/Assinatura com o Delta Chat?</a></li>
|
||||
<li><a href="#o-que-significa-fixar-silenciar-e-arquivar">O que significa Fixar, Silenciar e Arquivar?</a></li>
|
||||
<li><a href="#save">Como funcionam as “Mensagens salvas”?</a></li>
|
||||
<li><a href="#o-que-significa-o-ponto-verde">O que significa o ponto verde?</a></li>
|
||||
<li><a href="#o-que-significam-os-carrapatos-mostrados-ao-lado-das-mensagens-de-saída">O que significam os carrapatos mostrados ao lado das mensagens de saída?</a></li>
|
||||
<li><a href="#edit">Correct typos and delete messages after sending</a></li>
|
||||
<li><a href="#ephemeralmsgs">How do disappearing messages work?</a></li>
|
||||
<li><a href="#delold">What happens if I turn on “Delete Messages from Device”?</a></li>
|
||||
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
|
||||
<li><a href="#edit">Corrigir erros de digitação e excluir mensagens após o envio</a></li>
|
||||
<li><a href="#ephemeralmsgs">Como funcionam as mensagens efêmeras?</a></li>
|
||||
<li><a href="#delold">O que acontece se eu ativar a opção “Apagar mensagens do dispositivo”?</a></li>
|
||||
<li><a href="#remove-account">Como posso excluir minha conta?</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#groups">Groups</a>
|
||||
<li><a href="#groups">Grupos</a>
|
||||
<ul>
|
||||
<li><a href="#criação-de-um-grupo">Criação de um grupo</a></li>
|
||||
<li><a href="#addmembers">Add and remove members</a></li>
|
||||
<li><a href="#addmembers">Adicionar e remover membros</a></li>
|
||||
<li><a href="#deletei-minha-própria-conta-por-acidente">Deletei minha própria conta por acidente.</a></li>
|
||||
<li><a href="#não-quero-mais-receber-as-mensagens-de-um-grupo">Não quero mais receber as mensagens de um grupo.</a></li>
|
||||
<li><a href="#cloning-a-group">Cloning a group</a></li>
|
||||
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
|
||||
<li><a href="#clonagem-de-grupos">Clonagem de grupos</a></li>
|
||||
<li><a href="#quantos-membros-podem-participar-em-um-único-grupo">Quantos membros podem participar em um único grupo?</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#webxdc">In-chat apps</a>
|
||||
<li><a href="#webxdc">Aplicativos embutidos</a>
|
||||
<ul>
|
||||
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
|
||||
<li><a href="#onde-posso-obter-aplicativos-embutidos">Onde posso obter aplicativos embutidos?</a></li>
|
||||
<li><a href="#how-private-are-in-chat-apps">How private are in-chat apps?</a></li>
|
||||
<li><a href="#create-xdc">How can I create my own in-chat apps?</a></li>
|
||||
</ul>
|
||||
@@ -105,118 +105,118 @@
|
||||
|
||||
</h2>
|
||||
|
||||
<p>Delta Chat is a reliable, decentralized and secure instant messaging app,
|
||||
available for mobile and desktop platforms.</p>
|
||||
<p>Delta Chat é um aplicativo de mensagens instantâneas confiável, descentralizado e seguro,
|
||||
disponível para desktop e plataformas móveis.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Instant creation of <strong>private chat profiles</strong>
|
||||
with secure and interoperable <a href="https://chatmail.at/relays">chatmail relays</a>
|
||||
that offer instant message delivery, and Push Notifications for iOS and Android devices.</p>
|
||||
<p>Criação instantânea de **perfis de chat privados **
|
||||
com <a href="https://chatmail.at/relays">relays chatmail</a> seguros e interoperáveis
|
||||
que oferecem entrega de mensagens instantâneas e Notificações Push para dispositivos iOS e Android.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Pervasive <a href="#multiple-accounts">multi-profile</a> and
|
||||
<a href="#multiclient">multi-device</a> support on all platforms
|
||||
and between different <a href="https://chatmail.at/clients">chatmail apps</a>.</p>
|
||||
<p>Suporte a <a href="#multiple-accounts">vários perfis</a> e
|
||||
a <a href="#multiclient">vários dispositivos</a> em todas as plataformas
|
||||
e entre diferentes <a href="https://chatmail.at/clients">aplicativos de chatmail</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Interactive <a href="#webxdc">in-chat apps</a> for gaming and collaboration</p>
|
||||
<p><a href="#webxdc">Aplicativos interativos embutidos</a> para jogos e colaboração</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="#security-audits">Audited end-to-end encryption</a>
|
||||
safe against network and server attacks.</p>
|
||||
<p><a href="#security-audits">Criptografia de ponta a ponta auditada</a>
|
||||
segura contra ataques à rede e ao servidor.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Free and Open Source software, both app and server side,
|
||||
built on <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Internet Standards</a>.</p>
|
||||
<p>Software gratuito e de código aberto, tanto no aplicativo quanto no servidor,
|
||||
desenvolvido com base nos <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Padrões da Internet</a>.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="howtoe2ee">
|
||||
|
||||
|
||||
How can I find people to chat with? <a href="#howtoe2ee" class="anchor"></a>
|
||||
Como posso encontrar pessoas para conversar? <a href="#howtoe2ee" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>First, note that Delta Chat is a private messenger.
|
||||
There is no public discovery, <em>you</em> decide about your contacts.</p>
|
||||
<p>Primeiro, observe que o Delta Chat é um mensageiro privado.
|
||||
Não há descoberta pública, <em>você</em> decide sobre seus contatos.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>If you are <strong>face to face</strong> with your friend or family,
|
||||
tap the <strong>QR Code</strong> icon <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" />
|
||||
on the main screen.<br />
|
||||
Ask your chat partner to <strong>scan</strong> the QR image
|
||||
with their Delta Chat app.</p>
|
||||
<p>Se você estiver <strong>cara a cara</strong> com seu amigo ou parente,
|
||||
toque no ícone de <strong>QR Code</strong> <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" />
|
||||
na tela principal.<br />
|
||||
Peça ao seu parceiro para <strong>escanear</strong> o QR code
|
||||
com o aplicativo Delta Chat dele.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>For a <strong>remote</strong> contact setup,
|
||||
from the same screen,
|
||||
click “Copy” or “Share” and send the <strong>invite link</strong>
|
||||
through another private chat.</p>
|
||||
<p>Para criar um contato <strong>remotamente</strong>,
|
||||
na mesma tela,
|
||||
clique em “Copiar” ou “Compartilhar” e envie o <strong>link do convite</strong>
|
||||
por meio de outro mensageiro privado.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Now wait while connection gets established.</p>
|
||||
<p>Agora, aguarde enquanto a conexão se estabelece.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>If both sides are online, they will soon see a chat
|
||||
and can start messaging securely.</p>
|
||||
<p>Se ambos os lados estiverem on-line, vocês logo verão um chat
|
||||
e poderão começar a enviar mensagens com segurança.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If one side is offline or in bad network,
|
||||
the ability to chat is delayed until connectivity is restored.</p>
|
||||
<p>Se um lado estiver off-line ou em uma rede ruim,
|
||||
a capacidade de acessar o chat será atrasada até que a conectividade seja restaurada.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Congratulations!
|
||||
You now will automatically use <a href="#e2ee">end-to-end encryption</a> with this contact.
|
||||
If you add each other to <a href="#groups">groups</a>, end-to-end encryption will be established among all members.</p>
|
||||
<p>Parabéns!
|
||||
Agora você usará automaticamente a <a href="#e2ee">criptografia de ponta a ponta</a> com esse contato.
|
||||
Se vocês se adicionarem a <a href="#groups">grupos</a>, a criptografia de ponta a ponta será estabelecida entre todos os membros.</p>
|
||||
|
||||
<h3 id="why-is-a-chat-marked-as-request">
|
||||
<h3 id="por-que-um-chat-está-marcado-como-solicitação">
|
||||
|
||||
|
||||
Why is a chat marked as “Request”? <a href="#why-is-a-chat-marked-as-request" class="anchor"></a>
|
||||
Por que um chat está marcado como “Solicitação”? <a href="#por-que-um-chat-está-marcado-como-solicitação" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>As being a private messenger,
|
||||
only friends and family you <a href="#howtoe2ee">share your QR code or invite link with</a> can write to you.</p>
|
||||
<p>Por ser um mensageiro privado,
|
||||
somente amigos e familiares com quem você <a href="#howtoe2ee">compartilha seu QR code ou link de convite</a> podem escrever para você.</p>
|
||||
|
||||
<p>Your friends may share your contact with other friends,
|
||||
this appears as <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Request</b></p>
|
||||
<p>Seus amigos podem compartilhar seu contato com outros amigos,
|
||||
isso aparece como <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Solicitação</b></p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>You need to <strong>accept</strong> the request before you can reply.</p>
|
||||
<p>Você precisa <strong>aceitar</strong> a solicitação antes de poder responder.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>You can also <strong>delete</strong> it if you don’t want to chat with them for now.</p>
|
||||
<p>Você também pode <strong>excluir</strong> a solicitação se não quiser conversar com a pessoa por enquanto.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If you delete a request, future messages from that contact will still appear
|
||||
as message request, so you can change your mind. If you really don’t want to
|
||||
receive messages from this person, consider <strong>blocking</strong> them.</p>
|
||||
<p>Se você excluir uma solicitação, as mensagens futuras desse contato ainda aparecerão
|
||||
como solicitação de mensagem, assim você pode mudar de ideia. Se você realmente não quiser
|
||||
receber mensagens dessa pessoa, considere <strong>bloqueá-la</strong>.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="how-can-i-put-two-of-my-friends-in-contact-with-each-other">
|
||||
<h3 id="como-posso-colocar-dois-dos-meus-amigos-em-contato-um-com-o-outro">
|
||||
|
||||
|
||||
How can I put two of my friends in contact with each other? <a href="#how-can-i-put-two-of-my-friends-in-contact-with-each-other" class="anchor"></a>
|
||||
Como posso colocar dois dos meus amigos em contato um com o outro? <a href="#como-posso-colocar-dois-dos-meus-amigos-em-contato-um-com-o-outro" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Attach the first contact to the chat of the second using <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment Button → Contact</strong>.
|
||||
You can also add a little introduction message.</p>
|
||||
<p>Anexe o primeiro contato ao chat do segundo clicando em <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Botão de Anexo → Contato</strong>.
|
||||
Você também pode adicionar uma pequena mensagem de apresentação.</p>
|
||||
|
||||
<p>The second contact will receive a <strong>card</strong> then
|
||||
and can tap it to start chatting with the first contact.</p>
|
||||
<p>O segundo contato receberá um <strong>cartão</strong>
|
||||
e poderá tocar nele para começar a conversar com o primeiro contato.</p>
|
||||
|
||||
<h3 id="dá-para-mandar-imagens-vídeos-e-outros-anexos-pelo-delta-chat">
|
||||
|
||||
@@ -226,31 +226,31 @@ and can tap it to start chatting with the first contact.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Sim. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
|
||||
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
|
||||
<p>Sim. Imagens, vídeos, arquivos, mensagens de voz etc. podem ser enviados usando os botões de<img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Anexo-</strong>
|
||||
ou <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Mensagem de Voz</strong></p>
|
||||
|
||||
<p>For performance, images are optimized and sent at a smaller size by default, but you can send it as a “file” to preserve the original.</p>
|
||||
<p>Para fins de desempenho, as imagens são otimizadas e enviadas em um tamanho menor por padrão, mas você pode enviá-las como um “arquivo” para preservar o original.</p>
|
||||
|
||||
<h3 id="multiple-accounts">
|
||||
|
||||
|
||||
What are profiles? How can I switch between them? <a href="#multiple-accounts" class="anchor"></a>
|
||||
O que são perfis? Como posso alternar entre eles? <a href="#multiple-accounts" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>A profile is <strong>a name, a picture</strong> and some additional information for encrypting messages.
|
||||
A profile lives on your device(s) only
|
||||
and uses the server only to relay messages.</p>
|
||||
<p>Um perfil é <strong>um nome, uma foto</strong> e algumas informações adicionais para criptografar mensagens.
|
||||
Um perfil reside apenas em seu(s) dispositivo(s)
|
||||
e usa o servidor somente para transportar mensagens.</p>
|
||||
|
||||
<p>On first installation of Delta Chat a first profile is created.</p>
|
||||
<p>Na primeira instalação do Delta Chat, é criado um primeiro perfil.</p>
|
||||
|
||||
<p>Later, you can tap your profile image in the upper left corner to <strong>Add Profiles</strong>
|
||||
or to <strong>Switch Profiles</strong>.</p>
|
||||
<p>Depois, você pode tocar em sua imagem de perfil no canto superior esquerdo para <strong>Adicionar Conta</strong>
|
||||
ou para <strong>Mudar de Conta</strong>.</p>
|
||||
|
||||
<p>You may want to use separate profiles for political, family or work related activities.</p>
|
||||
<p>Talvez você queira usar perfis separados para atividades políticas, familiares ou atividades do trabalho.</p>
|
||||
|
||||
<p>You may also wish to learn <a href="#multiclient">how to use the same profile on multiple devices</a>.</p>
|
||||
<p>Talvez você também queira aprender <a href="#multiclient">como usar a mesma conta em vários dispositivos</a>.</p>
|
||||
|
||||
<h3 id="quem-consegue-ver-a-imagem-do-meu-perfil">
|
||||
|
||||
@@ -267,96 +267,96 @@ or to <strong>Switch Profiles</strong>.</p>
|
||||
<h3 id="signature">
|
||||
|
||||
|
||||
Can I set a Bio/Status with Delta Chat? <a href="#signature" class="anchor"></a>
|
||||
Posso definir uma Bio/Assinatura com o Delta Chat? <a href="#signature" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Yes,
|
||||
you can do so under <strong>Settings → Profile → Bio</strong>.
|
||||
Once you sent a message to a contact,
|
||||
they will see it when they view your contact details.</p>
|
||||
<p>Sim,
|
||||
você pode fazer isso em <strong>Configurações → Conta → Assinatura</strong>.
|
||||
Depois de enviar uma mensagem para um contato,
|
||||
ele verá sua assinatura quando visualizar seus detalhes de contato.</p>
|
||||
|
||||
<h3 id="what-do-pinning-muting-and-archiving-mean">
|
||||
<h3 id="o-que-significa-fixar-silenciar-e-arquivar">
|
||||
|
||||
|
||||
What do Pinning, Muting and Archiving mean? <a href="#what-do-pinning-muting-and-archiving-mean" class="anchor"></a>
|
||||
O que significa Fixar, Silenciar e Arquivar? <a href="#o-que-significa-fixar-silenciar-e-arquivar" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Use these tools to organize your chats and keep everything in its place:</p>
|
||||
<p>Use essas ferramentas para organizar seus chats e manter tudo em seu devido lugar:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p><strong>Pinned chats</strong> always stay atop of the chat list. You can use them to access your most loved chats quickly or temporarily to not forget about things.</p>
|
||||
<p>Os <strong>chats fixados</strong> sempre ficam no topo da lista de conversas. Você pode usá-los para acessar seus chats mais queridos rapidamente ou temporariamente para não se esquecer de nada.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Mute chats</strong> if you do not want to get notifications for them. Muted chats stay in place and you can also pin a muted chat.</p>
|
||||
<p><strong>Silencie os chats</strong> se não quiser receber notificações deles. Os bate-papos silenciados permanecem no lugar e você também pode fixar um chat silenciado.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Archive chats</strong> if you do not want to see them in your chat list any longer.
|
||||
They remain accessible above the chat list or via search
|
||||
and are marked by <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Archived</b></p>
|
||||
<p><strong>Arquive chats</strong> se não quiser mais vê-los em sua lista de conversas.
|
||||
Eles permanecem acessíveis acima da lista conversa ou por meio de pesquisa
|
||||
e são marcados como <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Arquivado</b></p>
|
||||
</li>
|
||||
<li>
|
||||
<p>When an archived chat gets a new message, unless muted, it will <strong>pop out of the archive</strong> and back into your chat list.
|
||||
<strong>Muted chats stay archived</strong> until you unarchive them manually.</p>
|
||||
<p>Quando um chat arquivado receber uma nova mensagem, a menos que esteja silenciado, ele <strong>sairá do arquivo</strong> e voltará à sua lista de conversas.
|
||||
<strong>Os chats silenciados permanecem arquivados</strong> até que você os desarquive manualmente.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>To use the functions, long tap or right click a chat in the chat list.</p>
|
||||
<p>Para usar as funções, toque e segure ou clique com o botão direito do mouse em um conversa na lista conversas.</p>
|
||||
|
||||
<h3 id="save">
|
||||
|
||||
|
||||
How do “Saved Messages” work? <a href="#save" class="anchor"></a>
|
||||
Como funcionam as “Mensagens salvas”? <a href="#save" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p><strong>Saved Messages</strong> is a chat that you can use to easily remember and find messages.</p>
|
||||
<p><strong>Saved Messages</strong> é um chat que você pode usar para lembrar e localizar facilmente as mensagens.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>In any chat, long tap or right click a message and select <strong>Save</strong></p>
|
||||
<p>Em qualquer chat, dê um toque longo ou clique com o botão direito do mouse em uma mensagem e selecione <strong>Salvar</strong></p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Saved messages are marked by the symbol
|
||||
<img style="vertical-align:middle; width:1.2em; margin:1px" src="../saved-icon.png" alt="Saved icon" />
|
||||
next to the timestamp</p>
|
||||
<p>As mensagens salvas são marcadas com o símbolo
|
||||
<img style="vertical-align:middle; width:1.2em; margin:1px" src="../saved-icon.png" alt="Saved icon" />
|
||||
ao lado do registro de data e hora</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Later, open the “Saved Messages” chat - and you will see the saved messages there.
|
||||
By tapping <img style="vertical-align:middle; width:1.2em; margin:1px" src="../go-to-original.png" alt="Arrow-right icon" />,
|
||||
you can go back to the original message in the original chat</p>
|
||||
<p>Depois, abra o chat de “Mensagens salvas” - e você verá as mensagens salvas lá.
|
||||
Ao tocar em <img style="vertical-align:middle; width:1.2em; margin:1px" src="../go-to-original.png" alt="Arrow-right icon" />,
|
||||
você pode voltar para a mensagem original no chat original</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Finally, you can also use “Saved Messages” to take <strong>personal notes</strong> - open the chat, type something, add a photo or a voice message etc.</p>
|
||||
<p>Por fim, você também pode usar “Mensagens Salvas” para fazer <strong>notas pessoais</strong> - abra o chat, digite algo, adicione uma foto ou uma mensagem de voz etc.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>As “Saved Message” are synced, they can become very handy for transferring data between devices</p>
|
||||
<p>Como as “Mensagens salvas” são sincronizadas, elas podem se tornar muito úteis para a transferência de dados entre dispositivos</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Messages stay saved even if they are edited or deleted -
|
||||
may it be by <a href="#edit">sender</a>, by <a href="#delold">device cleanup</a> or by <a href="#ephemeralmsgs">disappearing messages of other chats</a>.</p>
|
||||
<p>As mensagens permanecem salvas mesmo que sejam editadas ou excluídas -
|
||||
seja pelo <a href="#edit">remetente</a>, por <a href="#delold">limpeza do dispositivo</a> ou por <a href="#ephemeralmsgs">mensagens temporárias de outros chats</a>.</p>
|
||||
|
||||
<h3 id="what-does-the-green-dot-mean">
|
||||
<h3 id="o-que-significa-o-ponto-verde">
|
||||
|
||||
|
||||
What does the green dot mean? <a href="#what-does-the-green-dot-mean" class="anchor"></a>
|
||||
O que significa o ponto verde? <a href="#o-que-significa-o-ponto-verde" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>You can sometimes see a <strong>green dot</strong> <img style="vertical-align:middle; width:1.2em; margin:1px" src="../green-dot.png" alt="" />
|
||||
next to the avatar of a contact.
|
||||
It means they were <strong>recently seen by you</strong> in the last 10 minutes,
|
||||
e.g. because they messaged you or sent a read receipt.</p>
|
||||
<p>Às vezes, você pode ver um <strong>ponto verde</strong> <img style="vertical-align:middle; width:1.2em; margin:1px" src="../green-dot.png" alt="" />
|
||||
ao lado do avatar de um contato.
|
||||
Isso significa que ele foi <strong>visto recentemente por você</strong> nos últimos 10 minutos,
|
||||
por exemplo, porque ele enviou uma mensagem para você ou uma confirmação de leitura.</p>
|
||||
|
||||
<p>So this is not a real time online status
|
||||
and others will as well not always see that you are “online”.</p>
|
||||
<p>Portanto, essa não é uma checagem de status on-line em tempo real
|
||||
e outras pessoas também nem sempre verão que você está “on-line”.</p>
|
||||
|
||||
<h3 id="o-que-significam-os-carrapatos-mostrados-ao-lado-das-mensagens-de-saída">
|
||||
|
||||
@@ -368,132 +368,131 @@ and others will as well not always see that you are “online”.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p><strong>One tick</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick1.png" alt="" />
|
||||
means that the message was sent successfully to the <a href="#relays">relay</a>.</p>
|
||||
<p><strong>Um tique</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick1.png" alt="" />
|
||||
significa que a mensagem foi enviada com sucesso para o <a href="#relays">relay</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Two ticks</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick2.png" alt="" />
|
||||
indicate your contact has read the message.</p>
|
||||
<p>Dois ticks** <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick2.png" alt="" />
|
||||
indicam que seu contato leu a mensagem.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>In <a href="#groups">groups</a> the second tick means that at least one member has reported back having read the message.</p>
|
||||
<p>Em <a href="#groups">grupos</a>, o segundo tique significa que pelo menos um membro leu a mensagem.</p>
|
||||
|
||||
<p>You will only get the second tick if both you and one of the recipients who read the message
|
||||
has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
|
||||
<p>Você só receberá o segundo tique se tanto você quanto um dos destinatários que leu a mensagem
|
||||
tiverem <strong>Configurações → Conversas → Confirmação de leitura</strong> ativada.</p>
|
||||
|
||||
<h3 id="edit">
|
||||
|
||||
|
||||
Correct typos and delete messages after sending <a href="#edit" class="anchor"></a>
|
||||
Corrigir erros de digitação e excluir mensagens após o envio <a href="#edit" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>You can edit the text of your messages after sending.
|
||||
For that, long tap or right click the message and select <strong>Edit</strong>
|
||||
or <img style="vertical-align:middle; width:1.2em; margin:1px" src="../edit-icon.png" alt="Edit icon" />.</p>
|
||||
<p>Você pode editar o texto de suas mensagens após o envio.
|
||||
Para isso, toque longamente ou clique com o botão direito do mouse na mensagem e selecione <strong>Editar Mensagem</strong>
|
||||
ou <img style="vertical-align:middle; width:1.2em; margin:1px" src="../edit-icon.png" alt="Edit icon" />.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If you have sent a message accidentally,
|
||||
from the same menu, select <strong>Delete</strong> and then <strong>Delete for Everyone</strong>.</p>
|
||||
<p>Se você tiver enviado uma mensagem acidentalmente,
|
||||
no mesmo menu, selecione <strong>Apagar</strong> e, em seguida, <strong>Apagar para Toda a Gente</strong>.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>While edited messages will have the word “Edited” next to the timestamp,
|
||||
deleted messages will be removed without a marker in the chat.
|
||||
Notifications are not sent and there is no time limit.</p>
|
||||
<p>Enquanto as mensagens editadas terão a palavra “Editado” ao lado do registro de data e hora,
|
||||
as mensagens excluídas serão removidas sem um marcador no chat.
|
||||
Não são enviadas notificações e não há limite de tempo.</p>
|
||||
|
||||
<p>Note, that the original message may still be received by chat members
|
||||
who could have already replied, forwarded, saved, screenshotted or otherwise copied the message.</p>
|
||||
<p>Observe que a mensagem original ainda pode ser recebida pelos membros do conversa
|
||||
que podem ter respondido, encaminhado, salvo, capturado a tela ou copiado a mensagem.</p>
|
||||
|
||||
<h3 id="ephemeralmsgs">
|
||||
|
||||
|
||||
How do disappearing messages work? <a href="#ephemeralmsgs" class="anchor"></a>
|
||||
Como funcionam as mensagens efêmeras? <a href="#ephemeralmsgs" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>You can turn on “disappearing messages”
|
||||
in the settings of a chat,
|
||||
at the top right of the chat window,
|
||||
by selecting a time span
|
||||
between 5 minutes and 1 year.</p>
|
||||
<p>Você pode ativar a opção “mensagens temporárias”
|
||||
nas configurações de um conversa,
|
||||
no canto superior direito da janela conversa,
|
||||
selecionando um intervalo de tempo
|
||||
entre 5 minutos e 1 ano.</p>
|
||||
|
||||
<p>Until the setting is turned off again,
|
||||
each chat member’s Delta Chat app takes care
|
||||
of deleting the messages
|
||||
after the selected time span.
|
||||
The time span begins
|
||||
when the receiver first sees the message in Delta Chat.
|
||||
The messages are deleted both,
|
||||
on the servers,
|
||||
and in the apps itself.</p>
|
||||
<p>Até que a configuração seja desativada novamente,
|
||||
o aplicativo Delta Chat de cada membro do conversa se encarrega de
|
||||
de excluir as mensagens
|
||||
após o período de tempo selecionado.
|
||||
O intervalo de tempo começa
|
||||
quando o destinatário vê a mensagem pela primeira vez no Delta Chat.
|
||||
As mensagens são excluídas tanto
|
||||
nos servidores
|
||||
como nos próprios aplicativos.</p>
|
||||
|
||||
<p>Note that you can rely on disappearing messages
|
||||
only as long as you trust your chat partners;
|
||||
malicious chat partners can take photos,
|
||||
or otherwise save, copy or forward messages before deletion.</p>
|
||||
<p>Observe que você pode confiar no desaparecimento das mensagens
|
||||
somente se você confiar em seus parceiros do conversa;
|
||||
parceiros mal-intencionados de conversa podem tirar fotos,
|
||||
ou salvar, copiar ou encaminhar mensagens antes de excluí-las.</p>
|
||||
|
||||
<p>Apart from that,
|
||||
if one chat partner uninstalls Delta Chat,
|
||||
the (anyway encrypted) messages may take longer to get deleted from their server.</p>
|
||||
<p>Além disso,
|
||||
se um parceiro de conversa desinstalar o Delta Chat,
|
||||
as mensagens (de qualquer forma criptografadas) podem demorar mais para serem excluídas do servidor deles.</p>
|
||||
|
||||
<h3 id="delold">
|
||||
|
||||
|
||||
What happens if I turn on “Delete Messages from Device”? <a href="#delold" class="anchor"></a>
|
||||
O que acontece se eu ativar a opção “Apagar mensagens do dispositivo”? <a href="#delold" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>If you want to save storage on your device, you can choose to delete old
|
||||
messages automatically.</p>
|
||||
<p>Se quiser economizar espaço de armazenamento no dispositivo, você pode optar por excluir mensagens antigas automaticamente.</p>
|
||||
|
||||
<p>To turn it on, go to <strong>Settings → Chats → Delete Message from Device</strong>.
|
||||
You can set a timeframe between “after an hour” and “after a year”;
|
||||
this way, <em>all</em> messages will be deleted from your device as soon as they are
|
||||
older than that.</p>
|
||||
<p>Para ativá-la, vá para <strong>Configurações → Conversas → Apagar mensagens do dispositivo</strong>.
|
||||
Você pode definir um período de tempo entre “após uma hora” e “após um ano”;
|
||||
Dessa forma, <em>todas</em> as mensagens serão excluídas de seu dispositivo assim que forem
|
||||
mais antigas do que isso.</p>
|
||||
|
||||
<h3 id="remove-account">
|
||||
|
||||
|
||||
How can I delete my chat profile? <a href="#remove-account" class="anchor"></a>
|
||||
Como posso excluir minha conta? <a href="#remove-account" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>If you are using more than one chat profile,
|
||||
you can remove single ones in the top profile switcher menu (on Android and iOS),
|
||||
or in the sidebar with a right click (in the Desktop app).
|
||||
Chat profiles are only removed on the device where deletion was triggered.
|
||||
Chat profiles on other devices will continue to fully function.</p>
|
||||
<p>Se estiver usando mais de uma conta,
|
||||
você pode removê-las individualmente no menu superior de mudar de conta (no Android e no iOS),
|
||||
ou na barra lateral com um clique no botão direito do mouse (no aplicativo para desktop).
|
||||
As contas são removidas somente no dispositivo em que a exclusão foi acionada.
|
||||
Contas em outros dispositivos continuarão funcionando plenamente.</p>
|
||||
|
||||
<p>If you use a single default chat profile you can simply uninstall the app.
|
||||
This will still automatically trigger deletion of all associated address data on the chatmail server.
|
||||
For more info, please refer to <a href="https://nine.testrun.org/info.html#account-deletion">nine.testrun.org address-deletion</a>
|
||||
or the respective page from your chosen <a href="https://chatmail.at/relays">3rd party chatmail server</a>.</p>
|
||||
<p>Se você usar uma conta única padrão, poderá simplesmente desinstalar o aplicativo.
|
||||
Isso ainda acionará automaticamente a exclusão de todos os dados de endereço associados no servidor de chatmail.
|
||||
Para obter mais informações, consulte <a href="https://nine.testrun.org/info.html#account-deletion">nine.testrun.org address-deletion</a>
|
||||
ou a respectiva página do <a href="https://chatmail.at/relays">servidor de chatmail de terceiros</a> que você escolheu.</p>
|
||||
|
||||
<h2 id="groups">
|
||||
|
||||
|
||||
Groups <a href="#groups" class="anchor"></a>
|
||||
Grupos <a href="#groups" class="anchor"></a>
|
||||
|
||||
|
||||
</h2>
|
||||
|
||||
<p>Groups let several people chat together privately with <strong>equal rights</strong>.</p>
|
||||
<p>Os grupos permitem que várias pessoas conversem em particular com <strong>direitos iguais</strong>.</p>
|
||||
|
||||
<p>Anyone can
|
||||
change the group name or avatar,
|
||||
<a href="#addmembers">add or remove members</a>,
|
||||
set <a href="#ephemeralmsgs">disappearing messages</a>,
|
||||
and <a href="#edit">delete their own messages</a> from all member’s devices.</p>
|
||||
<p>Qualquer pessoa pode
|
||||
alterar o nome do grupo ou o avatar,
|
||||
<a href="#addmembers">adicionar ou remover membros</a>,
|
||||
definir <a href="#ephemeralmsgs">mensagens temporárias</a>,
|
||||
e <a href="#edit">excluir suas próprias mensagens</a> dos dispositivos de todos os membros.</p>
|
||||
|
||||
<p>Because all members have the same rights, groups work best among <strong>trusted friends and family</strong>.</p>
|
||||
<p>Como todos os membros têm os mesmos direitos, os grupos funcionam melhor entre <strong>amigos e familiares de confiança</strong>.</p>
|
||||
|
||||
<h3 id="criação-de-um-grupo">
|
||||
|
||||
@@ -518,33 +517,33 @@ and <a href="#edit">delete their own messages</a> from all member’s devices.</
|
||||
<h3 id="addmembers">
|
||||
|
||||
|
||||
Add and remove members <a href="#addmembers" class="anchor"></a>
|
||||
Adicionar e remover membros <a href="#addmembers" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>All group members have the <strong>same rights</strong>.
|
||||
For this reason, everyone can delete any member or add new ones.</p>
|
||||
<p>Todos os membros do grupo têm os <strong>mesmos direitos</strong>.
|
||||
Por este motivo, todos podem remover qualquer membro ou adicionar novos membros.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>To <strong>add or delete members</strong>, tap the group name in the chat and select the member to add or remove.</p>
|
||||
<p>Para <strong>adicionar ou remover membros</strong>, toque no nome do grupo dentro do chat e selecione o membro a adicionar ou a remover.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>If the member is not yet in your contact list, but <strong>face to face</strong> with you,
|
||||
from the same screen, show a <strong>QR code</strong>.<br />
|
||||
Ask your chat partner to <strong>scan</strong> the QR image with their Delta Chat app by tapping
|
||||
<img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> on the main screen.</p>
|
||||
<p>Se o membro ainda não estiver na sua lista de contactos, mas estiver <strong>cara a cara</strong> com você,
|
||||
na mesma tela, moste um <strong>QR code</strong>.<br />
|
||||
Peça ao seu parceiro de conversa para <strong>escanear</strong> o QR code com o Delta Chat dele, tocando em
|
||||
<img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> na tela inicial.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>For a <strong>remote</strong> member addition,
|
||||
click “Copy” or “Share” and send the <strong>invite link</strong>
|
||||
through another private chat to the new member.</p>
|
||||
<p>Para adicionar um membro <strong>remotamente</strong>,
|
||||
clique em “Copiar” ou “Compartilhar” e envie o link do <strong>convite</strong>
|
||||
por meio de outro mensageiro privado para o novo membro.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>QR code and invite link can be used to add several members.
|
||||
However, since groups are <a href="#groups">meant for trusted people</a>, avoid sharing them publicly.</p>
|
||||
<p>O QR code e o link de convite podem ser usados para adicionar vários membros.
|
||||
No entanto, como os grupos são <a href="#groups">destinados a pessoas de confiança</a>, evite compartilhá-los publicamente.</p>
|
||||
|
||||
<h3 id="deletei-minha-própria-conta-por-acidente">
|
||||
|
||||
@@ -571,60 +570,60 @@ Se você quiser entrar mais tarde no grupo novamente, peça a outra pessoa do gr
|
||||
|
||||
<p>Uma alternativa é “silenciar” um grupo. Fazendo isso, você receberá todas as mensagens e ainda poderá escrever, mas não será receberá mais notificações d enovas mensagens.</p>
|
||||
|
||||
<h3 id="cloning-a-group">
|
||||
<h3 id="clonagem-de-grupos">
|
||||
|
||||
|
||||
Cloning a group <a href="#cloning-a-group" class="anchor"></a>
|
||||
Clonagem de grupos <a href="#clonagem-de-grupos" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>You can duplicate a group to start a separate discussion
|
||||
or to exclude members without them noticing.</p>
|
||||
<p>Você pode duplicar um grupo para iniciar uma discussão separada
|
||||
ou para excluir membros sem que eles percebam.</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Open the group profile and tap <strong>Clone Chat</strong> (Android/iOS),
|
||||
or right-click the group in the chat list (Desktop).</p>
|
||||
<p>Abra o perfil do grupo e toque em <strong>Clonar conversa</strong> (Android/iOS),
|
||||
ou clique com o botão direito do mouse no grupo na lista de conversa (desktop).</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Set a new name, choose an avatar, and adjust the member list if needed.</p>
|
||||
<p>Defina um novo nome, escolha um avatar e ajuste a lista de membros, se necessário.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>The new group is <strong>fully independent</strong> from the original,
|
||||
which continues to work as before.</p>
|
||||
<p>O novo grupo é <strong>totalmente independente</strong> do original,
|
||||
que continua funcionando como antes.</p>
|
||||
|
||||
<h3 id="how-many-members-can-participate-in-a-single-group">
|
||||
<h3 id="quantos-membros-podem-participar-em-um-único-grupo">
|
||||
|
||||
|
||||
How many members can participate in a single group? <a href="#how-many-members-can-participate-in-a-single-group" class="anchor"></a>
|
||||
Quantos membros podem participar em um único grupo? <a href="#quantos-membros-podem-participar-em-um-único-grupo" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
|
||||
<p>There is no strict technical limit,
|
||||
but more than 150 is not recommended.</p>
|
||||
<p>Não há um limite técnico rigoroso,
|
||||
mas não é recomendável mais de 150.</p>
|
||||
|
||||
<p>As groups get larger, they can become socially unstable and may need a hierarchy -
|
||||
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
|
||||
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
|
||||
<p>À medida que os grupos se tornam maiores, eles podem se tornar socialmente instáveis e podem precisar de uma hierarquia -
|
||||
mas o Delta Chat é um mensageiro privado para conversar com <a href="#groups">direitos iguais</a>.
|
||||
Consulte o <a href="https://pt.wikipedia.org/wiki/N%C3%BAmero_de_Dunbar">Número de Dunbar</a> para obter mais informações.</p>
|
||||
|
||||
<h2 id="webxdc">
|
||||
|
||||
|
||||
In-chat apps <a href="#webxdc" class="anchor"></a>
|
||||
Aplicativos embutidos <a href="#webxdc" class="anchor"></a>
|
||||
|
||||
|
||||
</h2>
|
||||
|
||||
<p>You can send apps to a chat - games, editors, polls and other tools.
|
||||
This makes Delta Chat a truly extensible messenger.</p>
|
||||
<p>Você pode enviar aplicativos para um chat - jogos, editores, enquetes e outras ferramentas.
|
||||
Isso torna o Delta Chat um mensageiro realmente extensível.</p>
|
||||
|
||||
<h3 id="where-can-i-get-in-chat-apps">
|
||||
<h3 id="onde-posso-obter-aplicativos-embutidos">
|
||||
|
||||
|
||||
Where can I get in-chat apps? <a href="#where-can-i-get-in-chat-apps" class="anchor"></a>
|
||||
Onde posso obter aplicativos embutidos? <a href="#onde-posso-obter-aplicativos-embutidos" class="anchor"></a>
|
||||
|
||||
|
||||
</h3>
|
||||
@@ -1184,6 +1183,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
|
||||
<li>
|
||||
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
|
||||
establishing end-to-end encryption between contacts and all members of a group chat.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption and forward secrecy.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
|
||||
@@ -1368,12 +1371,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
|
||||
<p>Servers can therefore only see:</p>
|
||||
|
||||
<ul>
|
||||
<li>the sender and receiver addresses</li>
|
||||
<li>and the message size.</li>
|
||||
<li>Sender and receiver addresses, randomly generated by default</li>
|
||||
<li>Message size</li>
|
||||
</ul>
|
||||
|
||||
<p>By default, the addresses are randomly generated.</p>
|
||||
|
||||
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
|
||||
|
||||
<h3 id="device-seizure">
|
||||
@@ -1449,7 +1450,7 @@ but an implementation has not been agreed as a priority yet.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
|
||||
This means that if your private decryption key is leaked,
|
||||
@@ -1460,12 +1461,9 @@ Otherwise, someone obtaining your decryption keys
|
||||
is typically also able to get all your non-deleted messages
|
||||
and doesn’t even need to decrypt any previously collected messages.</p>
|
||||
|
||||
<p>We designed a Forward Secrecy approach that withstood
|
||||
initial examination from some cryptographers and implementation experts
|
||||
but is pending a more formal write up
|
||||
to ascertain it reliably works in federated messaging and with multi-device usage,
|
||||
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
|
||||
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will provide reliable deletion (forward secrecy) through automatic key rotation.
|
||||
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="pqc">
|
||||
|
||||
@@ -1475,12 +1473,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
|
||||
in collaboration with other OpenPGP implementers.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption to protect against quantum computer attacks.
|
||||
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="how-can-i-manually-check-encryption-information">
|
||||
|
||||
@@ -1659,52 +1658,38 @@ ordered chronologically:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
|
||||
and implementation of verified groups and setup contact protocols
|
||||
in 2017 and 2018 and also helped to integrate end-to-end Encryption
|
||||
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
|
||||
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
|
||||
and released a first Desktop app beta version, and which moreover
|
||||
moored our feature developments in UX research in human rights contexts,
|
||||
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
The second 2019/2020 grant (~$300K) helped us to
|
||||
release Delta/iOS versions, to convert our core library to Rust, and
|
||||
to provide new features for all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2021 we received further EU funding for two Next-Generation-Internet
|
||||
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
|
||||
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
|
||||
This funding supported our long-running goals to make Delta Chat more usable
|
||||
and compatible with a wide range of email servers world-wide, and more resilient and secure
|
||||
in places often affected by internet censorship and shutdowns.</p>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>2023-2024 we successfully completed the OTF-funded
|
||||
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
|
||||
allowing us to introduce guaranteed encryption,
|
||||
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
|
||||
and providing “instant onboarding” in all apps released from April 2024 on.</p>
|
||||
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
|
||||
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
|
||||
and released a first Desktop app beta version, and which moreover
|
||||
moored our feature developments in UX research in human rights contexts,
|
||||
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
The second 2019/2020 grant (~$300K) helped us to
|
||||
release Delta/iOS versions, to convert our core library to Rust, and
|
||||
to provide new features for all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
|
||||
and implementation of verified groups and setup contact protocols
|
||||
in 2017 and 2018 and also helped to integrate end-to-end Encryption
|
||||
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Sometimes we receive one-time donations from private individuals.
|
||||
|
||||
@@ -1181,6 +1181,10 @@ Chatmail использует INBOX по умолчанию для ретран
|
||||
<li>
|
||||
<p><a href="https://autocrypt.org">Autocrypt</a> используется для автоматической
|
||||
настройки сквозного шифрования между контактами и всеми членами группового чата.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, полное внедрение которого запланировано на 2026 год,
|
||||
обеспечит поддержку постквантового шифрования и прямой секретности.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Обмен контактом в
|
||||
@@ -1427,7 +1431,7 @@ Delta Chat вместо этого использует реализацию Ope
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Нет, еще нет.</p>
|
||||
<p>Нет, пока нет.</p>
|
||||
|
||||
<p>Мессенджер Signal внедрил функцию <a href="https://signal.org/blog/sealed-sender/">“Sealed Sender” (Засекреченный отправитель) в 2018 году</a>,
|
||||
чтобы их серверная инфраструктура не имела информации о том, кто отправляет сообщение группе получателей.
|
||||
@@ -1448,7 +1452,7 @@ Delta Chat вместо этого использует реализацию Ope
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Нет, еще нет.</p>
|
||||
<p>Пока нет, но это будет реализовано в <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>На данный момент, Delta Chat не поддерживает Perfect Forward Secrecy (PFS) (Совершенную прямую секретность).
|
||||
Это означает, что если ваш приватный ключ дешифрования будет скомпрометирован,
|
||||
@@ -1459,12 +1463,9 @@ Delta Chat вместо этого использует реализацию Ope
|
||||
также может получить все ваши не удалённые сообщения
|
||||
и ему даже не нужно расшифровывать какие-либо ранее собранные сообщения.</p>
|
||||
|
||||
<p>Мы разработали подход к Forward Secrecy (Прямой секретности), который прошёл
|
||||
первичную проверку некоторыми криптографами и экспертами по реализации
|
||||
но требует более формального описания
|
||||
чтобы убедиться, что он надёжно работает в федеративном обмене сообщениями и при использовании нескольких устройств,
|
||||
прежде чем он может быть внедрён в <a href="https://github.com/chatmail/core">ядро chatmail</a>,
|
||||
что сделает его доступным во всех <a href="https://chatmail.at/clients">клиентах clients</a>.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, полное внедрение которого запланировано на 2026 год,
|
||||
обеспечит надёжное удаление (прямую секретность) за счёт автоматической ротации ключей.
|
||||
Этот подход описан в черновике спецификации <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a>.</p>
|
||||
|
||||
<h3 id="pqc">
|
||||
|
||||
@@ -1474,12 +1475,13 @@ Delta Chat вместо этого использует реализацию Ope
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Нет, еще нет.</p>
|
||||
<p>Пока нет, но эта возможность появится в <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat использует библиотеку OpenPGP на Rust <a href="https://github.com/rpgp/rpgp">rPGP</a>,
|
||||
которая поддерживает последний <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">черновик IETF Post-Quantum-Cryptography OpenPGP</a>.
|
||||
Мы планируем добавить поддержку PQC в <a href="https://github.com/chatmail/core">ядро chatmail</a> после того, как черновик будет окончательно утвержден в IETF
|
||||
в сотрудничестве с другими разработчиками OpenPGP.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, полное внедрение которого запланировано на 2026 год,
|
||||
обеспечит поддержку постквантового шифрования для защиты от атак с использованием квантовых компьютеров.
|
||||
Delta Chat использует Rust-библиотеку OpenPGP <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
которая поддерживает актуальный черновик IETF <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP</a>.
|
||||
Особенности реализации описаны в черновике спецификации <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a>.</p>
|
||||
|
||||
<h3 id="как-можно-вручную-проверить-информацию-о-шифровании">
|
||||
|
||||
@@ -1658,10 +1660,22 @@ Google Play Store, F-Droid, Huawei App Gallery, iOS и macOS App Store, Microsof
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Проект ЕС <a href="https://nextleap.eu">NEXTLEAP</a> финансировал исследование
|
||||
и внедрение проверенных групп и настройку протоколов контактов
|
||||
в 2017 и 2018 годах, а также помог интегрировать сквозное шифрование
|
||||
через <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
<p>В 2023 и 2024 годах мы были приняты в программу Next Generation Internet (NGI)
|
||||
за нашу работу над <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
в сотрудничестве с партнерами, работающими над
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> и
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
Все эти проекты частично завершены или будут завершены в начале 2025 года.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>В 2021 г. мы получили дополнительное финансирование из ЕС для двух Next-Generation-Internet
|
||||
целей, а именно для <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - e-mail provider portability directory</a> (~97 тыс. евро) и <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90 тыс. евро). Это привело к улучшению поддержки нескольких профилей, улучшению настройки контактов и групп с помощью QR-кода и многим улучшениям в сетевом взаимодействии на всех платформах.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Фонд <a href="https://nlnet.nl/">NLnet Foundation</a> выделил в 2019/2020 году 46 тысяч евро на
|
||||
доработку связки Rust/Python и создание экосистемы чат-ботов..</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Фонд <a href="https://opentechfund.org">Open Technology Fund</a> предоставил нам
|
||||
@@ -1674,36 +1688,10 @@ Google Play Store, F-Droid, Huawei App Gallery, iOS и macOS App Store, Microsof
|
||||
предоставить новые функции для всех платформ.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://nlnet.nl/">Фонд NLnet</a> выделил в 2019/2020 году 46 тыс. евро на
|
||||
завершение привязки Rust/Python и создание экосистемы чат-ботов.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>В 2021 г. мы получили дополнительное финансирование из ЕС для двух Next-Generation-Internet
|
||||
целей, а именно для <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - e-mail provider portability directory</a> (~97 тыс. евро) и <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90 тыс. евро). Это привело к улучшению поддержки нескольких профилей, улучшению настройки контактов и групп с помощью QR-кода и многим улучшениям в сетевом взаимодействии на всех платформах.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>С конца 2021 года по март 2023 года мы получили финансирование в размере ($500 тыс.) от
|
||||
U.S. Bureau of Democracy, Human Rights and Labor (DRL) для поддержки <em>свободы интернета</em>.
|
||||
Это финансирование поддержало наши долгосрочные цели, сделать Delta Chat более удобным для использования
|
||||
и совместимым с широким спектром электронных почтовых серверов по всему миру, а также более устойчивым
|
||||
и безопасным в местах, часто подвергающихся интернет-цензуре и отключениям.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>2023-2024 мы завершили проект финансируемый OTF
|
||||
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
|
||||
что позволило нам внедрить гарантированное шифрование,
|
||||
создать сеть серверов <a href="https://delta.chat/chatmail">chatmail</a>
|
||||
и обеспечить “немедленную регистрацию” во всех приложениях, выпущенных с апреля 2024 года.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>В 2023 и 2024 годах мы были приняты в программу Next Generation Internet (NGI)
|
||||
за нашу работу над <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
в сотрудничестве с партнерами, работающими над
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> и
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
Все эти проекты частично завершены или будут завершены в начале 2025 года.</p>
|
||||
<p>Проект ЕС <a href="https://nextleap.eu">NEXTLEAP</a> финансировал исследование
|
||||
и внедрение проверенных групп и настройку протоколов контактов
|
||||
в 2017 и 2018 годах, а также помог интегрировать сквозное шифрование
|
||||
через <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Иногда мы получаем разовые пожертвования от физических лиц.
|
||||
|
||||
@@ -1188,6 +1188,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
|
||||
<li>
|
||||
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
|
||||
establishing end-to-end encryption between contacts and all members of a group chat.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption and forward secrecy.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
|
||||
@@ -1372,12 +1376,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
|
||||
<p>Servers can therefore only see:</p>
|
||||
|
||||
<ul>
|
||||
<li>the sender and receiver addresses</li>
|
||||
<li>and the message size.</li>
|
||||
<li>Sender and receiver addresses, randomly generated by default</li>
|
||||
<li>Message size</li>
|
||||
</ul>
|
||||
|
||||
<p>By default, the addresses are randomly generated.</p>
|
||||
|
||||
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
|
||||
|
||||
<h3 id="device-seizure">
|
||||
@@ -1453,7 +1455,7 @@ but an implementation has not been agreed as a priority yet.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
|
||||
This means that if your private decryption key is leaked,
|
||||
@@ -1464,12 +1466,9 @@ Otherwise, someone obtaining your decryption keys
|
||||
is typically also able to get all your non-deleted messages
|
||||
and doesn’t even need to decrypt any previously collected messages.</p>
|
||||
|
||||
<p>We designed a Forward Secrecy approach that withstood
|
||||
initial examination from some cryptographers and implementation experts
|
||||
but is pending a more formal write up
|
||||
to ascertain it reliably works in federated messaging and with multi-device usage,
|
||||
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
|
||||
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will provide reliable deletion (forward secrecy) through automatic key rotation.
|
||||
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="pqc">
|
||||
|
||||
@@ -1479,12 +1478,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
|
||||
in collaboration with other OpenPGP implementers.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption to protect against quantum computer attacks.
|
||||
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="how-can-i-manually-check-encryption-information">
|
||||
|
||||
@@ -1663,52 +1663,38 @@ ordered chronologically:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
|
||||
and implementation of verified groups and setup contact protocols
|
||||
in 2017 and 2018 and also helped to integrate end-to-end Encryption
|
||||
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
|
||||
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
|
||||
and released a first Desktop app beta version, and which moreover
|
||||
moored our feature developments in UX research in human rights contexts,
|
||||
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
The second 2019/2020 grant (~$300K) helped us to
|
||||
release Delta/iOS versions, to convert our core library to Rust, and
|
||||
to provide new features for all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2021 we received further EU funding for two Next-Generation-Internet
|
||||
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
|
||||
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
|
||||
This funding supported our long-running goals to make Delta Chat more usable
|
||||
and compatible with a wide range of email servers world-wide, and more resilient and secure
|
||||
in places often affected by internet censorship and shutdowns.</p>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>2023-2024 we successfully completed the OTF-funded
|
||||
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
|
||||
allowing us to introduce guaranteed encryption,
|
||||
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
|
||||
and providing “instant onboarding” in all apps released from April 2024 on.</p>
|
||||
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
|
||||
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
|
||||
and released a first Desktop app beta version, and which moreover
|
||||
moored our feature developments in UX research in human rights contexts,
|
||||
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
|
||||
The second 2019/2020 grant (~$300K) helped us to
|
||||
release Delta/iOS versions, to convert our core library to Rust, and
|
||||
to provide new features for all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
|
||||
and implementation of verified groups and setup contact protocols
|
||||
in 2017 and 2018 and also helped to integrate end-to-end Encryption
|
||||
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Sometimes we receive one-time donations from private individuals.
|
||||
|
||||
@@ -1190,6 +1190,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
|
||||
<li>
|
||||
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
|
||||
establishing end-to-end encryption between contacts and all members of a group chat.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption and forward secrecy.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
|
||||
@@ -1374,12 +1378,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
|
||||
<p>Servers can therefore only see:</p>
|
||||
|
||||
<ul>
|
||||
<li>the sender and receiver addresses</li>
|
||||
<li>and the message size.</li>
|
||||
<li>Sender and receiver addresses, randomly generated by default</li>
|
||||
<li>Message size</li>
|
||||
</ul>
|
||||
|
||||
<p>By default, the addresses are randomly generated.</p>
|
||||
|
||||
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
|
||||
|
||||
<h3 id="device-seizure">
|
||||
@@ -1455,7 +1457,7 @@ but an implementation has not been agreed as a priority yet.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
|
||||
This means that if your private decryption key is leaked,
|
||||
@@ -1466,12 +1468,9 @@ Otherwise, someone obtaining your decryption keys
|
||||
is typically also able to get all your non-deleted messages
|
||||
and doesn’t even need to decrypt any previously collected messages.</p>
|
||||
|
||||
<p>We designed a Forward Secrecy approach that withstood
|
||||
initial examination from some cryptographers and implementation experts
|
||||
but is pending a more formal write up
|
||||
to ascertain it reliably works in federated messaging and with multi-device usage,
|
||||
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
|
||||
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will provide reliable deletion (forward secrecy) through automatic key rotation.
|
||||
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="pqc">
|
||||
|
||||
@@ -1481,12 +1480,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
|
||||
in collaboration with other OpenPGP implementers.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption to protect against quantum computer attacks.
|
||||
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="how-can-i-manually-check-encryption-information">
|
||||
|
||||
@@ -1665,10 +1665,25 @@ ordered chronologically:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
|
||||
and implementation of verified groups and setup contact protocols
|
||||
in 2017 and 2018 and also helped to integrate end-to-end Encryption
|
||||
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Më 2021-n morëm financime të mëtejshme nga BE për dy propozime që shtrihen në
|
||||
“Internetin e Brezit Tjetër”, konkretisht për <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - e-mail provider portability directory</a> (~97K euro) dhe <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K euro) që sollën mbulim më të mirë për përdorues me shumë
|
||||
llogari, përmirësim të gjërave për kontakte me kod QR dhe grupe, si dhe mjaft
|
||||
përmirësime në punën në rrjet për krejt platformat.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://nlnet.nl/">Fondacioni NLnet</a> dhuroi 46K euro gjatë 2019/2020 për
|
||||
plotësimin e <em>Rust/Python bindings</em> dhe për t’i dhënë udhë një ekosistemi
|
||||
Chat-bot.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://opentechfund.org">Open Technology Fund</a> na dha grantin e parë
|
||||
@@ -1681,36 +1696,10 @@ versione Delta/iOS, për të shndërruar bibliotekën tonë bazë në Rust, si d
|
||||
për të sjellë veçori të reja për krejt platformat.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
|
||||
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2021 we received further EU funding for two Next-Generation-Internet
|
||||
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
|
||||
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
|
||||
This funding supported our long-running goals to make Delta Chat more usable
|
||||
and compatible with a wide range of email servers world-wide, and more resilient and secure
|
||||
in places often affected by internet censorship and shutdowns.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>2023-2024 we successfully completed the OTF-funded
|
||||
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
|
||||
allowing us to introduce guaranteed encryption,
|
||||
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
|
||||
and providing “instant onboarding” in all apps released from April 2024 on.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
|
||||
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
|
||||
along with collaboration partners working on
|
||||
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
|
||||
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
|
||||
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
|
||||
All of these projects are partially completed or to be completed in early 2025.</p>
|
||||
<p>Projekti <a href="https://nextleap.eu">NEXTLEAP</a> i BE-së financoi kërkimin
|
||||
për dhe sendërtimin e grupeve të verifikuara dhe protokolleve të
|
||||
ujdisjes së kontakteve më 2017-n dhe 2018-n dhe ndihmoi gjithashtu
|
||||
të integrohet Fshehtëzim Skaj-më-Skaj përmes <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Ndonjëherë marrim dhurime unike nga individë privatë.
|
||||
|
||||
@@ -140,17 +140,10 @@
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>If you are <strong>face to face</strong> with your friend or family,
|
||||
tap the <strong>QR Code</strong> icon <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" />
|
||||
on the main screen.<br />
|
||||
Ask your chat partner to <strong>scan</strong> the QR image
|
||||
with their Delta Chat app.</p>
|
||||
<p>Якщо ви перебуваєте <strong>віч-на-віч</strong> зі своїм другом або родиною, торкніться піктограми <strong>QR-код</strong> на головному екрані <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> на головному екрані. Попросіть вашого партнера по чату <strong>сканувати</strong> QR-зображення за допомогою їхнього застосунку Delta Chat.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>For a <strong>remote</strong> contact setup,
|
||||
from the same screen,
|
||||
click “Copy” or “Share” and send the <strong>invite link</strong>
|
||||
through another private chat.</p>
|
||||
<p>Для <strong>віддаленого</strong> налаштування контакту, на тому ж самому екрані, натисніть “Копіювати” або “Поділитися” і відправте <strong>запрошувальне посилання</strong> через інший приватний чат.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1095,6 +1088,10 @@ to send anonymous usage statistics.</p>
|
||||
<li>
|
||||
<p><a href="https://autocrypt.org">Autocrypt</a> використовується для автоматичного встановлення наскрізного шифрування між контактами і всіма учасниками групового чату.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption and forward secrecy.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Поширення контакту в чаті</a> дозволяє отримувачам використовувати наскрізне шифрування з контактом.</p>
|
||||
</li>
|
||||
@@ -1298,11 +1295,13 @@ but an implementation has not been agreed as a priority yet.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Ні, поки ще ні.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat наразі не підтримує ідеальну пряму секретність (Perfect Forward Secrecy, PFS). Це означає, що якщо ваш приватний ключ для розшифрування буде скомпрометовано, а хтось заздалегідь зібрав ваші повідомлення під час передачі, він зможе розшифрувати та прочитати їх, використовуючи зламаний ключ. Зверніть увагу, що пряма секретність підвищує рівень безпеки лише в тому разі, якщо ви видаляєте повідомлення. Інакше, якщо хтось отримує доступ до ваших ключів розшифрування, він зазвичай також має доступ до всіх ваших невидалених повідомлень і навіть не потребує розшифровувати заздалегідь перехоплені дані.</p>
|
||||
|
||||
<p>Ми розробили підхід Forward Secrecy, який витримав початкову експертизу від деяких криптографів та експертів з реалізації але чекає на більш офіційний звіт щоб переконатися, що він надійно працює в об’єднаних системах обміну повідомленнями та при використанні декількох пристроїв, перш ніж його можна буде реалізувати в <a href="https://github.com/chatmail/core">ядрі чату</a>, що зробить його доступним у всіх <a href="https://chatmail.at/clients">клієнтах чату</a>.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will provide reliable deletion (forward secrecy) through automatic key rotation.
|
||||
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="pqc">
|
||||
|
||||
@@ -1312,9 +1311,13 @@ but an implementation has not been agreed as a priority yet.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Ні, поки ще ні.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat використовує бібліотеку Rust OpenPGP <a href="https://github.com/rpgp/rpgp">rPGP</a> яка підтримує останню версію <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>. Ми плануємо додати підтримку PQC у <a href="https://github.com/chatmail/core">chatmail core</a> після того, як проект буде завершено у IETF у співпраці з іншими розробниками OpenPGP.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption to protect against quantum computer attacks.
|
||||
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="як-я-можу-вручну-перевірити-інформацію-про-шифрування">
|
||||
|
||||
@@ -1445,7 +1448,14 @@ Google Play Store, F-Droid, Huawei App Gallery, iOS and macOS App Store, Microso
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Проект ЄС <a href="https://nextleap.eu">NEXTLEAP</a> фінансував дослідження та впровадження верифікованих груп і протоколів встановлення контактів у 2017 та 2018 роках, а також допоміг інтегрувати наскрізне шифрування через <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
<p>У 2023 та 2024 роках нас прийняли до програми Next Generation Internet (NGI) за нашу роботу над <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>, а також у співпраці з партнерами, які працюють над <a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>, <a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>, <a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> та <a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>. Усі ці проєкти частково завершені або будуть завершені на початку 2025 року.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>У 2021 році ми отримали подальше фінансування від ЄС на дві пропозиції щодо Інтернету наступного покоління а саме на <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - каталог перенесення провайдерів електронної пошти</a> (~97 тис. євро) та <a href="https://nlnet.nl/project/EmailPorting/">AEAP - перенесення адрес електронної пошти</a> (~90 тис. євро), що дозволило нам покращити багатопрофільну підтримку, вдосконалити налаштування контактів та груп за допомогою QR-коду та багато інших мережевих покращень на всіх платформах.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Фонд <a href="https://nlnet.nl/">NLnet</a> виділив у 2019/2020 роках 46 тисяч євро на
|
||||
завершення прив’язок Rust/Python та запуск екосистеми чат-ботів.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://opentechfund.org">Open Technology Fund</a> надав нам два гранти.
|
||||
@@ -1458,25 +1468,7 @@ Google Play Store, F-Droid, Huawei App Gallery, iOS and macOS App Store, Microso
|
||||
і додати нові функції для всіх платформ.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Фонд <a href="https://nlnet.nl/">NLnet</a> виділив у 2019/2020 роках 46 тисяч євро на
|
||||
завершення прив’язок Rust/Python та запуск екосистеми чат-ботів.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2021 we received further EU funding for two Next-Generation-Internet
|
||||
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
|
||||
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
|
||||
This funding supported our long-running goals to make Delta Chat more usable
|
||||
and compatible with a wide range of email servers world-wide, and more resilient and secure
|
||||
in places often affected by internet censorship and shutdowns.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>У 2023-2024 роках ми успішно завершили проєкт <a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail</a>, що фінансувався OTF, що дозволило нам запровадити гарантоване шифрування, створити <a href="https://delta.chat/chatmail">мережу серверів chatmail</a> та забезпечити “миттєву реєстрацію” у всіх застосунках, випущених з квітня 2024 року.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>У 2023 та 2024 роках нас прийняли до програми Next Generation Internet (NGI) за нашу роботу над <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>, а також у співпраці з партнерами, які працюють над <a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>, <a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>, <a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> та <a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>. Усі ці проєкти частково завершені або будуть завершені на початку 2025 року.</p>
|
||||
<p>Проект ЄС <a href="https://nextleap.eu">NEXTLEAP</a> фінансував дослідження та впровадження верифікованих груп і протоколів встановлення контактів у 2017 та 2018 роках, а також допоміг інтегрувати наскрізне шифрування через <a href="https://autocrypt.org">Autocrypt</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Іноді ми отримуємо одноразові пожертви від приватних осіб. Наприклад, у 2021 році щедра приватна особа перерахував нам 4 тис. євро з повідомленням «так тримати!». 💜 Ми використовуємо такі пожертви для фінансування зборів на розвиток або для тимчасових витрат, які важко передбачити або відшкодувати за рахунок грантів державного фінансування. Отримання більшої кількості пожертв також допомагає нам стати більш незалежними та довгостроково життєздатними як спільнота контриб’юторів.</p>
|
||||
|
||||
@@ -1161,6 +1161,10 @@ weekly statistics will be automatically sent to a bot.</p>
|
||||
<li>
|
||||
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
|
||||
用于在联系人和群聊的所有成员之间自动建立端到端加密。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption and forward secrecy.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">将联系人分享到聊天中
|
||||
@@ -1342,12 +1346,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
|
||||
<p>Servers can therefore only see:</p>
|
||||
|
||||
<ul>
|
||||
<li>the sender and receiver addresses</li>
|
||||
<li>and the message size.</li>
|
||||
<li>Sender and receiver addresses, randomly generated by default</li>
|
||||
<li>Message size</li>
|
||||
</ul>
|
||||
|
||||
<p>By default, the addresses are randomly generated.</p>
|
||||
|
||||
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
|
||||
|
||||
<h3 id="device-seizure">
|
||||
@@ -1423,7 +1425,7 @@ but an implementation has not been agreed as a priority yet.</p>
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
|
||||
This means that if your private decryption key is leaked,
|
||||
@@ -1434,12 +1436,9 @@ Otherwise, someone obtaining your decryption keys
|
||||
is typically also able to get all your non-deleted messages
|
||||
and doesn’t even need to decrypt any previously collected messages.</p>
|
||||
|
||||
<p>We designed a Forward Secrecy approach that withstood
|
||||
initial examination from some cryptographers and implementation experts
|
||||
but is pending a more formal write up
|
||||
to ascertain it reliably works in federated messaging and with multi-device usage,
|
||||
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
|
||||
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will provide reliable deletion (forward secrecy) through automatic key rotation.
|
||||
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="pqc">
|
||||
|
||||
@@ -1449,12 +1448,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
|
||||
|
||||
</h3>
|
||||
|
||||
<p>No, not yet.</p>
|
||||
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
|
||||
|
||||
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
|
||||
in collaboration with other OpenPGP implementers.</p>
|
||||
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
|
||||
will bring post-quantum resistant encryption to protect against quantum computer attacks.
|
||||
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
|
||||
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
|
||||
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
|
||||
|
||||
<h3 id="how-can-i-manually-check-encryption-information">
|
||||
|
||||
@@ -1623,33 +1623,6 @@ Google Play Store, F-Droid, Huawei App Gallery, iOS and macOS App Store, Microso
|
||||
按时间顺序排列:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p><a href="https://nextleap.eu">NEXTLEAP</a>欧盟项目资助了以下研究和实施工作:在 2017 年和 2018 年实施的验证组和设置联系协议和通过 <a href="https://autocrypt.org">Autocrypt</a>整合了端到端加密。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>在<a href="https://opentechfund.org">开放技术基金</a> 2018/2019 年提供的第一笔赠款(约 20 万美元)期间,我们显著改善了安卓应用,发布了第一个桌面测试版,并根据人权方面的用户体验研究进行了功能开发,请参阅我们的结论<a href="https://delta.chat/en/2019-07-19-uxreport">《需求发现与用户体验报告》</a>。2019/2020 年的第二笔赠款(约 30 万美元)对发布 Delta/iOS 版本,将核心库转换到 Rust ,以及为所有平台开发新功能提供了帮助。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://nlnet.nl/">NLnet 基金会</a> 2019/2020 年拨款 4.6 万欧元,用于完成 Rust/Python 绑定并建立聊天机器人生态系统。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>In 2021 we received further EU funding for two Next-Generation-Internet
|
||||
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
|
||||
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
|
||||
This funding supported our long-running goals to make Delta Chat more usable
|
||||
and compatible with a wide range of email servers world-wide, and more resilient and secure
|
||||
in places often affected by internet censorship and shutdowns.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>2023-2024 年,我们成功完成了 OTF 资助的
|
||||
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">安全 Chatmail 项目</a>,
|
||||
使我们能够引入保证的加密,
|
||||
创建一个 <a href="https://delta.chat/chatmail">Chatmail 服务器网络</a>,
|
||||
并在 2024 年 4 月起发布的所有应用中提供“即时入职”。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>在 2023 年和 2024 年,我们的 <a href="https://nlnet.nl/project/WebXDC-Push/">WebXDC PUSH</a> 工作已在下一代互联网 (NGI) 中获得认可,
|
||||
并与致力于
|
||||
@@ -1659,6 +1632,18 @@ in places often affected by internet censorship and shutdowns.</p>
|
||||
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a> 的合作伙伴合作。
|
||||
所有这些项目都已部分完成或将在 2025 年初完成。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>在 2021 年,我们从两项下一代互联网提案收到了欧盟的进一步资助,即 <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - 电子邮件提供商可移植性目录</a>(约 9.7 万欧元)和 <a href="https://nlnet.nl/project/EmailPorting/">AEAP - 电子邮件地址移植</a>(约 9 万欧元)。这带来了更好的多账户支持,改进的二维码联系人和群组设置,和所有平台上的多处网络改进。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://nlnet.nl/">NLnet 基金会</a> 2019/2020 年拨款 4.6 万欧元,用于完成 Rust/Python 绑定并建立聊天机器人生态系统。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>在<a href="https://opentechfund.org">开放技术基金</a> 2018/2019 年提供的第一笔赠款(约 20 万美元)期间,我们显著改善了安卓应用,发布了第一个桌面测试版,并根据人权方面的用户体验研究进行了功能开发,请参阅我们的结论<a href="https://delta.chat/en/2019-07-19-uxreport">《需求发现与用户体验报告》</a>。2019/2020 年的第二笔赠款(约 30 万美元)对发布 Delta/iOS 版本,将核心库转换到 Rust ,以及为所有平台开发新功能提供了帮助。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://nextleap.eu">NEXTLEAP</a>欧盟项目资助了以下研究和实施工作:在 2017 年和 2018 年实施的验证组和设置联系协议和通过 <a href="https://autocrypt.org">Autocrypt</a>整合了端到端加密。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>我们有时会收到个人的一次性捐款。 例如,2021 年,一位慷慨的人士以 “继续保持良好的发展态势!”为主题通过银行向我们捐赠了4千欧元💜。 我们用这些钱来资助发展活动或支付不容易预测或从公共基金中报销的临时费用。收到更多的捐款也有助于我们作为一个贡献者社区变得更加独立和长期可持续。</p>
|
||||
|
||||
|
||||
@@ -211,11 +211,11 @@ public class Rpc {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set configuration values from a QR code. (technically from the URI that is stored in the qrcode)
|
||||
* Before this function is called, `checkQr()` should confirm the type of the
|
||||
* QR code is `account` or `webrtcInstance`.
|
||||
* Set configuration values from a QR code (technically from the URI stored in it).
|
||||
* Before this function is called, `check_qr()` should be used to get the QR code type.
|
||||
* <p>
|
||||
* Internally, the function will call dc_set_config() with the appropriate keys,
|
||||
* "DCACCOUNT:" and "DCLOGIN:" QR codes configure the account, but I/O mustn't be started for
|
||||
* such QR codes, consider using [`Self::add_transport_from_qr`] which also restarts I/O.
|
||||
*/
|
||||
public void setConfigFromQr(Integer accountId, String qrContent) throws RpcException {
|
||||
transport.call("set_config_from_qr", mapper.valueToTree(accountId), mapper.valueToTree(qrContent));
|
||||
@@ -234,6 +234,11 @@ public class Rpc {
|
||||
return transport.callForResult(new TypeReference<java.util.Map<String, String>>(){}, "batch_get_config", mapper.valueToTree(accountId), mapper.valueToTree(keys));
|
||||
}
|
||||
|
||||
/** Returns all `ui.*` config keys that were set by the UI. */
|
||||
public java.util.List<String> getAllUiConfigKeys(Integer accountId) throws RpcException {
|
||||
return transport.callForResult(new TypeReference<java.util.List<String>>(){}, "get_all_ui_config_keys", mapper.valueToTree(accountId));
|
||||
}
|
||||
|
||||
public void setStockStrings(java.util.Map<String, String> strings) throws RpcException {
|
||||
transport.call("set_stock_strings", mapper.valueToTree(strings));
|
||||
}
|
||||
@@ -449,11 +454,11 @@ public class Rpc {
|
||||
* Delete a chat.
|
||||
* <p>
|
||||
* Messages are deleted from the device and the chat database entry is deleted.
|
||||
* After that, the event #DC_EVENT_MSGS_CHANGED is posted.
|
||||
* After that, a `MsgsChanged` event is emitted.
|
||||
* Messages are deleted from the server in background.
|
||||
* <p>
|
||||
* Things that are _not done_ implicitly:
|
||||
* <p>
|
||||
* - Messages are **not deleted from the server**.
|
||||
* - The chat or the contact is **not blocked**, so new messages from the user/the group may appear as a contact request
|
||||
* and the user may create the chat again.
|
||||
* - **Groups are not left** - this would
|
||||
@@ -677,7 +682,8 @@ public class Rpc {
|
||||
* Set group name.
|
||||
* <p>
|
||||
* If the group is already _promoted_ (any message was sent to the group),
|
||||
* all group members are informed by a special status message that is sent automatically by this function.
|
||||
* or if this is a brodacast channel,
|
||||
* all members are informed by a special status message that is sent automatically by this function.
|
||||
* <p>
|
||||
* Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
|
||||
*/
|
||||
@@ -685,11 +691,37 @@ public class Rpc {
|
||||
transport.call("set_chat_name", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(newName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set group or broadcast channel description.
|
||||
* <p>
|
||||
* If the group is already _promoted_ (any message was sent to the group),
|
||||
* or if this is a brodacast channel,
|
||||
* all members are informed by a special status message that is sent automatically by this function.
|
||||
* <p>
|
||||
* Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
|
||||
* <p>
|
||||
* See also [`Self::get_chat_description`] / `getChatDescription()`.
|
||||
*/
|
||||
public void setChatDescription(Integer accountId, Integer chatId, String description) throws RpcException {
|
||||
transport.call("set_chat_description", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(description));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the chat description from the database.
|
||||
* <p>
|
||||
* UIs show this in the profile page of the chat,
|
||||
* it is settable by [`Self::set_chat_description`] / `setChatDescription()`.
|
||||
*/
|
||||
public String getChatDescription(Integer accountId, Integer chatId) throws RpcException {
|
||||
return transport.callForResult(new TypeReference<String>(){}, "get_chat_description", mapper.valueToTree(accountId), mapper.valueToTree(chatId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set group profile image.
|
||||
* <p>
|
||||
* If the group is already _promoted_ (any message was sent to the group),
|
||||
* all group members are informed by a special status message that is sent automatically by this function.
|
||||
* or if this is a brodacast channel,
|
||||
* all members are informed by a special status message that is sent automatically by this function.
|
||||
* <p>
|
||||
* Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
|
||||
* <p>
|
||||
@@ -733,11 +765,26 @@ public class Rpc {
|
||||
return transport.callForResult(new TypeReference<Integer>(){}, "add_device_message", mapper.valueToTree(accountId), mapper.valueToTree(label), mapper.valueToTree(msg));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all messages in all chats as _noticed_.
|
||||
* Skips messages from blocked contacts, but does not skip messages in muted chats.
|
||||
* <p>
|
||||
* _Noticed_ messages are no longer _fresh_ and do not count as being unseen
|
||||
* but are still waiting for being marked as "seen" using markseen_msgs()
|
||||
* (read receipts aren't sent for noticed messages).
|
||||
* <p>
|
||||
* Calling this function usually results in the event #DC_EVENT_MSGS_NOTICED.
|
||||
* See also markseen_msgs().
|
||||
*/
|
||||
public void marknoticedAllChats(Integer accountId) throws RpcException {
|
||||
transport.call("marknoticed_all_chats", mapper.valueToTree(accountId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all messages in a chat as _noticed_.
|
||||
* _Noticed_ messages are no longer _fresh_ and do not count as being unseen
|
||||
* but are still waiting for being marked as "seen" using markseen_msgs()
|
||||
* (IMAP/MDNs is not done for noticed messages).
|
||||
* (read receipts aren't sent for noticed messages).
|
||||
* <p>
|
||||
* Calling this function usually results in the event #DC_EVENT_MSGS_NOTICED.
|
||||
* See also markseen_msgs().
|
||||
@@ -893,6 +940,15 @@ public class Rpc {
|
||||
return transport.callForResult(new TypeReference<MessageInfo>(){}, "get_message_info_object", mapper.valueToTree(accountId), mapper.valueToTree(messageId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns count of read receipts on message.
|
||||
* <p>
|
||||
* This view count is meant as a feedback measure for the channel owner only.
|
||||
*/
|
||||
public Integer getMessageReadReceiptCount(Integer accountId, Integer messageId) throws RpcException {
|
||||
return transport.callForResult(new TypeReference<Integer>(){}, "get_message_read_receipt_count", mapper.valueToTree(accountId), mapper.valueToTree(messageId));
|
||||
}
|
||||
|
||||
/** Returns contacts that sent read receipts and the time of reading. */
|
||||
public java.util.List<MessageReadReceipt> getMessageReadReceipts(Integer accountId, Integer messageId) throws RpcException {
|
||||
return transport.callForResult(new TypeReference<java.util.List<MessageReadReceipt>>(){}, "get_message_read_receipts", mapper.valueToTree(accountId), mapper.valueToTree(messageId));
|
||||
@@ -1286,8 +1342,8 @@ public class Rpc {
|
||||
}
|
||||
|
||||
/** Starts an outgoing call. */
|
||||
public Integer placeOutgoingCall(Integer accountId, Integer chatId, String placeCallInfo) throws RpcException {
|
||||
return transport.callForResult(new TypeReference<Integer>(){}, "place_outgoing_call", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(placeCallInfo));
|
||||
public Integer placeOutgoingCall(Integer accountId, Integer chatId, String placeCallInfo, Boolean hasVideo) throws RpcException {
|
||||
return transport.callForResult(new TypeReference<Integer>(){}, "place_outgoing_call", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(placeCallInfo), mapper.valueToTree(hasVideo));
|
||||
}
|
||||
|
||||
/** Accepts an incoming call. */
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
package chat.delta.rpc.types;
|
||||
|
||||
/**
|
||||
* cheaper version of fullchat, omits: - contacts - contact_ids - fresh_message_counter - ephemeral_timer - self_in_group - was_seen_recently - can_send
|
||||
* cheaper version of fullchat, omits: - contact_ids - fresh_message_counter - ephemeral_timer - self_in_group - was_seen_recently - can_send
|
||||
* <p>
|
||||
* used when you only need the basic metadata of a chat like type, name, profile picture
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
package chat.delta.rpc.types;
|
||||
|
||||
public class CallInfo {
|
||||
/** True if SDP offer has a video. */
|
||||
/** True if the call is started as a video call. */
|
||||
public Boolean hasVideo;
|
||||
/**
|
||||
* SDP offer.
|
||||
|
||||
@@ -38,7 +38,7 @@ public class Contact {
|
||||
* <p>
|
||||
* UI should display the information in the contact's profile as follows:
|
||||
* <p>
|
||||
* - If `verifierId` != 0, display text "Introduced by ..." with the name and address of the contact formatted by `name_and_addr`/`nameAndAddr`. Prefix the text by a green checkmark.
|
||||
* - If `verifierId` != 0, display text "Introduced by ..." with the name of the contact. Prefix the text by a green checkmark.
|
||||
* <p>
|
||||
* - If `verifierId` == 0 and `isVerified` != 0, display "Introduced" prefixed by a green checkmark.
|
||||
* <p>
|
||||
|
||||
@@ -412,7 +412,9 @@ public abstract class EventType {
|
||||
/**
|
||||
* One or more transports has changed.
|
||||
* <p>
|
||||
* This event is used for tests to detect when transport synchronization messages arrives. UIs don't need to use it, it is unlikely that user modifies transports on multiple devices simultaneously.
|
||||
* UI should update the list.
|
||||
* <p>
|
||||
* This event is emitted when transport synchronization messages arrives, but not when the UI modifies the transport list by itself.
|
||||
*/
|
||||
public static class TransportsModified extends EventType {
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ public class FullChat {
|
||||
public ChatType chatType;
|
||||
public String color;
|
||||
public java.util.List<Integer> contactIds;
|
||||
public java.util.List<Contact> contacts;
|
||||
public Integer ephemeralTimer;
|
||||
public Integer freshMessageCounter;
|
||||
public Integer id;
|
||||
|
||||
@@ -12,6 +12,7 @@ public class Message {
|
||||
public String error;
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||
public String file;
|
||||
/** The size of the file in bytes, if applicable. If message is a pre-message, then this is the size of the file to be downloaded. */
|
||||
public Integer fileBytes;
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||
public String fileMime;
|
||||
|
||||
@@ -22,6 +22,7 @@ public abstract class MessageLoadResult {
|
||||
public String error;
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||
public String file;
|
||||
/** The size of the file in bytes, if applicable. If message is a pre-message, then this is the size of the file to be downloaded. */
|
||||
public Integer fileBytes;
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
|
||||
public String fileMime;
|
||||
|
||||
@@ -4,6 +4,7 @@ package chat.delta.rpc.types;
|
||||
public enum SystemMessageType {
|
||||
Unknown,
|
||||
GroupNameChanged,
|
||||
GroupDescriptionChanged,
|
||||
GroupImageChanged,
|
||||
MemberAddedToGroup,
|
||||
MemberRemovedFromGroup,
|
||||
|
||||
@@ -2,8 +2,9 @@ package com.b44t.messenger;
|
||||
|
||||
public class DcAccounts {
|
||||
|
||||
public DcAccounts(String dir) {
|
||||
accountsCPtr = createAccountsCPtr(dir);
|
||||
public DcAccounts(String dir, DcEventChannel channel) {
|
||||
accountsCPtr = createAccountsCPtr(dir, channel);
|
||||
if (accountsCPtr == 0) throw new RuntimeException("createAccountsCPtr() returned null pointer");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -45,7 +46,7 @@ public class DcAccounts {
|
||||
|
||||
// working with raw c-data
|
||||
private long accountsCPtr; // CAVE: the name is referenced in the JNI
|
||||
private native long createAccountsCPtr (String dir);
|
||||
private native long createAccountsCPtr (String dir, DcEventChannel channel);
|
||||
private native void unrefAccountsCPtr ();
|
||||
private native long getEventEmitterCPtr ();
|
||||
private native long getJsonrpcInstanceCPtr ();
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.b44t.messenger;
|
||||
|
||||
public class DcEventChannel {
|
||||
|
||||
public DcEventChannel() {
|
||||
eventChannelCPtr = createEventChannelCPtr();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
if (eventChannelCPtr != 0) {
|
||||
unrefEventChannelCPtr();
|
||||
eventChannelCPtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public DcEventEmitter getEventEmitter() {
|
||||
return new DcEventEmitter(getEventEmitterCPtr());
|
||||
}
|
||||
|
||||
// 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 ();
|
||||
}
|
||||
@@ -1,16 +1,24 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.media3.session.MediaController;
|
||||
import androidx.media3.session.SessionCommand;
|
||||
import androidx.media3.session.SessionToken;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.b44t.messenger.DcChat;
|
||||
@@ -18,11 +26,13 @@ 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 org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.service.AudioPlaybackService;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -30,6 +40,7 @@ import java.util.ArrayList;
|
||||
public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
implements DcEventCenter.DcEventDelegate
|
||||
{
|
||||
private static final String TAG = AllMediaActivity.class.getSimpleName();
|
||||
|
||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||
public static final String CONTACT_ID_EXTRA = "contact_id";
|
||||
@@ -57,6 +68,10 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
private TabLayout tabLayout;
|
||||
private ViewPager viewPager;
|
||||
|
||||
private @Nullable MediaController mediaController;
|
||||
private ListenableFuture<MediaController> mediaControllerFuture;
|
||||
private AudioPlaybackViewModel playbackViewModel;
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
dynamicTheme = new DynamicNoActionBarTheme();
|
||||
@@ -91,11 +106,19 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
DcEventCenter eventCenter = DcHelper.getEventCenter(this);
|
||||
eventCenter.addObserver(DcContext.DC_EVENT_CHAT_MODIFIED, this);
|
||||
eventCenter.addObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
|
||||
|
||||
playbackViewModel = new ViewModelProvider(this).get(AudioPlaybackViewModel.class);
|
||||
initializeMediaController();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
DcHelper.getEventCenter(this).removeObservers(this);
|
||||
if (mediaController != null) {
|
||||
MediaController.releaseFuture(mediaControllerFuture);
|
||||
mediaController = null;
|
||||
playbackViewModel.setMediaController(null);
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@@ -124,6 +147,40 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
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));
|
||||
}
|
||||
|
||||
private void addActivityContext(Bundle extras, String activityClassName) {
|
||||
if (mediaController == null) return;
|
||||
|
||||
Bundle commandArgs = new Bundle();
|
||||
commandArgs.putString("activity_class", activityClassName);
|
||||
if (extras != null) {
|
||||
commandArgs.putAll(extras);
|
||||
}
|
||||
|
||||
SessionCommand updateContextCommand =
|
||||
new SessionCommand("UPDATE_ACTIVITY_CONTEXT", Bundle.EMPTY);
|
||||
|
||||
mediaController.sendCustomCommand(updateContextCommand, commandArgs);
|
||||
}
|
||||
|
||||
private boolean isGlobalGallery() {
|
||||
return contactId==0 && chatId==0;
|
||||
}
|
||||
|
||||
@@ -11,9 +11,10 @@ import androidx.annotation.NonNull;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
import com.codewaves.stickyheadergrid.StickyHeaderGridAdapter;
|
||||
|
||||
import org.thoughtcrime.securesms.components.AudioView;
|
||||
import org.thoughtcrime.securesms.components.DocumentView;
|
||||
import org.thoughtcrime.securesms.components.WebxdcView;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioView;
|
||||
import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader.BucketedThreadMedia;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
import org.thoughtcrime.securesms.mms.DocumentSlide;
|
||||
@@ -31,7 +32,8 @@ class AllMediaDocumentsAdapter extends StickyHeaderGridAdapter {
|
||||
private final ItemClickListener itemClickListener;
|
||||
private final Set<DcMsg> selected;
|
||||
|
||||
private BucketedThreadMedia media;
|
||||
private BucketedThreadMedia media;
|
||||
private AudioPlaybackViewModel playbackViewModel;
|
||||
|
||||
private static class ViewHolder extends StickyHeaderGridAdapter.ItemViewHolder {
|
||||
private final DocumentView documentView;
|
||||
@@ -71,6 +73,10 @@ class AllMediaDocumentsAdapter extends StickyHeaderGridAdapter {
|
||||
this.media = media;
|
||||
}
|
||||
|
||||
public void setPlaybackViewModel(AudioPlaybackViewModel playbackViewModel) {
|
||||
this.playbackViewModel = playbackViewModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StickyHeaderGridAdapter.HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int headerType) {
|
||||
return new HeaderHolder(LayoutInflater.from(context).inflate(R.layout.contact_selection_list_divider, parent, false));
|
||||
@@ -97,7 +103,8 @@ class AllMediaDocumentsAdapter extends StickyHeaderGridAdapter {
|
||||
viewHolder.webxdcView.setVisibility(View.GONE);
|
||||
|
||||
viewHolder.audioView.setVisibility(View.VISIBLE);
|
||||
viewHolder.audioView.setAudio((AudioSlide)slide, dcMsg.getDuration());
|
||||
viewHolder.audioView.setPlaybackViewModel(playbackViewModel);
|
||||
viewHolder.audioView.setAudio((AudioSlide)slide);
|
||||
viewHolder.audioView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg));
|
||||
viewHolder.audioView.setOnLongClickListener(view -> { itemClickListener.onMediaLongClicked(dcMsg); return true; });
|
||||
viewHolder.audioView.disablePlayer(!selected.isEmpty());
|
||||
|
||||
@@ -16,6 +16,7 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -25,6 +26,7 @@ import com.b44t.messenger.DcEvent;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager;
|
||||
|
||||
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;
|
||||
@@ -54,7 +56,6 @@ public class AllMediaDocumentsFragment
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
dcContext = DcHelper.getContext(getContext());
|
||||
chatId = getArguments().getInt(CHAT_ID_EXTRA, -1);
|
||||
viewtype1 = getArguments().getInt(VIEWTYPE1, 0);
|
||||
viewtype2 = getArguments().getInt(VIEWTYPE2, 0);
|
||||
@@ -73,9 +74,11 @@ public class AllMediaDocumentsFragment
|
||||
// add padding to avoid content hidden behind system bars
|
||||
ViewUtil.applyWindowInsets(recyclerView, true, false, true, true);
|
||||
|
||||
this.recyclerView.setAdapter(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));
|
||||
this.recyclerView.setLayoutManager(gridManager);
|
||||
this.recyclerView.setHasFixedSize(true);
|
||||
|
||||
@@ -240,12 +243,13 @@ public class AllMediaDocumentsFragment
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem menuItem) {
|
||||
int itemId = menuItem.getItemId();
|
||||
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());
|
||||
handleDeleteMessages(chatId, getListAdapter().getSelectedMedia(), playbackViewModel::stopByIds, playbackViewModel::stopByIds);
|
||||
mode.finish();
|
||||
return true;
|
||||
} else if (itemId == R.id.share) {
|
||||
|
||||
@@ -52,7 +52,6 @@ public class AllMediaGalleryFragment
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
dcContext = DcHelper.getContext(getContext());
|
||||
chatId = getArguments().getInt(CHAT_ID_EXTRA, -1);
|
||||
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
|
||||
@@ -23,6 +23,7 @@ import androidx.work.WorkManager;
|
||||
import com.b44t.messenger.DcAccounts;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
import com.b44t.messenger.DcEventChannel;
|
||||
import com.b44t.messenger.DcEventEmitter;
|
||||
import com.b44t.messenger.FFITransport;
|
||||
|
||||
@@ -164,7 +165,8 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
throwable.printStackTrace(new PrintWriter(stringWriter, true));
|
||||
String errorMsg = "Android " + Build.VERSION.RELEASE +":\n" + stringWriter.getBuffer().toString();
|
||||
String subject = "ArcaneChat " + BuildConfig.VERSION_NAME + " Crash Report";
|
||||
errorMsg += "\n" + LogViewFragment.grabLogcat();
|
||||
String subject = "ArcaneChat " + BuildConfig.VERSION_NAME + "-" + BuildConfig.FLAVOR + " Crash Report";
|
||||
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
|
||||
@@ -195,7 +197,29 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
Util.runOnBackground(() -> {
|
||||
synchronized (initLock) {
|
||||
try {
|
||||
dcAccounts = new DcAccounts(new File(getFilesDir(), "accounts").getAbsolutePath());
|
||||
DcEventChannel eventChannel = new DcEventChannel();
|
||||
DcEventEmitter emitter = eventChannel.getEventEmitter();
|
||||
eventCenter = new DcEventCenter(this);
|
||||
|
||||
new Thread(() -> {
|
||||
Log.i(TAG, "Starting event loop");
|
||||
while (true) {
|
||||
DcEvent event = emitter.getNextEvent();
|
||||
if (event == null) {
|
||||
break;
|
||||
}
|
||||
if (isInitialized) {
|
||||
eventCenter.handleEvent(event);
|
||||
} else {
|
||||
// not fully initialized, only handle logging events,
|
||||
// ex. account migrations during DcAccounts initialization
|
||||
eventCenter.handleLogging(event);
|
||||
}
|
||||
}
|
||||
Log.i("DeltaChat", "shutting down event handler");
|
||||
}, "eventThread").start();
|
||||
|
||||
dcAccounts = new DcAccounts(new File(getFilesDir(), "accounts").getAbsolutePath(), eventChannel);
|
||||
Log.i(TAG, "DcAccounts created");
|
||||
rpc = new Rpc(new FFITransport(dcAccounts.getJsonrpcInstance()));
|
||||
Log.i(TAG, "Rpc created");
|
||||
@@ -235,28 +259,12 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
}
|
||||
dcContext = dcAccounts.getSelectedAccount();
|
||||
notificationCenter = new NotificationCenter(this);
|
||||
eventCenter = new DcEventCenter(this);
|
||||
dcLocationManager = new DcLocationManager(this, dcContext);
|
||||
|
||||
// Mark as initialized before starting threads that depend on it
|
||||
isInitialized = true;
|
||||
initLock.notifyAll();
|
||||
Log.i(TAG, "DcAccounts initialization complete");
|
||||
|
||||
new Thread(() -> {
|
||||
Log.i(TAG, "Starting event loop");
|
||||
DcEventEmitter emitter = dcAccounts.getEventEmitter();
|
||||
Log.i(TAG, "DcEventEmitter obtained");
|
||||
while (true) {
|
||||
DcEvent event = emitter.getNextEvent();
|
||||
if (event==null) {
|
||||
break;
|
||||
}
|
||||
eventCenter.handleEvent(event);
|
||||
}
|
||||
Log.i("DeltaChat", "shutting down event handler");
|
||||
}, "eventThread").start();
|
||||
|
||||
// set translations before starting I/O to avoid sending untranslated MDNs (issue #2288)
|
||||
DcHelper.setStockTranslations(this);
|
||||
|
||||
|
||||
@@ -50,11 +50,11 @@ public abstract class BaseConversationItem extends LinearLayout
|
||||
this.rpc = DcHelper.getRpc(context);
|
||||
}
|
||||
|
||||
protected void bind(@NonNull DcMsg messageRecord,
|
||||
@NonNull DcChat dcChat,
|
||||
@NonNull Set<DcMsg> batchSelected,
|
||||
boolean pulseHighlight,
|
||||
@NonNull Recipient 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;
|
||||
@@ -126,6 +126,8 @@ public abstract class BaseConversationItem extends LinearLayout
|
||||
|
||||
public void onClick(View v) {
|
||||
if (!shouldInterceptClicks(messageRecord) && parent != null) {
|
||||
// The click workaround on ConversationItem shall be revised.
|
||||
// In fact, it is probably better rethinking accessibility approach for the items.
|
||||
if (batchSelected.isEmpty() && Util.isTouchExplorationEnabled(context)) {
|
||||
BaseConversationItem.this.onAccessibilityClick();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import androidx.annotation.Nullable;
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
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;
|
||||
|
||||
@@ -17,7 +19,9 @@ public interface BindableConversationItem extends Unbindable {
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Set<DcMsg> batchSelected,
|
||||
@NonNull Recipient recipients,
|
||||
boolean pulseHighlight);
|
||||
boolean pulseHighlight,
|
||||
@Nullable AudioPlaybackViewModel playbackViewModel,
|
||||
AudioView.OnActionListener audioPlayPauseListener);
|
||||
|
||||
DcMsg getMessageRecord();
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import java.util.List;
|
||||
public class ContactMultiSelectionActivity extends ContactSelectionActivity {
|
||||
|
||||
public static final String CONTACTS_EXTRA = "contacts_extra";
|
||||
public static final String DESELECTED_CONTACTS_EXTRA = "deselected_contacts_extra";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle, boolean ready) {
|
||||
@@ -71,7 +72,9 @@ public class ContactMultiSelectionActivity extends ContactSelectionActivity {
|
||||
private void saveSelection() {
|
||||
Intent resultIntent = getIntent();
|
||||
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));
|
||||
setResult(RESULT_OK, resultIntent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +81,9 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
super.onOptionsItemSelected(item);
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home: super.onBackPressed(); return true;
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -22,6 +22,7 @@ import static org.thoughtcrime.securesms.util.ShareUtil.isRelayingMessageContent
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -29,6 +30,7 @@ import android.view.MenuInflater;
|
||||
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;
|
||||
@@ -56,6 +58,7 @@ 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;
|
||||
@@ -81,9 +84,11 @@ public class ContactSelectionListFragment extends Fragment
|
||||
private DcContext dcContext;
|
||||
|
||||
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;
|
||||
|
||||
@@ -113,6 +118,7 @@ public class ContactSelectionListFragment extends Fragment
|
||||
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);
|
||||
|
||||
// add padding to avoid content hidden behind system bars
|
||||
ViewUtil.applyWindowInsets(recyclerView, true, false, true, true);
|
||||
@@ -123,8 +129,8 @@ public class ContactSelectionListFragment extends Fragment
|
||||
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
|
||||
MenuInflater inflater = getActivity().getMenuInflater();
|
||||
inflater.inflate(R.menu.contact_list, menu);
|
||||
setCorrectMenuVisibility(menu);
|
||||
actionMode.setTitle("1");
|
||||
menu.findItem(R.id.menu_delete_selected).setVisible(!isMulti());
|
||||
updateActionModeState(actionMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -161,19 +167,16 @@ public class ContactSelectionListFragment extends Fragment
|
||||
|
||||
private void handleSelectAll() {
|
||||
getContactSelectionListAdapter().selectAll();
|
||||
updateActionModeTitle();
|
||||
updateActionModeState(actionMode);
|
||||
}
|
||||
|
||||
private void updateActionModeTitle() {
|
||||
actionMode.setTitle(String.valueOf(getContactSelectionListAdapter().getActionModeSelection().size()));
|
||||
}
|
||||
|
||||
private void setCorrectMenuVisibility(Menu menu) {
|
||||
ContactSelectionListAdapter adapter = getContactSelectionListAdapter();
|
||||
if (adapter.getActionModeSelection().size() > 1) {
|
||||
menu.findItem(R.id.menu_view_profile).setVisible(false);
|
||||
private void updateActionModeState(ActionMode actionMode) {
|
||||
int size = getContactSelectionListAdapter().getActionModeSelection().size();
|
||||
if (size == 0) {
|
||||
actionMode.finish();
|
||||
} else {
|
||||
menu.findItem(R.id.menu_view_profile).setVisible(true);
|
||||
actionMode.getMenu().findItem(R.id.menu_view_profile).setVisible(size == 1);
|
||||
actionMode.setTitle(String.valueOf(size));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,6 +229,15 @@ public class ContactSelectionListFragment extends Fragment
|
||||
return selected;
|
||||
}
|
||||
|
||||
public @NonNull List<Integer> getDeselectedContacts() {
|
||||
List<Integer> deselected = new LinkedList<>();
|
||||
if (deselectedContacts != null) {
|
||||
deselected.addAll(deselectedContacts);
|
||||
}
|
||||
|
||||
return deselected;
|
||||
}
|
||||
|
||||
private boolean isMulti() {
|
||||
return getActivity().getIntent().getBooleanExtra(MULTI_SELECT, false);
|
||||
}
|
||||
@@ -241,6 +253,7 @@ public class ContactSelectionListFragment extends Fragment
|
||||
isMulti(),
|
||||
true);
|
||||
selectedContacts = adapter.getSelectedContacts();
|
||||
deselectedContacts = new HashSet<>();
|
||||
ArrayList<Integer> preselectedContacts = getActivity().getIntent().getIntegerArrayListExtra(PRESELECTED_CONTACTS);
|
||||
if(preselectedContacts!=null) {
|
||||
selectedContacts.addAll(preselectedContacts);
|
||||
@@ -266,7 +279,16 @@ public class ContactSelectionListFragment extends Fragment
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<DcContactsLoader.Ret> loader, DcContactsLoader.Ret data) {
|
||||
((ContactSelectionListAdapter) recyclerView.getAdapter()).changeData(data);
|
||||
ContactSelectionListAdapter adapter = (ContactSelectionListAdapter) recyclerView.getAdapter();
|
||||
adapter.changeData(data);
|
||||
if (emptyView != null) {
|
||||
if (adapter.getItemCount() > 0 || TextUtils.isEmpty(cursorFilter)) {
|
||||
emptyView.setVisibility(View.GONE);
|
||||
} else {
|
||||
emptyView.setText(getString(R.string.search_no_result_for_x, cursorFilter));
|
||||
emptyView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -280,10 +302,7 @@ public class ContactSelectionListFragment extends Fragment
|
||||
{
|
||||
if (handleActionMode) {
|
||||
if (actionMode != null) {
|
||||
Menu menu = actionMode.getMenu();
|
||||
setCorrectMenuVisibility(menu);
|
||||
updateActionModeTitle();
|
||||
finishActionModeIfSelectionIsEmpty();
|
||||
updateActionModeState(actionMode);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -303,12 +322,14 @@ public class ContactSelectionListFragment extends Fragment
|
||||
}
|
||||
|
||||
selectedContacts.add(contactId);
|
||||
deselectedContacts.remove(contactId);
|
||||
contact.setChecked(true);
|
||||
if (onContactSelectedListener != null) {
|
||||
onContactSelectedListener.onContactSelected(contactId);
|
||||
}
|
||||
} else {
|
||||
selectedContacts.remove(contactId);
|
||||
deselectedContacts.add(contactId);
|
||||
contact.setChecked(false);
|
||||
if (onContactSelectedListener != null) {
|
||||
onContactSelectedListener.onContactDeselected(contactId);
|
||||
@@ -321,17 +342,11 @@ public class ContactSelectionListFragment extends Fragment
|
||||
if (actionMode == null) {
|
||||
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback);
|
||||
} else {
|
||||
finishActionModeIfSelectionIsEmpty();
|
||||
updateActionModeState(actionMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void finishActionModeIfSelectionIsEmpty() {
|
||||
if (getContactSelectionListAdapter().getActionModeSelection().size() == 0) {
|
||||
actionMode.finish();
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
|
||||
this.onContactSelectedListener = onContactSelectedListener;
|
||||
}
|
||||
@@ -355,6 +370,7 @@ public class ContactSelectionListFragment extends Fragment
|
||||
int contactId = data.getIntExtra(NewContactActivity.CONTACT_ID_EXTRA, 0);
|
||||
if (contactId != 0) {
|
||||
selectedContacts.add(contactId);
|
||||
deselectedContacts.remove(contactId);
|
||||
}
|
||||
getLoaderManager().restartLoader(0, null, ContactSelectionListFragment.this);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ClipData;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
@@ -57,6 +58,7 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
@@ -64,7 +66,12 @@ import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.media3.session.MediaController;
|
||||
import androidx.media3.session.SessionCommand;
|
||||
import androidx.media3.session.SessionToken;
|
||||
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContact;
|
||||
@@ -75,7 +82,7 @@ import com.b44t.messenger.DcMsg;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||
import org.thoughtcrime.securesms.audio.AudioRecorder;
|
||||
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
|
||||
import org.thoughtcrime.securesms.calls.CallUtil;
|
||||
import org.thoughtcrime.securesms.components.AnimatingToggle;
|
||||
import org.thoughtcrime.securesms.components.AttachmentTypeSelector;
|
||||
import org.thoughtcrime.securesms.components.ComposeText;
|
||||
@@ -85,6 +92,8 @@ import org.thoughtcrime.securesms.components.InputPanel;
|
||||
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener;
|
||||
import org.thoughtcrime.securesms.components.ScaleStableImageView;
|
||||
import org.thoughtcrime.securesms.components.SendButton;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioView;
|
||||
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
|
||||
import org.thoughtcrime.securesms.connect.AccountManager;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
@@ -103,19 +112,19 @@ import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.scribbles.ScribbleActivity;
|
||||
import org.thoughtcrime.securesms.service.AudioPlaybackService;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Prefs;
|
||||
import org.thoughtcrime.securesms.util.ShareUtil;
|
||||
import org.thoughtcrime.securesms.util.SendRelayedMessageUtil;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.ShareUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||
import org.thoughtcrime.securesms.util.guava.Optional;
|
||||
import org.thoughtcrime.securesms.util.views.ProgressDialog;
|
||||
import org.thoughtcrime.securesms.video.recode.VideoRecoder;
|
||||
import org.thoughtcrime.securesms.calls.CallUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
@@ -137,14 +146,13 @@ import chat.delta.util.SettableFuture;
|
||||
*/
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
implements ConversationFragment.ConversationFragmentListener,
|
||||
AttachmentManager.AttachmentListener,
|
||||
SearchView.OnQueryTextListener,
|
||||
DcEventCenter.DcEventDelegate,
|
||||
OnKeyboardShownListener,
|
||||
InputPanel.Listener,
|
||||
InputPanel.MediaListener
|
||||
{
|
||||
implements ConversationFragment.ConversationFragmentListener,
|
||||
AttachmentManager.AttachmentListener,
|
||||
SearchView.OnQueryTextListener,
|
||||
DcEventCenter.DcEventDelegate,
|
||||
OnKeyboardShownListener,
|
||||
InputPanel.Listener,
|
||||
InputPanel.MediaListener, AudioView.OnActionListener {
|
||||
private static final String TAG = ConversationActivity.class.getSimpleName();
|
||||
|
||||
public static final String ACCOUNT_ID_EXTRA = "account_id";
|
||||
@@ -184,10 +192,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private MediaKeyboard emojiPicker;
|
||||
protected HidingLinearLayout quickAttachmentToggle;
|
||||
private InputPanel inputPanel;
|
||||
private @Nullable MediaController mediaController;
|
||||
private com.google.common.util.concurrent.ListenableFuture<MediaController> mediaControllerFuture;
|
||||
private AudioPlaybackViewModel playbackViewModel;
|
||||
|
||||
private ApplicationContext context;
|
||||
private Recipient recipient;
|
||||
private DcContext dcContext;
|
||||
private Rpc rpc;
|
||||
private DcChat dcChat = new DcChat(0, 0);
|
||||
private int chatId;
|
||||
@@ -196,11 +206,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private boolean isSecurityInitialized = false;
|
||||
private boolean successfulForwardingAttempt = false;
|
||||
private boolean isEditing = false;
|
||||
private boolean switchedProfile = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle state, boolean ready) {
|
||||
this.context = ApplicationContext.getInstance(getApplicationContext());
|
||||
this.dcContext = DcHelper.getContext(context);
|
||||
this.rpc = DcHelper.getRpc(context);
|
||||
|
||||
supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY);
|
||||
@@ -217,6 +227,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
initializeActionBar();
|
||||
initializeViews();
|
||||
initializeResources();
|
||||
|
||||
playbackViewModel = new ViewModelProvider(this).get(AudioPlaybackViewModel.class);
|
||||
initializeMediaController();
|
||||
|
||||
initializeSecurity(false, isDefaultSms).addListener(new AssertedSuccessListener<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean result) {
|
||||
@@ -237,17 +251,64 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
});
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (container.isInputOpen()) {
|
||||
container.hideCurrentInput(composeText);
|
||||
} else {
|
||||
handleReturnToConversationList();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setDcEventListener();
|
||||
handleRelaying();
|
||||
}
|
||||
|
||||
private void setDcEventListener() {
|
||||
DcEventCenter eventCenter = DcHelper.getEventCenter(this);
|
||||
eventCenter.addObserver(DcContext.DC_EVENT_CHAT_MODIFIED, this);
|
||||
eventCenter.addObserver(DcContext.DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED, this);
|
||||
eventCenter.addObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
|
||||
// first cleanup in case it was already registered for other chat
|
||||
eventCenter.removeObservers(this);
|
||||
|
||||
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_CHAT_MODIFIED, this);
|
||||
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED, this);
|
||||
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
|
||||
|
||||
if (!isMultiUser()) {
|
||||
eventCenter.addObserver(DcContext.DC_EVENT_INCOMING_MSG, this);
|
||||
eventCenter.addObserver(DcContext.DC_EVENT_MSG_READ, this);
|
||||
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_INCOMING_MSG, this);
|
||||
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_MSG_READ, this);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
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) {
|
||||
if (mediaController == null) return;
|
||||
|
||||
Bundle commandArgs = new Bundle();
|
||||
commandArgs.putString("activity_class", activityClassName);
|
||||
if (extras != null) {
|
||||
commandArgs.putAll(extras);
|
||||
}
|
||||
|
||||
handleRelaying();
|
||||
SessionCommand updateContextCommand =
|
||||
new SessionCommand("UPDATE_ACTIVITY_CONTEXT", Bundle.EMPTY);
|
||||
|
||||
mediaController.sendCustomCommand(updateContextCommand, commandArgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -273,6 +334,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
});
|
||||
|
||||
setDcEventListener(); // reset event listener
|
||||
handleRelaying();
|
||||
|
||||
if (fragment != null) {
|
||||
@@ -299,7 +361,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
titleView.setTitle(glideRequests, dcChat);
|
||||
|
||||
DcHelper.getNotificationCenter(this).updateVisibleChat(dcContext.getAccountId(), chatId);
|
||||
try {
|
||||
int accId = rpc.getSelectedAccountId();
|
||||
DcHelper.getNotificationCenter(this).updateVisibleChat(accId, chatId);
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "rpc.getSelectedAccountId() failed", e);
|
||||
}
|
||||
|
||||
|
||||
attachmentManager.onResume();
|
||||
}
|
||||
@@ -313,7 +381,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
DcHelper.getNotificationCenter(this).clearVisibleChat();
|
||||
if (isFinishing()) overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right);
|
||||
inputPanel.onPause();
|
||||
AudioSlidePlayer.stopAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -333,6 +400,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
DcHelper.getEventCenter(this).removeObservers(this);
|
||||
if (mediaController != null) {
|
||||
MediaController.releaseFuture(mediaControllerFuture);
|
||||
mediaController = null;
|
||||
playbackViewModel.setMediaController(null);
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@@ -389,7 +461,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
break;
|
||||
|
||||
case GROUP_EDIT:
|
||||
dcChat = dcContext.getChat(chatId);
|
||||
dcChat = DcHelper.getContext(context).getChat(chatId);
|
||||
titleView.setTitle(glideRequests, dcChat);
|
||||
break;
|
||||
|
||||
@@ -545,8 +617,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
} else if (itemId == R.id.menu_show_map) {
|
||||
WebxdcActivity.openMaps(this, chatId);
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_start_call) {
|
||||
CallUtil.startCall(this, chatId);
|
||||
} else if (itemId == R.id.menu_start_audio_call) {
|
||||
CallUtil.startCall(this, chatId, false);
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_start_video_call) {
|
||||
CallUtil.startCall(this, chatId, true);
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_all_media) {
|
||||
handleAllMedia();
|
||||
@@ -568,15 +643,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (container.isInputOpen()){
|
||||
container.hideCurrentInput(composeText);
|
||||
} else {
|
||||
handleReturnToConversationList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onKeyboardShown() {
|
||||
inputPanel.onKeyboardShown();
|
||||
@@ -599,6 +665,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
//////// Event Handlers
|
||||
|
||||
private void handleEphemeralMessages() {
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
int preselected = dcContext.getChatEphemeralTimer(chatId);
|
||||
EphemeralMessagesDialog.show(this, preselected, duration -> {
|
||||
dcContext.setChatEphemeralTimer(chatId, (int) duration);
|
||||
@@ -610,6 +677,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void handleReturnToConversationList(@Nullable Bundle extras) {
|
||||
if (switchedProfile) { // force refreshing of chatlist
|
||||
if (extras == null) extras = new Bundle();
|
||||
extras.putInt(ConversationListFragment.RELOAD_LIST, 1);
|
||||
}
|
||||
|
||||
playbackViewModel.stopNonMessageAudioPlayback();
|
||||
|
||||
boolean archived = getIntent().getBooleanExtra(FROM_ARCHIVED_CHATS_EXTRA, false);
|
||||
Intent intent = new Intent(this, (archived ? ConversationListArchiveActivity.class : ConversationListActivity.class));
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
@@ -619,6 +693,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void handleMuteNotifications() {
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
if(!dcChat.isMuted()) {
|
||||
MuteDialog.show(this, duration -> {
|
||||
dcContext.setChatMuteDuration(chatId, duration);
|
||||
@@ -654,7 +729,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
.setMessage(getString(R.string.ask_leave_group))
|
||||
.setPositiveButton(leaveLabel, (d, which) -> {
|
||||
dcContext.removeContactFromChat(chatId, DcContact.DC_CONTACT_ID_SELF);
|
||||
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)
|
||||
@@ -663,6 +738,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void handleArchiveChat() {
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
int newVisibility = isArchived() ?
|
||||
DcChat.DC_CHAT_VISIBILITY_NORMAL : DcChat.DC_CHAT_VISIBILITY_ARCHIVED;
|
||||
dcContext.setChatVisibility(chatId, newVisibility);
|
||||
@@ -678,7 +754,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
.setMessage(getResources().getString(R.string.ask_delete_named_chat, dcChat.getName()))
|
||||
.setPositiveButton(R.string.delete, (d, which) -> {
|
||||
dcContext.deleteChat(chatId);
|
||||
DcHelper.getContext(context).deleteChat(chatId);
|
||||
DirectShareUtil.clearShortcut(this, chatId);
|
||||
finish();
|
||||
})
|
||||
@@ -708,6 +784,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void handleForwarding() {
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
DcChat dcChat = dcContext.getChat(chatId);
|
||||
if (dcChat.isSelfTalk()) {
|
||||
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
|
||||
@@ -756,7 +833,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
|
||||
} else {
|
||||
Uri uri = uriList.isEmpty()? null : uriList.get(0);
|
||||
dcContext.setDraft(chatId, SendRelayedMessageUtil.createMessage(this, uri, ShareUtil.getSharedType(this), null, null, getSharedText(this)));
|
||||
DcHelper.getContext(context).setDraft(chatId, SendRelayedMessageUtil.createMessage(this, uri, ShareUtil.getSharedType(this), null, null, getSharedText(this)));
|
||||
}
|
||||
initializeDraft();
|
||||
}
|
||||
@@ -771,7 +848,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private ListenableFuture<Boolean> initializeDraft() {
|
||||
isEditing = false;
|
||||
final SettableFuture<Boolean> future = new SettableFuture<>();
|
||||
DcMsg draft = dcContext.getDraft(chatId);
|
||||
DcMsg draft = DcHelper.getContext(context).getDraft(chatId);
|
||||
final String sharedText = ShareUtil.getSharedText(this);
|
||||
|
||||
if (!draft.isOk()) {
|
||||
@@ -932,7 +1009,15 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void initializeBackground() {
|
||||
String backgroundImagePath = Prefs.getBackgroundImagePath(this, dcContext.getAccountId());
|
||||
int accId;
|
||||
try {
|
||||
accId = rpc.getSelectedAccountId();
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "rpc.getSelectedAccountId() failed", e);
|
||||
return;
|
||||
}
|
||||
|
||||
String backgroundImagePath = Prefs.getBackgroundImagePath(this, accId);
|
||||
Drawable background;
|
||||
if(!backgroundImagePath.isEmpty()) {
|
||||
background = Drawable.createFromPath(backgroundImagePath);
|
||||
@@ -958,33 +1043,44 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, dcContext.getAccountId());
|
||||
if (accountId != dcContext.getAccountId()) {
|
||||
int selectedAccId = DcHelper.getContext(context).getAccountId();
|
||||
int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, selectedAccId);
|
||||
if (accountId != selectedAccId) {
|
||||
switchedProfile = true;
|
||||
AccountManager.getInstance().switchAccount(context, accountId);
|
||||
fragment.dcContext = dcContext = context.getDcContext();
|
||||
initializeBackground();
|
||||
}
|
||||
chatId = getIntent().getIntExtra(CHAT_ID_EXTRA, -1);
|
||||
if(chatId == DcChat.DC_CHAT_NO_CHAT)
|
||||
throw new IllegalStateException("can't display a conversation for no chat.");
|
||||
dcChat = dcContext.getChat(chatId);
|
||||
dcChat = DcHelper.getContext(context).getChat(chatId);
|
||||
recipient = new Recipient(this, dcChat);
|
||||
glideRequests = GlideApp.with(this);
|
||||
|
||||
setComposePanelVisibility();
|
||||
setComposePanelVisibility(true);
|
||||
initializeContactRequest();
|
||||
}
|
||||
|
||||
private void setComposePanelVisibility() {
|
||||
private void setComposePanelVisibility(boolean isInitialization) {
|
||||
if (dcChat.canSend()) {
|
||||
composePanel.setVisibility(View.VISIBLE);
|
||||
attachmentManager.setHidden(false);
|
||||
inputPanel.setSubjectVisible(!dcChat.isEncrypted());
|
||||
// FIXME: disabled for now to avoid problems with chat scrolling and keyboard covering input bar
|
||||
// ViewUtil.forceApplyWindowInsets(findViewById(R.id.root_layout), true, false, true, true);
|
||||
// fragment.handleRemoveBottomInsets();
|
||||
} else {
|
||||
composePanel.setVisibility(View.GONE);
|
||||
attachmentManager.setHidden(true);
|
||||
hideSoftKeyboard();
|
||||
inputPanel.setSubjectVisible(false);
|
||||
// FIXME: disabled for now to avoid problems with chat scrolling and keyboard covering input bar
|
||||
/*
|
||||
if (isInitialization) {
|
||||
ViewUtil.forceApplyWindowInsets(findViewById(R.id.root_layout), true, false, true, false);
|
||||
fragment.handleAddBottomInsets();
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1021,11 +1117,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
return new SettableFuture<>(false);
|
||||
}
|
||||
|
||||
return attachmentManager.setMedia(glideRequests, uri, null, mediaType, 0, 0, chatId);
|
||||
return attachmentManager.setMedia(glideRequests, uri, null, mediaType, 0, 0, chatId, playbackViewModel);
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> setMedia(DcMsg msg, @NonNull MediaType mediaType) {
|
||||
return attachmentManager.setMedia(glideRequests, Uri.fromFile(new File(msg.getFile())), msg, mediaType, 0, 0, chatId);
|
||||
return attachmentManager.setMedia(glideRequests, Uri.fromFile(new File(msg.getFile())), msg, mediaType, 0, 0, chatId, playbackViewModel);
|
||||
}
|
||||
|
||||
private void addAttachmentContactInfo(int contactId) {
|
||||
@@ -1034,11 +1130,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] vcard = rpc.makeVcard(dcContext.getAccountId(), Collections.singletonList(contactId)).getBytes();
|
||||
byte[] vcard = rpc.makeVcard(rpc.getSelectedAccountId(), Collections.singletonList(contactId)).getBytes();
|
||||
String mimeType = "application/octet-stream";
|
||||
setMedia(PersistentBlobProvider.getInstance().create(this, vcard, mimeType, "vcard.vcf"), MediaType.DOCUMENT);
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "makeVcard() failed", e);
|
||||
Log.e(TAG, "RPC failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1077,6 +1173,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
inputPanel.clearSubject();
|
||||
}
|
||||
|
||||
// Stop draft audio playback regardless, since it is unlikely
|
||||
// we will need background playback for drafts
|
||||
playbackViewModel.stopNonMessageAudioPlayback();
|
||||
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
DcMsg msg = null;
|
||||
int recompress = 0;
|
||||
@@ -1209,7 +1310,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
if (refreshFragment) {
|
||||
fragment.reload(recipient, chatId);
|
||||
DcHelper.getNotificationCenter(this).updateVisibleChat(dcContext.getAccountId(), chatId);
|
||||
try {
|
||||
int accId = rpc.getSelectedAccountId();
|
||||
DcHelper.getNotificationCenter(this).updateVisibleChat(accId, chatId);
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "rpc.getSelectedAccountId() failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
fragment.scrollToBottom();
|
||||
@@ -1371,6 +1477,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
Optional<QuoteModel> quote = inputPanel.getQuote();
|
||||
inputPanel.clearQuote();
|
||||
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_STICKER);
|
||||
if (quote.isPresent()) {
|
||||
msg.setQuote(quote.get().getQuotedMsg());
|
||||
@@ -1382,6 +1489,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
// Listeners
|
||||
|
||||
@Override
|
||||
public void onPlayPauseButtonClicked(View view) {
|
||||
addActivityContext(
|
||||
this.getIntent().getExtras(),
|
||||
this.getClass().getName()
|
||||
);
|
||||
}
|
||||
|
||||
private class AttachmentTypeListener implements AttachmentTypeSelector.AttachmentClickedListener {
|
||||
@Override
|
||||
public void onClick(int type) {
|
||||
@@ -1492,7 +1607,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
if (isEditing) composeText.setText("");
|
||||
isEditing = false;
|
||||
// If you modify these lines you may also want to modify ConversationItem.setQuote():
|
||||
Recipient author = new Recipient(this, dcContext.getContact(msg.getFromId()));
|
||||
Recipient author = new Recipient(this, DcHelper.getContext(context).getContact(msg.getFromId()));
|
||||
|
||||
SlideDeck slideDeck = new SlideDeck();
|
||||
if (msg.hasFile()) {
|
||||
@@ -1515,7 +1630,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
public void handleEditMessage(DcMsg msg) {
|
||||
isEditing = true;
|
||||
Recipient author = new Recipient(this, dcContext.getContact(msg.getFromId()));
|
||||
Recipient author = new Recipient(this, DcHelper.getContext(context).getContact(msg.getFromId()));
|
||||
|
||||
SlideDeck slideDeck = new SlideDeck();
|
||||
String text = msg.getSummarytext(500);
|
||||
@@ -1540,6 +1655,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
public void handleEvent(@NonNull DcEvent event) {
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
if (event.getAccountId() != dcContext.getAccountId()) return;
|
||||
|
||||
int eventId = event.getId();
|
||||
if ((eventId == DcContext.DC_EVENT_CHAT_MODIFIED && event.getData1Int() == chatId)
|
||||
|| (eventId == DcContext.DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED && event.getData1Int() == chatId)
|
||||
@@ -1547,7 +1665,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
dcChat = dcContext.getChat(chatId);
|
||||
titleView.setTitle(glideRequests, dcChat);
|
||||
initializeSecurity(isSecureText, isDefaultSms);
|
||||
setComposePanelVisibility();
|
||||
setComposePanelVisibility(false);
|
||||
initializeContactRequest();
|
||||
} else if ((eventId == DcContext.DC_EVENT_INCOMING_MSG
|
||||
|| eventId == DcContext.DC_EVENT_MSG_READ)
|
||||
@@ -1626,7 +1744,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
String normQuery = query.trim();
|
||||
searchResult = dcContext.searchMsgs(chatId, normQuery);
|
||||
searchResult = DcHelper.getContext(context).searchMsgs(chatId, normQuery);
|
||||
|
||||
if(searchResult.length>0) {
|
||||
searchResultPosition = searchResult.length - 1;
|
||||
@@ -1657,7 +1775,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
messageRequestBottomView.setVisibility(View.VISIBLE);
|
||||
messageRequestBottomView.setAcceptOnClickListener(v -> {
|
||||
dcContext.acceptChat(chatId);
|
||||
DcHelper.getContext(context).acceptChat(chatId);
|
||||
messageRequestBottomView.setVisibility(View.GONE);
|
||||
composePanel.setVisibility(View.VISIBLE);
|
||||
});
|
||||
@@ -1675,7 +1793,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
// avoid showing compose panel on receiving DC_EVENT_CONTACTS_CHANGED for the chat that is no longer a request after blocking
|
||||
DcHelper.getEventCenter(this).removeObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
|
||||
|
||||
dcContext.blockChat(chatId);
|
||||
DcHelper.getContext(context).blockChat(chatId);
|
||||
Bundle extras = new Bundle();
|
||||
extras.putInt(ConversationListFragment.RELOAD_LIST, 1);
|
||||
handleReturnToConversationList(extras);
|
||||
|
||||
@@ -34,6 +34,8 @@ import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioView;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -59,6 +61,7 @@ import java.util.Set;
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
// FIXME: this breaks type checks, that is why there are so many casts.
|
||||
public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
extends RecyclerView.Adapter
|
||||
implements StickyHeaderDecoration.StickyHeaderAdapter<HeaderViewHolder>
|
||||
@@ -97,6 +100,8 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
private long pulseHighlightingSince = -1;
|
||||
private int lastSeenPosition = -1;
|
||||
private long lastSeen = -1;
|
||||
private AudioPlaybackViewModel playbackViewModel;
|
||||
private AudioView.OnActionListener audioPlayPauseListener;
|
||||
|
||||
protected static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public <V extends View & BindableConversationItem> ViewHolder(final @NonNull V itemView) {
|
||||
@@ -170,6 +175,14 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
return fromDb;
|
||||
}
|
||||
|
||||
public void setPlaybackViewModel(AudioPlaybackViewModel playbackViewModel) {
|
||||
this.playbackViewModel = playbackViewModel;
|
||||
}
|
||||
|
||||
public void setAudioPlayPauseListener(AudioView.OnActionListener audioPlayPauseListener) {
|
||||
this.audioPlayPauseListener = audioPlayPauseListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position of the message with msgId in the chat list, counted from the top
|
||||
*/
|
||||
@@ -237,7 +250,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
long elapsed = now - pulseHighlightingSince;
|
||||
boolean pulseHighlight = (positionCurrentlyPulseHighlighting == position && elapsed < PULSE_HIGHLIGHT_MILLIS);
|
||||
|
||||
holder.getItem().bind(getMsg(position), dcChat, glideRequests, batchSelected, recipient, pulseHighlight);
|
||||
holder.getItem().bind(getMsg(position), dcChat, glideRequests, batchSelected, recipient, pulseHighlight, playbackViewModel, audioPlayPauseListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -41,6 +41,7 @@ import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
|
||||
@@ -52,6 +53,7 @@ import com.b44t.messenger.DcEvent;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import org.thoughtcrime.securesms.ConversationAdapter.ItemClickListener;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||
import org.thoughtcrime.securesms.components.reminder.DozeReminder;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
@@ -68,6 +70,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.ConversationAdaptiveActionsToolbar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -75,6 +78,9 @@ import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public class ConversationFragment extends MessageSelectorFragment
|
||||
{
|
||||
@@ -97,17 +103,21 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
private StickyHeaderDecoration dateDecoration;
|
||||
private View scrollToBottomButton;
|
||||
private View floatingLocationButton;
|
||||
private View bottomDivider;
|
||||
private AddReactionView addReactionView;
|
||||
private TextView noMessageTextView;
|
||||
private Timer reloadTimer;
|
||||
|
||||
public boolean isPaused;
|
||||
private Debouncer markseenDebouncer;
|
||||
private Rpc rpc;
|
||||
private boolean pendingAddBottomInsets;
|
||||
private boolean pendingRemoveBottomInsets;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
this.dcContext = DcHelper.getContext(getContext());
|
||||
rpc = DcHelper.getRpc(getContext());
|
||||
|
||||
DcEventCenter eventCenter = DcHelper.getEventCenter(getContext());
|
||||
eventCenter.addObserver(DcContext.DC_EVENT_INCOMING_MSG, this);
|
||||
@@ -136,6 +146,7 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
floatingLocationButton = ViewUtil.findById(view, R.id.floating_location_button);
|
||||
addReactionView = ViewUtil.findById(view, R.id.add_reaction_view);
|
||||
noMessageTextView = ViewUtil.findById(view, R.id.no_messages_text_view);
|
||||
bottomDivider = ViewUtil.findById(view, R.id.bottom_divider);
|
||||
|
||||
scrollToBottomButton.setOnClickListener(v -> scrollToBottom());
|
||||
|
||||
@@ -154,18 +165,32 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
// with hardware layers, drawing may result in errors as "OpenGLRenderer: Path too large to be rendered into a texture"
|
||||
list.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
|
||||
|
||||
if (pendingAddBottomInsets) {
|
||||
bottomDivider.setVisibility(View.GONE);
|
||||
ViewUtil.forceApplyWindowInsets(list, false, true, false, true);
|
||||
ViewUtil.forceApplyWindowInsetsAsMargin(scrollToBottomButton, true, true, true, true);
|
||||
pendingAddBottomInsets = false;
|
||||
}
|
||||
|
||||
if (pendingRemoveBottomInsets) {
|
||||
bottomDivider.setVisibility(View.VISIBLE);
|
||||
ViewUtil.forceApplyWindowInsets(list, false, true, false, false);
|
||||
ViewUtil.forceApplyWindowInsetsAsMargin(scrollToBottomButton, true, true, true, false);
|
||||
pendingRemoveBottomInsets = false;
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle bundle) {
|
||||
super.onActivityCreated(bundle);
|
||||
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
initializeResources();
|
||||
initializeListAdapter();
|
||||
}
|
||||
|
||||
private void setNoMessageText() {
|
||||
private void setNoMessageText() {
|
||||
DcChat dcChat = getListAdapter().getChat();
|
||||
if(dcChat.isMultiUser()){
|
||||
if (dcChat.isInBroadcast() || dcChat.isOutBroadcast()) {
|
||||
@@ -192,6 +217,28 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
}
|
||||
}
|
||||
|
||||
public void handleAddBottomInsets() {
|
||||
if (bottomDivider != null) {
|
||||
bottomDivider.setVisibility(View.GONE);
|
||||
ViewUtil.forceApplyWindowInsets(list, false, true, false, true);
|
||||
ViewUtil.forceApplyWindowInsetsAsMargin(scrollToBottomButton, false, false, false, true);
|
||||
pendingAddBottomInsets = false;
|
||||
} else {
|
||||
pendingAddBottomInsets = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void handleRemoveBottomInsets() {
|
||||
if (bottomDivider != null) {
|
||||
bottomDivider.setVisibility(View.VISIBLE);
|
||||
ViewUtil.forceApplyWindowInsets(list, false, true, false, false);
|
||||
ViewUtil.forceApplyWindowInsetsAsMargin(scrollToBottomButton, false, false, false, false);
|
||||
pendingRemoveBottomInsets = false;
|
||||
} else {
|
||||
pendingRemoveBottomInsets = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
DcHelper.getEventCenter(getContext()).removeObservers(this);
|
||||
@@ -208,8 +255,13 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
Util.runOnBackground(() -> dcContext.marknoticedChat((int) chatId));
|
||||
Util.runOnBackground(() -> {
|
||||
try {
|
||||
rpc.marknoticedChat(rpc.getSelectedAccountId(), (int) chatId);
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "RPC error", e);
|
||||
}
|
||||
});
|
||||
if (list.getAdapter() != null) {
|
||||
list.getAdapter().notifyDataSetChanged();
|
||||
}
|
||||
@@ -281,6 +333,10 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
if (this.recipient != null && this.chatId != -1) {
|
||||
ConversationAdapter adapter = new ConversationAdapter(getActivity(), this.recipient.getChat(), GlideApp.with(this), selectionClickListener, this.recipient);
|
||||
list.setAdapter(adapter);
|
||||
AudioPlaybackViewModel playbackViewModel =
|
||||
new ViewModelProvider(requireActivity()).get(AudioPlaybackViewModel.class);
|
||||
adapter.setPlaybackViewModel(playbackViewModel);
|
||||
adapter.setAudioPlayPauseListener(((ConversationActivity) requireActivity()));
|
||||
|
||||
if (dateDecoration != null) {
|
||||
list.removeItemDecoration(dateDecoration);
|
||||
@@ -288,7 +344,12 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
dateDecoration = new StickyHeaderDecoration(adapter, false, false);
|
||||
list.addItemDecoration(dateDecoration);
|
||||
|
||||
int freshMsgs = dcContext.getFreshMsgCount((int) chatId);
|
||||
int freshMsgs = 0;
|
||||
try {
|
||||
freshMsgs = rpc.getFreshMsgCnt(rpc.getSelectedAccountId(), (int) chatId);
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "RPC error", e);
|
||||
}
|
||||
SetStartingPositionLinearLayoutManager layoutManager = (SetStartingPositionLinearLayoutManager) list.getLayoutManager();
|
||||
if (startingPosition > -1) {
|
||||
layoutManager.setStartingPosition(startingPosition);
|
||||
@@ -392,7 +453,14 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
}
|
||||
|
||||
public void handleClearChat() {
|
||||
handleDeleteMessages((int) chatId, getListAdapter().getMessageIds());
|
||||
AudioPlaybackViewModel playbackViewModel =
|
||||
new ViewModelProvider(requireActivity()).get(AudioPlaybackViewModel.class);
|
||||
|
||||
handleDeleteMessages(
|
||||
(int) chatId,
|
||||
getListAdapter().getMessageIds(),
|
||||
playbackViewModel::stopByIds,
|
||||
playbackViewModel::stopByIds);
|
||||
}
|
||||
|
||||
private ConversationAdapter getListAdapter() {
|
||||
@@ -448,6 +516,7 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
DcContext dcContext = DcHelper.getContext(getContext());
|
||||
DcMsg prevMsg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
|
||||
for (DcMsg msg : dcMsgsList) {
|
||||
if (result.length() > 0) {
|
||||
@@ -476,9 +545,13 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
private void handleForwardMessage(final Set<DcMsg> messageRecords) {
|
||||
Intent composeIntent = new Intent();
|
||||
int[] msgIds = DcMsg.msgSetToIds(messageRecords);
|
||||
setForwardingMessageIds(composeIntent, msgIds);
|
||||
ConversationListRelayingActivity.start(this, composeIntent);
|
||||
getActivity().overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out);
|
||||
try {
|
||||
setForwardingMessageIds(composeIntent, msgIds, rpc.getSelectedAccountId());
|
||||
ConversationListRelayingActivity.start(this, composeIntent);
|
||||
getActivity().overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out);
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "RPC error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@@ -504,6 +577,7 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
private void handleReplyMessagePrivately(final DcMsg msg) {
|
||||
|
||||
if (getActivity() != null) {
|
||||
DcContext dcContext = DcHelper.getContext(getActivity());
|
||||
int privateChatId = dcContext.createChatByContactId(msg.getFromId());
|
||||
DcMsg replyMsg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
|
||||
replyMsg.setQuote(msg);
|
||||
@@ -520,10 +594,14 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
private void handleToggleSave(final Set<DcMsg> messageRecords) {
|
||||
DcMsg msg = getSelectedMessageRecord(messageRecords);
|
||||
if (msg.getSavedMsgId() != 0) {
|
||||
dcContext.deleteMsgs(new int[]{msg.getSavedMsgId()});
|
||||
} else {
|
||||
dcContext.saveMsgs(new int[]{msg.getId()});
|
||||
try {
|
||||
if (msg.getSavedMsgId() != 0) {
|
||||
rpc.deleteMessages(rpc.getSelectedAccountId(), Collections.singletonList(msg.getSavedMsgId()));
|
||||
} else {
|
||||
rpc.saveMsgs(rpc.getSelectedAccountId(), Collections.singletonList(msg.getId()));
|
||||
}
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "RPC error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,7 +671,7 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
}
|
||||
|
||||
private void updateLocationButton() {
|
||||
floatingLocationButton.setVisibility(dcContext.isSendingLocationsToChat((int) chatId)? View.VISIBLE : View.GONE);
|
||||
floatingLocationButton.setVisibility(DcHelper.getContext(getContext()).isSendingLocationsToChat((int) chatId)? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void scrollAndHighlight(final int pos, boolean smooth) {
|
||||
@@ -729,16 +807,20 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
return;
|
||||
}
|
||||
|
||||
int[] ids = new int[lastPos - firstPos + 1];
|
||||
int index = 0;
|
||||
ArrayList<Integer> ids = new ArrayList<>(lastPos - firstPos + 1);
|
||||
for(int pos = firstPos; pos <= lastPos; pos++) {
|
||||
DcMsg message = ((ConversationAdapter) list.getAdapter()).getMsg(pos);
|
||||
if (message.getFromId() != DC_CONTACT_ID_SELF) {
|
||||
ids[index] = message.getId();
|
||||
index++;
|
||||
ids.add(message.getId());
|
||||
}
|
||||
}
|
||||
Util.runOnAnyBackgroundThread(() -> dcContext.markseenMsgs(ids));
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
try {
|
||||
rpc.markseenMsgs(rpc.getSelectedAccountId(), ids);
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "RPC error", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class ConversationFragmentItemClickListener implements ItemClickListener {
|
||||
@@ -783,7 +865,7 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
startActivity(intent);
|
||||
}
|
||||
else {
|
||||
String self_mail = dcContext.getConfig("configured_mail_user");
|
||||
String self_mail = DcHelper.getContext(getContext()).getConfig("configured_mail_user");
|
||||
if (self_mail != null && !self_mail.isEmpty()
|
||||
&& messageRecord.getText().contains(self_mail)
|
||||
&& getListAdapter().getChat().isDeviceTalk()) {
|
||||
@@ -822,7 +904,7 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
if (foreignChatId != 0 && foreignChatId != chatId) {
|
||||
Intent intent = new Intent(getActivity(), ConversationActivity.class);
|
||||
intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, foreignChatId);
|
||||
int start = DcMsg.getMessagePosition(original, dcContext);
|
||||
int start = DcMsg.getMessagePosition(original, DcHelper.getContext(getContext()));
|
||||
intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, start);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
((ConversationActivity) getActivity()).hideSoftKeyboard();
|
||||
@@ -838,7 +920,7 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
@Override
|
||||
public void onJumpToOriginalClicked(DcMsg messageRecord) {
|
||||
jumpToOriginal(dcContext.getMsg(messageRecord.getOriginalMsgId()));
|
||||
jumpToOriginal(DcHelper.getContext(getContext()).getMsg(messageRecord.getOriginalMsgId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -857,12 +939,12 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
|
||||
@Override
|
||||
public void onDownloadClicked(DcMsg messageRecord) {
|
||||
dcContext.downloadFullMsg(messageRecord.getId());
|
||||
DcHelper.getContext(getContext()).downloadFullMsg(messageRecord.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReactionClicked(DcMsg messageRecord) {
|
||||
ReactionsDetailsFragment dialog = new ReactionsDetailsFragment(messageRecord.getId());
|
||||
ReactionsDetailsFragment dialog = ReactionsDetailsFragment.newInstance(messageRecord.getId());
|
||||
dialog.show(getActivity().getSupportFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
@@ -911,12 +993,15 @@ public class ConversationFragment extends MessageSelectorFragment
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
hideAddReactionView();
|
||||
int itemId = item.getItemId();
|
||||
AudioPlaybackViewModel playbackViewModel =
|
||||
new ViewModelProvider(requireActivity()).get(AudioPlaybackViewModel.class);
|
||||
|
||||
if (itemId == R.id.menu_context_copy) {
|
||||
handleCopyMessage(getListAdapter().getSelectedItems());
|
||||
actionMode.finish();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_context_delete_message) {
|
||||
handleDeleteMessages((int) chatId, getListAdapter().getSelectedItems());
|
||||
handleDeleteMessages((int) chatId, getListAdapter().getSelectedItems(), playbackViewModel::stopByIds, playbackViewModel::stopByIds);
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_context_share) {
|
||||
DcHelper.openForViewOrShare(getContext(), getSelectedMessageRecord(getListAdapter().getSelectedItems()).getId(), Intent.ACTION_SEND);
|
||||
|
||||
@@ -41,8 +41,7 @@ import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContact;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
|
||||
import org.thoughtcrime.securesms.components.AudioView;
|
||||
import org.thoughtcrime.securesms.calls.CallUtil;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.BorderlessImageView;
|
||||
import org.thoughtcrime.securesms.components.CallItemView;
|
||||
@@ -52,6 +51,8 @@ import org.thoughtcrime.securesms.components.DocumentView;
|
||||
import org.thoughtcrime.securesms.components.QuoteView;
|
||||
import org.thoughtcrime.securesms.components.VcardView;
|
||||
import org.thoughtcrime.securesms.components.WebxdcView;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioView;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
import org.thoughtcrime.securesms.mms.DocumentSlide;
|
||||
@@ -71,7 +72,6 @@ import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
import org.thoughtcrime.securesms.calls.CallUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -181,9 +181,11 @@ public class ConversationItem extends BaseConversationItem
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Set<DcMsg> batchSelected,
|
||||
@NonNull Recipient recipients,
|
||||
boolean pulseHighlight)
|
||||
boolean pulseHighlight,
|
||||
@Nullable AudioPlaybackViewModel playbackViewModel,
|
||||
AudioView.OnActionListener audioPlayPauseListener)
|
||||
{
|
||||
bind(messageRecord, dcChat, batchSelected, pulseHighlight, recipients);
|
||||
bindPartial(messageRecord, dcChat, batchSelected, pulseHighlight, recipients);
|
||||
this.glideRequests = glideRequests;
|
||||
this.showSender = ((dcChat.isMultiUser() || dcChat.isSelfTalk()) && !messageRecord.isOutgoing()) || messageRecord.getOverrideSenderName() != null;
|
||||
|
||||
@@ -204,7 +206,7 @@ public class ConversationItem extends BaseConversationItem
|
||||
|
||||
setGutterSizes(messageRecord, showSender);
|
||||
setMessageShape(messageRecord);
|
||||
setMediaAttributes(messageRecord, showSender);
|
||||
setMediaAttributes(messageRecord, showSender, playbackViewModel, audioPlayPauseListener);
|
||||
setBodyText(messageRecord);
|
||||
setBubbleState(messageRecord);
|
||||
setContactPhoto();
|
||||
@@ -482,24 +484,10 @@ public class ConversationItem extends BaseConversationItem
|
||||
}
|
||||
|
||||
private void setMediaAttributes(@NonNull DcMsg messageRecord,
|
||||
boolean showSender)
|
||||
boolean showSender,
|
||||
AudioPlaybackViewModel playbackViewModel,
|
||||
AudioView.OnActionListener audioPlayPauseListener)
|
||||
{
|
||||
class SetDurationListener implements AudioSlidePlayer.Listener {
|
||||
@Override
|
||||
public void onStart() {}
|
||||
|
||||
@Override
|
||||
public void onStop() {}
|
||||
|
||||
@Override
|
||||
public void onProgress(AudioSlide slide, double progress, long millis) {}
|
||||
|
||||
@Override
|
||||
public void onReceivedDuration(int millis) {
|
||||
messageRecord.lateFilingMediaSize(0,0, millis);
|
||||
audioViewStub.get().setDuration(millis);
|
||||
}
|
||||
}
|
||||
if (hasAudio(messageRecord)) {
|
||||
audioViewStub.get().setVisibility(View.VISIBLE);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
@@ -509,15 +497,9 @@ public class ConversationItem extends BaseConversationItem
|
||||
if (vcardViewStub.resolved()) vcardViewStub.get().setVisibility(View.GONE);
|
||||
if (callViewStub.resolved()) callViewStub.get().setVisibility(View.GONE);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
int duration = messageRecord.getDuration();
|
||||
if (duration == 0) {
|
||||
AudioSlide audio = new AudioSlide(context, messageRecord);
|
||||
AudioSlidePlayer audioSlidePlayer = AudioSlidePlayer.createFor(getContext(), audio, new SetDurationListener());
|
||||
audioSlidePlayer.requestDuration();
|
||||
}
|
||||
|
||||
audioViewStub.get().setAudio(new AudioSlide(context, messageRecord), duration);
|
||||
audioViewStub.get().setPlaybackViewModel(playbackViewModel);
|
||||
audioViewStub.get().setOnActionListener(audioPlayPauseListener);
|
||||
audioViewStub.get().setAudio(new AudioSlide(context, messageRecord));
|
||||
audioViewStub.get().setOnClickListener(passthroughClickListener);
|
||||
audioViewStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
audioViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
@@ -1000,9 +982,9 @@ public class ConversationItem extends BaseConversationItem
|
||||
int chatId = messageRecord.getChatId();
|
||||
if (!messageRecord.isOutgoing() && callInfo.state instanceof CallState.Alerting) {
|
||||
int callId = messageRecord.getId();
|
||||
CallUtil.openCall(getContext(), accId, chatId, callId, callInfo.sdpOffer);
|
||||
CallUtil.openCall(getContext(), accId, chatId, callId, callInfo.sdpOffer, callInfo.hasVideo);
|
||||
} else {
|
||||
CallUtil.startCall(getContext(), accId, chatId);
|
||||
CallUtil.startCall(getContext(), accId, chatId, callInfo.hasVideo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import static org.thoughtcrime.securesms.ConversationActivity.CHAT_ID_EXTRA;
|
||||
import static org.thoughtcrime.securesms.ConversationActivity.STARTING_POSITION_EXTRA;
|
||||
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;
|
||||
@@ -25,10 +23,12 @@ import static org.thoughtcrime.securesms.util.ShareUtil.getDirectSharingChatId;
|
||||
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;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
@@ -46,6 +46,7 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
@@ -98,6 +99,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
public static final String CLEAR_NOTIFICATIONS = "clear_notifications";
|
||||
public static final String ACCOUNT_ID_EXTRA = "account_id";
|
||||
public static final String FROM_WELCOME = "from_welcome";
|
||||
public static final String FROM_WELCOME_RAW_QR = "from_welcome_raw_qr";
|
||||
private static final int REQUEST_CODE_CONFIRM_CREDENTIALS_DELETE_PROFILE = ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS+1;
|
||||
|
||||
private ConversationListFragment conversationListFragment;
|
||||
@@ -123,36 +125,9 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle, boolean ready) {
|
||||
// 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.
|
||||
try {
|
||||
DcContext dcContext = DcHelper.getContext(this);
|
||||
final String deviceMsgLabel = "update_2_33_1_android";
|
||||
if (!dcContext.wasDeviceMsgEverAdded(deviceMsgLabel)) {
|
||||
DcMsg msg = null;
|
||||
if (!getIntent().getBooleanExtra(FROM_WELCOME, false)) {
|
||||
msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
|
||||
|
||||
// InputStream inputStream = getResources().getAssets().open("device-messages/green-checkmark.jpg");
|
||||
// String outputFile = DcHelper.getBlobdirFile(dcContext, "green-checkmark", ".jpg");
|
||||
// Util.copy(inputStream, new FileOutputStream(outputFile));
|
||||
// msg.setFile(outputFile, "image/jpeg");
|
||||
|
||||
msg.setText(getString(R.string.update_2_33, "https://arcanechat.me/#contribute"));
|
||||
}
|
||||
dcContext.addDeviceMsg(deviceMsgLabel, msg);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Prefs.setStringPreference(this, Prefs.LAST_DEVICE_MSG_LABEL, deviceMsgLabel);
|
||||
}
|
||||
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
addDeviceMessages(getIntent().getBooleanExtra(FROM_WELCOME, false));
|
||||
if (getIntent().getIntExtra(ACCOUNT_ID_EXTRA, -1) <= 0) {
|
||||
getIntent().putExtra(ACCOUNT_ID_EXTRA, DcHelper.getContext(this).getAccountId());
|
||||
}
|
||||
|
||||
// create view
|
||||
@@ -176,13 +151,41 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
selfAvatar.setOnClickListener(v -> AccountManager.getInstance().showSwitchAccountMenu(this, false));
|
||||
findViewById(R.id.avatar_and_title).setOnClickListener(v -> {
|
||||
if (!isRelayingMessageContent(this)) {
|
||||
AccountManager.getInstance().showSwitchAccountMenu(this);
|
||||
AccountManager.getInstance().showSwitchAccountMenu(this, false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -191,6 +194,13 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
if (BuildConfig.DEBUG) checkNdkArchitecture();
|
||||
|
||||
DcHelper.maybeShowMigrationError(this);
|
||||
|
||||
String rawQrString = getIntent().getStringExtra(FROM_WELCOME_RAW_QR);
|
||||
// Launch chat directly, if coming from onboarding with a join chat/group QR
|
||||
if (rawQrString != null) {
|
||||
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
|
||||
qrCodeHandler.secureJoinByQr(rawQrString, SecurejoinSource.Scan, SecurejoinUiPath.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,24 +267,29 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
}
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
if (getIntent().getIntExtra(ACCOUNT_ID_EXTRA, -1) <= 0) {
|
||||
getIntent().putExtra(ACCOUNT_ID_EXTRA, DcHelper.getContext(this).getAccountId());
|
||||
}
|
||||
refresh();
|
||||
conversationListFragment.onNewIntent();
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
DcContext dcContext = DcHelper.getContext(this);
|
||||
int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, dcContext.getAccountId());
|
||||
int selectedAccId = DcHelper.getContext(this).getAccountId();
|
||||
int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, selectedAccId);
|
||||
if (getIntent().getBooleanExtra(CLEAR_NOTIFICATIONS, false)) {
|
||||
DcHelper.getNotificationCenter(this).removeAllNotifications(accountId);
|
||||
}
|
||||
if (accountId != dcContext.getAccountId()) {
|
||||
AccountManager.getInstance().switchAccountAndStartActivity(this, accountId);
|
||||
if (accountId != selectedAccId) {
|
||||
AccountManager.getInstance().switchAccount(this, accountId);
|
||||
onProfileSwitched(accountId);
|
||||
} else {
|
||||
refreshAvatar();
|
||||
refreshUnreadIndicator();
|
||||
refreshTitle();
|
||||
}
|
||||
|
||||
refreshAvatar();
|
||||
refreshUnreadIndicator();
|
||||
refreshTitle();
|
||||
handleOpenpgp4fpr();
|
||||
if (isDirectSharing(this)) {
|
||||
openConversation(getDirectSharingChatId(this), -1);
|
||||
@@ -452,13 +467,16 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
startActivity(new Intent(this, ProxySettingsActivity.class));
|
||||
return true;
|
||||
} else if (itemId == android.R.id.home) {
|
||||
onBackPressed();
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_all_media) {
|
||||
startActivity(new Intent(this, AllMediaActivity.class));
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_export_attachment) {
|
||||
handleSaveAttachment();
|
||||
} else if (itemId == R.id.menu_switch_account) {
|
||||
AccountManager.getInstance().showSwitchAccountMenu(this, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -491,7 +509,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
};
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(this);
|
||||
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachments);
|
||||
onBackPressed();
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
}
|
||||
|
||||
private void handleOpenpgp4fpr() {
|
||||
@@ -526,15 +544,17 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
searchToolbar.clearFocus();
|
||||
|
||||
final DcContext dcContext = DcHelper.getContext(this);
|
||||
if (isForwarding(this) && dcContext.getChat(chatId).isSelfTalk()) {
|
||||
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();
|
||||
handleResetRelaying();
|
||||
finish();
|
||||
} else {
|
||||
Intent intent = new Intent(this, ConversationActivity.class);
|
||||
intent.putExtra(CHAT_ID_EXTRA, chatId);
|
||||
intent.putExtra(STARTING_POSITION_EXTRA, startingPosition);
|
||||
intent.putExtra(ConversationActivity.ACCOUNT_ID_EXTRA, dcContext.getAccountId());
|
||||
intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, chatId);
|
||||
intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, startingPosition);
|
||||
if (isRelayingMessageContent(this)) {
|
||||
acquireRelayMessageContent(this, intent);
|
||||
}
|
||||
@@ -554,15 +574,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (searchToolbar.isVisible()) searchToolbar.collapse();
|
||||
else if (isRelayingMessageContent(this)) {
|
||||
handleResetRelaying();
|
||||
finish();
|
||||
} else super.onBackPressed();
|
||||
}
|
||||
|
||||
private void createChat() {
|
||||
Intent intent = new Intent(this, NewConversationActivity.class);
|
||||
if (isRelayingMessageContent(this)) {
|
||||
@@ -579,6 +590,48 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.chat_share_with_title)));
|
||||
}
|
||||
|
||||
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.
|
||||
try {
|
||||
DcContext dcContext = DcHelper.getContext(this);
|
||||
final String deviceMsgLabel = "update_2_33_1_android";
|
||||
if (!dcContext.wasDeviceMsgEverAdded(deviceMsgLabel)) {
|
||||
DcMsg msg = null;
|
||||
if (!getIntent().getBooleanExtra(FROM_WELCOME, false)) {
|
||||
msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
|
||||
|
||||
// InputStream inputStream = getResources().getAssets().open("device-messages/green-checkmark.jpg");
|
||||
// String outputFile = DcHelper.getBlobdirFile(dcContext, "green-checkmark", ".jpg");
|
||||
// Util.copy(inputStream, new FileOutputStream(outputFile));
|
||||
// msg.setFile(outputFile, "image/jpeg");
|
||||
|
||||
msg.setText(getString(R.string.update_2_33, "https://arcanechat.me/#contribute"));
|
||||
}
|
||||
dcContext.addDeviceMsg(deviceMsgLabel, msg);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Prefs.setStringPreference(this, Prefs.LAST_DEVICE_MSG_LABEL, deviceMsgLabel);
|
||||
}
|
||||
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void onProfileSwitched(int profileId) {
|
||||
addDeviceMessages(false);
|
||||
refreshAvatar();
|
||||
refreshUnreadIndicator();
|
||||
refreshTitle();
|
||||
conversationListFragment.loadChatlistAsync();
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -594,9 +647,14 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
accounts.removeAccount(profileId);
|
||||
if (selected) {
|
||||
DcContext selAcc = accounts.getSelectedAccount();
|
||||
AccountManager.getInstance().switchAccountAndStartActivity(this, selAcc.isOk()? selAcc.getAccountId() : 0);
|
||||
if (selAcc.isOk()) {
|
||||
AccountManager.getInstance().switchAccount(this, selAcc.getAccountId());
|
||||
onProfileSwitched(selAcc.getAccountId());
|
||||
} else {
|
||||
AccountManager.getInstance().switchAccountAndStartActivity(this, 0);
|
||||
}
|
||||
} else {
|
||||
AccountManager.getInstance().showSwitchAccountMenu(this);
|
||||
AccountManager.getInstance().showSwitchAccountMenu(this, false);
|
||||
}
|
||||
|
||||
// title update needed to show "Delta Chat" in case there is only one profile left
|
||||
|
||||
@@ -12,6 +12,8 @@ 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;
|
||||
@@ -33,6 +35,21 @@ public class ConversationListArchiveActivity extends PassphraseRequiredActionBar
|
||||
Bundle bundle = new Bundle();
|
||||
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);
|
||||
}
|
||||
|
||||
setEnabled(false);
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,7 +80,7 @@ public class ConversationListArchiveActivity extends PassphraseRequiredActionBar
|
||||
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == android.R.id.home) {
|
||||
onBackPressed();
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
return true;
|
||||
} else if (itemId == R.id.mark_as_read) {
|
||||
DcHelper.getContext(this).marknoticedChat(DcChat.DC_CHAT_ID_ARCHIVED_LINK);
|
||||
@@ -73,20 +90,6 @@ public class ConversationListArchiveActivity extends PassphraseRequiredActionBar
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (isRelayingMessageContent(this)) {
|
||||
// Go back to the ConversationListRelayingActivity
|
||||
super.onBackPressed();
|
||||
} else {
|
||||
// Load the ConversationListActivity in case it's not existent for some reason
|
||||
Intent intent = new Intent(this, ConversationListActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateConversation(int chatId) {
|
||||
Intent intent = new Intent(this, ConversationActivity.class);
|
||||
|
||||
@@ -233,7 +233,7 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
private final Object loadChatlistLock = new Object();
|
||||
private boolean inLoadChatlist;
|
||||
private boolean needsAnotherLoad;
|
||||
private void loadChatlistAsync() {
|
||||
public void loadChatlistAsync() {
|
||||
synchronized (loadChatlistLock) {
|
||||
needsAnotherLoad = true;
|
||||
if (inLoadChatlist) {
|
||||
|
||||
@@ -124,7 +124,6 @@ public class ConversationListItem extends RelativeLayout
|
||||
this.msgId = msgId;
|
||||
|
||||
int state = dcSummary.getState();
|
||||
int unreadCount = thread.getUnreadCount();
|
||||
|
||||
if (highlightSubstring != null) {
|
||||
this.fromView.setText(getHighlightedSpan(recipient.getName(), highlightSubstring));
|
||||
@@ -151,7 +150,7 @@ public class ConversationListItem extends RelativeLayout
|
||||
thread.getVisibility()==DcChat.DC_CHAT_VISIBILITY_PINNED? R.drawable.ic_pinned_chatlist : 0, 0
|
||||
);
|
||||
|
||||
setStatusIcons(thread.getVisibility(), state, unreadCount, thread.isContactRequest(), thread.isMuted() || chatId == DcChat.DC_CHAT_ID_ARCHIVED_LINK);
|
||||
setStatusIcons(thread, state);
|
||||
setBatchState(batchMode);
|
||||
setBgColor(thread);
|
||||
|
||||
@@ -240,14 +239,14 @@ public class ConversationListItem extends RelativeLayout
|
||||
return msgId;
|
||||
}
|
||||
|
||||
private void setStatusIcons(int visibility, int state, int unreadCount, boolean isContactRequest, boolean isMuted) {
|
||||
if (visibility==DcChat.DC_CHAT_VISIBILITY_ARCHIVED)
|
||||
private void setStatusIcons(ThreadRecord thread, int state) {
|
||||
if (thread.getVisibility() == DcChat.DC_CHAT_VISIBILITY_ARCHIVED)
|
||||
{
|
||||
archivedBadgeView.setVisibility(View.VISIBLE);
|
||||
requestBadgeView.setVisibility(isContactRequest ? View.VISIBLE : View.GONE);
|
||||
requestBadgeView.setVisibility(thread.isContactRequest() ? View.VISIBLE : View.GONE);
|
||||
deliveryStatusIndicator.setNone();
|
||||
}
|
||||
else if (isContactRequest) {
|
||||
else if (thread.isContactRequest()) {
|
||||
requestBadgeView.setVisibility(View.VISIBLE);
|
||||
archivedBadgeView.setVisibility(View.GONE);
|
||||
deliveryStatusIndicator.setNone();
|
||||
@@ -259,30 +258,27 @@ public class ConversationListItem extends RelativeLayout
|
||||
|
||||
if (state == DcMsg.DC_STATE_OUT_FAILED) {
|
||||
deliveryStatusIndicator.setFailed();
|
||||
} else if (state == DcMsg.DC_STATE_OUT_MDN_RCVD) {
|
||||
deliveryStatusIndicator.setRead();
|
||||
} else if (state == DcMsg.DC_STATE_OUT_DELIVERED) {
|
||||
deliveryStatusIndicator.setSent();
|
||||
} else if (state == DcMsg.DC_STATE_OUT_PREPARING) {
|
||||
deliveryStatusIndicator.setPreparing();
|
||||
} else if (state == DcMsg.DC_STATE_OUT_PENDING) {
|
||||
deliveryStatusIndicator.setPending();
|
||||
} else if (thread.isOutChannel()) {
|
||||
deliveryStatusIndicator.setNone();
|
||||
} else if (state == DcMsg.DC_STATE_OUT_MDN_RCVD) {
|
||||
deliveryStatusIndicator.setRead();
|
||||
} else if (state == DcMsg.DC_STATE_OUT_DELIVERED) {
|
||||
deliveryStatusIndicator.setSent();
|
||||
} else {
|
||||
deliveryStatusIndicator.setNone();
|
||||
}
|
||||
|
||||
if (state == DcMsg.DC_STATE_OUT_FAILED) {
|
||||
deliveryStatusIndicator.setTint(Color.RED);
|
||||
} else {
|
||||
deliveryStatusIndicator.resetTint();
|
||||
}
|
||||
}
|
||||
|
||||
if(unreadCount==0 || isContactRequest) {
|
||||
int unreadCount = thread.getUnreadCount();
|
||||
if(unreadCount==0 || thread.isContactRequest()) {
|
||||
unreadIndicator.setVisibility(View.GONE);
|
||||
} else {
|
||||
final int color;
|
||||
if(isMuted){
|
||||
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[] {
|
||||
|
||||
@@ -15,6 +15,8 @@ import com.b44t.messenger.DcMsg;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.thoughtcrime.securesms.components.DeliveryStatusView;
|
||||
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 org.thoughtcrime.securesms.util.JsonUtils;
|
||||
@@ -61,9 +63,11 @@ public class ConversationUpdateItem extends BaseConversationItem
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Set<DcMsg> batchSelected,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
boolean pulseUpdate)
|
||||
boolean pulseUpdate,
|
||||
@Nullable AudioPlaybackViewModel playbackViewModel,
|
||||
AudioView.OnActionListener audioPlayPauseListener)
|
||||
{
|
||||
bind(messageRecord, dcChat, batchSelected, pulseUpdate, conversationRecipient);
|
||||
bindPartial(messageRecord, dcChat, batchSelected, pulseUpdate, conversationRecipient);
|
||||
setGenericInfoRecord(messageRecord);
|
||||
}
|
||||
|
||||
@@ -119,12 +123,6 @@ public class ConversationUpdateItem extends BaseConversationItem
|
||||
else if (messageRecord.isPreparing()) deliveryStatusView.setPreparing();
|
||||
else if (messageRecord.isPending()) deliveryStatusView.setPending();
|
||||
else deliveryStatusView.setNone();
|
||||
|
||||
if (messageRecord.isFailed()) {
|
||||
deliveryStatusView.setTint(Color.RED);
|
||||
} else {
|
||||
deliveryStatusView.setTint(textColor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,6 +21,7 @@ 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;
|
||||
|
||||
@@ -52,8 +53,6 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
|
||||
private static final String TAG = CreateProfileActivity.class.getSimpleName();
|
||||
|
||||
public static final String FROM_WELCOME = "from_welcome";
|
||||
|
||||
private static final int REQUEST_CODE_AVATAR = 1;
|
||||
|
||||
private InputAwareLayout container;
|
||||
@@ -61,7 +60,6 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
private EditText name;
|
||||
private EditText statusView;
|
||||
|
||||
private boolean fromWelcome;
|
||||
private boolean avatarChanged;
|
||||
private boolean imageLoaded;
|
||||
|
||||
@@ -72,12 +70,11 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
this.fromWelcome = getIntent().getBooleanExtra(FROM_WELCOME, false);
|
||||
|
||||
setContentView(R.layout.profile_create_activity);
|
||||
|
||||
getSupportActionBar().setTitle(R.string.pref_profile_info_headline);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(!this.fromWelcome);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp);
|
||||
|
||||
attachmentManager = new AttachmentManager(this, () -> {});
|
||||
@@ -86,6 +83,18 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
initializeProfileName();
|
||||
initializeProfileAvatar();
|
||||
initializeStatusText();
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (container.isInputOpen()) {
|
||||
container.hideCurrentInput(name);
|
||||
} else {
|
||||
setEnabled(false);
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,7 +109,7 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
super.onOptionsItemSelected(item);
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == android.R.id.home) {
|
||||
onBackPressed();
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_create_profile) {
|
||||
updateProfile();
|
||||
@@ -109,17 +118,6 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (container.isInputOpen()) {
|
||||
container.hideCurrentInput(name);
|
||||
} else if (fromWelcome) {
|
||||
updateProfile();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
@@ -187,13 +185,7 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
// add padding to avoid content hidden behind system bars
|
||||
ViewUtil.applyWindowInsets(container);
|
||||
|
||||
if (fromWelcome) {
|
||||
loginSuccessText.setText(R.string.set_name_and_avatar_explain);
|
||||
ViewUtil.findById(this, R.id.status_text_layout).setVisibility(View.GONE);
|
||||
ViewUtil.findById(this, R.id.information_label).setVisibility(View.GONE);
|
||||
} else {
|
||||
loginSuccessText.setVisibility(View.GONE);
|
||||
}
|
||||
loginSuccessText.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void initializeProfileName() {
|
||||
@@ -260,11 +252,6 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
|
||||
if (result) {
|
||||
attachmentManager.cleanup();
|
||||
if (fromWelcome) {
|
||||
Intent intent = new Intent(getApplicationContext(), ConversationListActivity.class);
|
||||
intent.putExtra(ConversationListActivity.FROM_WELCOME, true);
|
||||
startActivity(intent);
|
||||
}
|
||||
finish();
|
||||
} else {
|
||||
Toast.makeText(CreateProfileActivity.this, R.string.error, Toast.LENGTH_LONG).show();
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -44,12 +45,14 @@ 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
|
||||
{
|
||||
|
||||
private static final String TAG = GroupCreateActivity.class.getSimpleName();
|
||||
public static final String EDIT_GROUP_CHAT_ID = "edit_group_chat_id";
|
||||
public static final String CREATE_BROADCAST = "create_broadcast";
|
||||
public static final String UNENCRYPTED = "unencrypted";
|
||||
@@ -63,6 +66,7 @@ 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;
|
||||
@@ -140,6 +144,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
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
|
||||
@@ -178,6 +183,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
} else if (unencrypted) {
|
||||
avatar.setVisibility(View.GONE);
|
||||
groupName.setHint(R.string.subject);
|
||||
findViewById(R.id.chat_description_container).setVisibility(View.GONE);
|
||||
chatHints.setVisibility(View.GONE);
|
||||
} else {
|
||||
chatHints.setVisibility(View.GONE);
|
||||
@@ -186,6 +192,14 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
if(isEdit()) {
|
||||
groupName.setText(dcContext.getChat(groupChatId).getName());
|
||||
lv.setVisibility(View.GONE);
|
||||
|
||||
Rpc rpc = DcHelper.getRpc(this);
|
||||
try {
|
||||
String description = rpc.getChatDescription(rpc.getSelectedAccountId(), groupChatId);
|
||||
chatDescription.setText(description);
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "RPC error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,22 +275,22 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void createGroup(String groupName) {
|
||||
if (broadcast) {
|
||||
try {
|
||||
groupChatId = DcHelper.getRpc(this).createBroadcast(dcContext.getAccountId(), groupName);
|
||||
} catch (RpcException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
Rpc rpc = DcHelper.getRpc(this);
|
||||
int accId;
|
||||
try {
|
||||
accId = rpc.getSelectedAccountId();
|
||||
if (broadcast) {
|
||||
groupChatId = rpc.createBroadcast(accId, groupName);
|
||||
} else if (unencrypted) {
|
||||
groupChatId = rpc.createGroupChatUnencrypted(accId, groupName);
|
||||
} else {
|
||||
groupChatId = rpc.createGroupChat(accId, groupName, false);
|
||||
}
|
||||
} else if (unencrypted) {
|
||||
try {
|
||||
groupChatId = DcHelper.getRpc(this).createGroupChatUnencrypted(dcContext.getAccountId(), groupName);
|
||||
} catch (RpcException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
groupChatId = dcContext.createGroupChat(groupName);
|
||||
|
||||
rpc.setChatDescription(accId, groupChatId, getChatDescription());
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "RPC error", e);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int contactId : getAdapter().getContacts()) {
|
||||
@@ -307,6 +321,14 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
dcContext.setChatName(groupChatId, groupName);
|
||||
|
||||
Rpc rpc = DcHelper.getRpc(this);
|
||||
String description = getChatDescription();
|
||||
try {
|
||||
rpc.setChatDescription(rpc.getSelectedAccountId(), groupChatId, description);
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "RPC error", e);
|
||||
}
|
||||
|
||||
if (avatarChanged) AvatarHelper.setGroupAvatar(this, groupChatId, avatarBmp);
|
||||
|
||||
attachmentManager.cleanup();
|
||||
@@ -331,6 +353,10 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
return ret;
|
||||
}
|
||||
|
||||
private @Nullable String getChatDescription() {
|
||||
return chatDescription.getText() != null ? chatDescription.getText().toString().trim() : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
@@ -25,7 +25,6 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
@@ -41,7 +40,6 @@ import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
|
||||
import org.thoughtcrime.securesms.components.AvatarSelector;
|
||||
import org.thoughtcrime.securesms.connect.AccountManager;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||
@@ -56,6 +54,7 @@ import org.thoughtcrime.securesms.relay.RelayListActivity;
|
||||
import org.thoughtcrime.securesms.scribbles.ScribbleActivity;
|
||||
import org.thoughtcrime.securesms.util.IntentUtils;
|
||||
import org.thoughtcrime.securesms.util.Prefs;
|
||||
import org.thoughtcrime.securesms.util.TextUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.ProgressDialog;
|
||||
@@ -64,6 +63,8 @@ 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;
|
||||
@@ -76,11 +77,11 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
private static final String INSTANCES_URL = "https://chatmail.at/relays";
|
||||
private static final String DEFAULT_CHATMAIL_HOST = "arcanechat.me";
|
||||
|
||||
public static final String FROM_WELCOME = "from_welcome";
|
||||
private static final int REQUEST_CODE_AVATAR = 1;
|
||||
|
||||
private ImageView avatar;
|
||||
private EditText name;
|
||||
private TextView invitationText;
|
||||
private TextView privacyPolicyBtn;
|
||||
private Button signUpBtn;
|
||||
|
||||
@@ -88,7 +89,11 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
private boolean imageLoaded;
|
||||
private String providerHost;
|
||||
private String providerQrData;
|
||||
private String rawQrData;
|
||||
private DcLot parsedQrData;
|
||||
private boolean isDcLogin;
|
||||
private boolean isContactInvitation;
|
||||
private boolean isGroupInvitation;
|
||||
|
||||
private AttachmentManager attachmentManager;
|
||||
private Bitmap avatarBmp;
|
||||
@@ -98,6 +103,8 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
|
||||
private DcContext dcContext;
|
||||
|
||||
private ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
@@ -107,9 +114,9 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
Objects.requireNonNull(getSupportActionBar()).setTitle(R.string.onboarding_create_instant_account);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
boolean fromWelcome = getIntent().getBooleanExtra(FROM_WELCOME, false);
|
||||
boolean configured = DcHelper.getContext(this).isConfigured() == 1;
|
||||
|
||||
if (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
|
||||
Uri uri = getIntent().getData();
|
||||
@@ -117,25 +124,11 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
Intent intent = new Intent(this, RelayListActivity.class);
|
||||
intent.putExtra(RelayListActivity.EXTRA_QR_DATA, uri.toString());
|
||||
startActivity(intent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
// if URI is unexpectedly null, then fallback to new profile creation
|
||||
AccountManager.getInstance().beginAccountCreation(this);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(!fromWelcome) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
AccountManager accountManager = AccountManager.getInstance();
|
||||
if (accountManager.canRollbackAccountCreation(InstantOnboardingActivity.this)) {
|
||||
accountManager.rollbackAccountCreation(InstantOnboardingActivity.this);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
isDcLogin = false;
|
||||
providerHost = DEFAULT_CHATMAIL_HOST;
|
||||
providerQrData = DCACCOUNT + ":" + providerHost;
|
||||
@@ -222,17 +215,31 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
|
||||
private void setProviderFromQr(String rawQr) {
|
||||
DcLot qrParsed = dcContext.checkQr(rawQr);
|
||||
boolean isDcLogin = qrParsed.getState() == DcContext.DC_QR_LOGIN;
|
||||
if (isDcLogin || qrParsed.getState() == DcContext.DC_QR_ACCOUNT) {
|
||||
this.isDcLogin = isDcLogin;
|
||||
providerHost = qrParsed.getText1();
|
||||
providerQrData = rawQr;
|
||||
updateProvider();
|
||||
} else {
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.qraccount_qr_code_cannot_be_used)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
switch (qrParsed.getState()) {
|
||||
case DcContext.DC_QR_LOGIN:
|
||||
isDcLogin = true; // Intentional fall-through
|
||||
case DcContext.DC_QR_ACCOUNT:
|
||||
providerHost = qrParsed.getText1();
|
||||
providerQrData = rawQr;
|
||||
updateProvider();
|
||||
break;
|
||||
case DcContext.DC_QR_ASK_VERIFYCONTACT:
|
||||
isContactInvitation = true;
|
||||
rawQrData = rawQr;
|
||||
parsedQrData = qrParsed;
|
||||
updateProvider();
|
||||
break;
|
||||
case DcContext.DC_QR_ASK_VERIFYGROUP:
|
||||
isGroupInvitation = true;
|
||||
rawQrData = rawQr;
|
||||
parsedQrData = qrParsed;
|
||||
updateProvider();
|
||||
break;
|
||||
default:
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.qraccount_qr_code_cannot_be_used)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +283,7 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
DcHelper.getEventCenter(this).removeObservers(this);
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
private void handleIntent() {
|
||||
@@ -327,6 +335,7 @@ 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.privacyPolicyBtn = findViewById(R.id.privacy_policy_button);
|
||||
this.signUpBtn = findViewById(R.id.signup_button);
|
||||
|
||||
@@ -341,7 +350,10 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
|
||||
signUpBtn.setOnClickListener(view -> createProfile());
|
||||
|
||||
findViewById(R.id.use_other_server).setOnClickListener((v) -> {
|
||||
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) -> {
|
||||
@@ -365,10 +377,25 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
}
|
||||
|
||||
if (DEFAULT_CHATMAIL_HOST.equals(providerHost)) {
|
||||
privacyPolicyBtn.setText(getString(R.string.instant_onboarding_agree_default2, providerHost));
|
||||
privacyPolicyBtn.setText(TextUtil.markAsExternal(
|
||||
getString(R.string.instant_onboarding_agree_default2, providerHost)));
|
||||
} else {
|
||||
privacyPolicyBtn.setText(getString(R.string.instant_onboarding_agree_instance, providerHost));
|
||||
privacyPolicyBtn.setText(TextUtil.markAsExternal(
|
||||
getString(R.string.instant_onboarding_agree_instance, providerHost)));
|
||||
}
|
||||
|
||||
if (parsedQrData != null) {
|
||||
if (isContactInvitation) {
|
||||
String name = dcContext.getContact(parsedQrData.getId()).getDisplayName();
|
||||
invitationText.setText(this.getString(R.string.instant_onboarding_contact_info, name));
|
||||
invitationText.setVisibility(View.VISIBLE);
|
||||
} else if (isGroupInvitation) {
|
||||
String groupName = parsedQrData.getText1();
|
||||
invitationText.setText(this.getString(R.string.instant_onboarding_group_info, groupName));
|
||||
invitationText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,11 +457,14 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
|
||||
Intent intent = new Intent(getApplicationContext(), ConversationListActivity.class);
|
||||
intent.putExtra(ConversationListActivity.FROM_WELCOME, true);
|
||||
if (isContactInvitation || isGroupInvitation) {
|
||||
intent.putExtra(ConversationListActivity.FROM_WELCOME_RAW_QR, rawQrData);
|
||||
}
|
||||
|
||||
startActivity(intent);
|
||||
finishAffinity();
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private void createProfile() {
|
||||
if (TextUtils.isEmpty(this.name.getText())) {
|
||||
Toast.makeText(this, R.string.please_enter_name, Toast.LENGTH_LONG).show();
|
||||
@@ -442,37 +472,31 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
|
||||
}
|
||||
final String name = this.name.getText().toString();
|
||||
|
||||
new AsyncTask<Void, Void, Boolean>() {
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
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);
|
||||
|
||||
if (avatarChanged) {
|
||||
try {
|
||||
AvatarHelper.setSelfAvatar(InstantOnboardingActivity.this, avatarBmp);
|
||||
Prefs.setProfileAvatarId(InstantOnboardingActivity.this, new SecureRandom().nextInt());
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return 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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(Boolean result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
if (result) {
|
||||
boolean finalResult = result;
|
||||
runOnUiThread(() -> {
|
||||
if (finalResult) {
|
||||
attachmentManager.cleanup();
|
||||
startQrAccountCreation(providerQrData);
|
||||
} else {
|
||||
Toast.makeText(InstantOnboardingActivity.this, R.string.error, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void startQrAccountCreation(String qrCode)
|
||||
|
||||
@@ -5,6 +5,9 @@ import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
|
||||
import org.thoughtcrime.securesms.util.TextUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.InputStream;
|
||||
@@ -44,6 +47,18 @@ public class LocalHelpActivity extends WebViewActivity
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
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 : ""));
|
||||
}
|
||||
|
||||
@@ -51,6 +66,28 @@ public class LocalHelpActivity extends WebViewActivity
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
this.getMenuInflater().inflate(R.menu.local_help, menu);
|
||||
|
||||
// Append " ↗" to external link buttons
|
||||
MenuItem item = menu.findItem(R.id.learn_more);
|
||||
if (item != null) {
|
||||
item.setTitle(TextUtil.markAsExternal(getString(R.string.delta_chat_homepage)));
|
||||
}
|
||||
|
||||
item = menu.findItem(R.id.privacy_policy);
|
||||
if (item != null) {
|
||||
item.setTitle(TextUtil.markAsExternal(getString(R.string.privacy_policy)));
|
||||
}
|
||||
|
||||
item = menu.findItem(R.id.contribute);
|
||||
if (item != null) {
|
||||
item.setTitle(TextUtil.markAsExternal(getString(R.string.contribute)));
|
||||
}
|
||||
|
||||
item = menu.findItem(R.id.report_issue);
|
||||
if (item != null) {
|
||||
item.setTitle(TextUtil.markAsExternal(getString(R.string.global_menu_help_report_desktop)));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -77,15 +114,6 @@ public class LocalHelpActivity extends WebViewActivity
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean assetExists(String fileName) {
|
||||
// test using AssetManager.open();
|
||||
// AssetManager.list() is unreliable eg. on my Android 7 Moto G
|
||||
|
||||
@@ -129,7 +129,7 @@ public class LogViewFragment extends Fragment {
|
||||
return logFile;
|
||||
}
|
||||
|
||||
private static String grabLogcat(LogViewFragment fragment) {
|
||||
public static String grabLogcat() {
|
||||
String command = "logcat -v threadtime -d -t 10000 *:I";
|
||||
try {
|
||||
final Process process = Runtime.getRuntime().exec(command);
|
||||
@@ -165,7 +165,7 @@ public class LogViewFragment extends Fragment {
|
||||
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(fragment);
|
||||
buildDescription(fragment) + "\n" + grabLogcat();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
@@ -12,6 +13,7 @@ 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;
|
||||
@@ -32,7 +34,6 @@ public abstract class MessageSelectorFragment
|
||||
implements DcEventCenter.DcEventDelegate
|
||||
{
|
||||
protected ActionMode actionMode;
|
||||
protected DcContext dcContext;
|
||||
|
||||
protected abstract void setCorrectMenuVisibility(Menu menu);
|
||||
|
||||
@@ -48,7 +49,7 @@ public abstract class MessageSelectorFragment
|
||||
protected void handleDisplayDetails(DcMsg dcMsg) {
|
||||
View view = View.inflate(getActivity(), R.layout.message_details_view, null);
|
||||
TextView detailsText = view.findViewById(R.id.details_text);
|
||||
detailsText.setText(dcContext.getMsgInfo(dcMsg.getId()));
|
||||
detailsText.setText(DcHelper.getContext(getContext()).getMsgInfo(dcMsg.getId()));
|
||||
|
||||
AlertDialog d = new AlertDialog.Builder(getActivity())
|
||||
.setView(view)
|
||||
@@ -58,10 +59,15 @@ public abstract class MessageSelectorFragment
|
||||
}
|
||||
|
||||
protected void handleDeleteMessages(int chatId, final Set<DcMsg> messageRecords) {
|
||||
handleDeleteMessages(chatId, DcMsg.msgSetToIds(messageRecords));
|
||||
handleDeleteMessages(chatId, DcMsg.msgSetToIds(messageRecords), null, null);
|
||||
}
|
||||
|
||||
protected void handleDeleteMessages(int chatId, final int[] messageIds) {
|
||||
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) {
|
||||
DcContext dcContext = DcHelper.getContext(getContext());
|
||||
DcChat dcChat = dcContext.getChat(chatId);
|
||||
boolean canDeleteForAll = true;
|
||||
if (dcChat.isEncrypted() && dcChat.canSend() && !dcChat.isSelfTalk()) {
|
||||
@@ -79,20 +85,24 @@ public abstract class MessageSelectorFragment
|
||||
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, (d, which) -> {
|
||||
Util.runOnAnyBackgroundThread(() -> dcContext.deleteMsgs(messageIds));
|
||||
if (actionMode != null) actionMode.finish();
|
||||
});
|
||||
.setPositiveButton(positiveBtnLabel, deleteForMeListener);
|
||||
|
||||
if(canDeleteForAll) {
|
||||
builder.setNegativeButton(R.string.delete_for_everyone, (d, which) -> {
|
||||
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);
|
||||
Util.redPositiveButton(dialog);
|
||||
@@ -134,7 +144,7 @@ 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, dcContext));
|
||||
intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, DcMsg.getMessagePosition(dcMsg, DcHelper.getContext(getContext())));
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@@ -144,6 +154,7 @@ 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(() -> {
|
||||
|
||||
@@ -22,10 +22,7 @@ import static org.thoughtcrime.securesms.util.ShareUtil.acquireRelayMessageConte
|
||||
import static org.thoughtcrime.securesms.util.ShareUtil.isRelayingMessageContent;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
@@ -37,7 +34,6 @@ 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 org.thoughtcrime.securesms.util.MailtoUtil;
|
||||
|
||||
import chat.delta.rpc.types.SecurejoinSource;
|
||||
import chat.delta.rpc.types.SecurejoinUiPath;
|
||||
@@ -57,53 +53,6 @@ public class NewConversationActivity extends ContactSelectionActivity {
|
||||
super.onCreate(bundle, ready);
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
handleIntent();
|
||||
}
|
||||
|
||||
private void handleIntent() {
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
if(Intent.ACTION_VIEW.equals(action) || Intent.ACTION_SENDTO.equals(action)) {
|
||||
try {
|
||||
Uri uri = intent.getData();
|
||||
if(uri != null) {
|
||||
String scheme = uri.getScheme();
|
||||
if(MailtoUtil.isMailto(uri)) {
|
||||
String textToShare = MailtoUtil.getText(uri);
|
||||
String[] recipientsArray = MailtoUtil.getRecipients(uri);
|
||||
if (recipientsArray.length >= 1) {
|
||||
if (!textToShare.isEmpty()) {
|
||||
getIntent().putExtra(TEXT_EXTRA, textToShare);
|
||||
}
|
||||
final String addr = recipientsArray[0];
|
||||
final DcContext dcContext = DcHelper.getContext(this);
|
||||
int contactId = dcContext.lookupContactIdByAddr(addr);
|
||||
if (contactId == 0 && dcContext.mayBeValidAddr(addr)) {
|
||||
contactId = dcContext.createContact(null, recipientsArray[0]);
|
||||
}
|
||||
if (contactId == 0) {
|
||||
Toast.makeText(this, R.string.bad_email_address, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
onContactSelected(contactId);
|
||||
}
|
||||
} else {
|
||||
Intent shareIntent = new Intent(this, ShareActivity.class);
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, textToShare);
|
||||
startActivity(shareIntent);
|
||||
finish();
|
||||
}
|
||||
} else if(scheme != null && scheme.startsWith("http")) {
|
||||
Intent shareIntent = new Intent(this, ShareActivity.class);
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, uri.toString());
|
||||
startActivity(shareIntent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e) {
|
||||
Log.e(TAG, "start activity from external 'mailto:' link failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -30,16 +31,21 @@ 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
|
||||
{
|
||||
private static final String TAG = ProfileAdapter.class.getSimpleName();
|
||||
|
||||
public static final int ITEM_AVATAR = 10;
|
||||
public static final int ITEM_DIVIDER = 20;
|
||||
public static final int ITEM_SIGNATURE = 25;
|
||||
public static final int ITEM_ALL_MEDIA_BUTTON = 30;
|
||||
public static final int ITEM_SEND_MESSAGE_BUTTON = 35;
|
||||
public static final int ITEM_LAST_SEEN = 40;
|
||||
public static final int ITEM_BLOCKED = 43;
|
||||
public static final int ITEM_INTRODUCED_BY = 45;
|
||||
public static final int ITEM_ADDRESS = 50;
|
||||
public static final int ITEM_HEADER = 53;
|
||||
public static final int ITEM_MEMBERS = 55;
|
||||
public static final int ITEM_SHARED_CHATS = 60;
|
||||
@@ -140,7 +146,7 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
} 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);
|
||||
return new ViewHolder(item);
|
||||
} else if (viewType == ITEM_LAST_SEEN || viewType == ITEM_INTRODUCED_BY || viewType == ITEM_ADDRESS) {
|
||||
} 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 {
|
||||
@@ -195,7 +201,7 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
}
|
||||
else if(holder.itemView instanceof ProfileStatusItem) {
|
||||
ProfileStatusItem item = (ProfileStatusItem) holder.itemView;
|
||||
item.setOnLongClickListener(view -> {clickListener.onStatusLongClicked(); return true;});
|
||||
item.setOnLongClickListener(view -> {clickListener.onStatusLongClicked(dcContact == null); return true;});
|
||||
item.set(data.label);
|
||||
}
|
||||
else if(holder.itemView instanceof ProfileAvatarItem) {
|
||||
@@ -206,14 +212,11 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
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;
|
||||
boolean tintIcon = data.viewType != ITEM_INTRODUCED_BY && data.viewType != ITEM_BLOCKED;
|
||||
item.set(data.label, data.icon, tintIcon);
|
||||
if (data.viewType == ITEM_LAST_SEEN || data.viewType == ITEM_ADDRESS) {
|
||||
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);
|
||||
if (data.viewType == ITEM_ADDRESS) {
|
||||
fragment.registerForContextMenu(item);
|
||||
}
|
||||
} 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());
|
||||
@@ -233,7 +236,7 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
|
||||
public interface ItemClickListener {
|
||||
void onSettingsClicked(int settingsId);
|
||||
void onStatusLongClicked();
|
||||
void onStatusLongClicked(boolean isMultiUser);
|
||||
void onSharedChatClicked(int chatId);
|
||||
void onMemberClicked(int contactId);
|
||||
void onMemberLongClicked(int contactId);
|
||||
@@ -281,8 +284,21 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
|
||||
itemData.add(new ItemData(ITEM_AVATAR, null, 0));
|
||||
|
||||
if (isSelfTalk || dcContact != null && !dcContact.getStatus().isEmpty()) {
|
||||
itemDataStatusText = isSelfTalk ? context.getString(R.string.saved_messages_explain) : dcContact.getStatus();
|
||||
if (isSelfTalk) {
|
||||
itemDataStatusText = context.getString(R.string.saved_messages_explain);
|
||||
} else if (dcContact != null) {
|
||||
itemDataStatusText = dcContact.getStatus();
|
||||
} else if (dcChat != null && dcChat.isEncrypted()) {
|
||||
// Load group or channel description
|
||||
try {
|
||||
Rpc rpc = DcHelper.getRpc(context);
|
||||
itemDataStatusText = rpc.getChatDescription(rpc.getSelectedAccountId(), dcChat.getId());
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "RPC error", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!itemDataStatusText.isEmpty()) {
|
||||
itemData.add(new ItemData(ITEM_SIGNATURE, itemDataStatusText, 0));
|
||||
} else {
|
||||
itemData.add(new ItemData(ITEM_DIVIDER, null, 0));
|
||||
@@ -308,6 +324,10 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
}
|
||||
*/
|
||||
|
||||
if (dcContact != null && !isDeviceTalk && !isSelfTalk && dcContact.isBlocked()) {
|
||||
itemData.add(new ItemData(ITEM_BLOCKED, context.getString(R.string.contact_blocked), R.drawable.contact_blocked_24));
|
||||
}
|
||||
|
||||
if (memberList!=null && !isInBroadcast && !isMailingList) {
|
||||
itemData.add(new ItemData(ITEM_DIVIDER, null, 0));
|
||||
if (dcChat != null) {
|
||||
@@ -345,10 +365,6 @@ public class ProfileAdapter extends RecyclerView.Adapter
|
||||
String introducedBy = context.getString(R.string.verified_by_unknown);
|
||||
itemData.add(new ItemData(ITEM_INTRODUCED_BY, introducedBy, R.drawable.ic_verified));
|
||||
}
|
||||
|
||||
if (dcContact != null) {
|
||||
itemData.add(new ItemData(ITEM_ADDRESS, dcContact.getAddr(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
|
||||
@@ -140,10 +140,10 @@ public class ProfileFragment extends Fragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusLongClicked() {
|
||||
public void onStatusLongClicked(boolean isMultiUser) {
|
||||
Context context = requireContext();
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.pref_default_status_label)
|
||||
.setTitle(isMultiUser? R.string.chat_description : R.string.pref_default_status_label)
|
||||
.setItems(new CharSequence[]{
|
||||
context.getString(R.string.menu_copy_to_clipboard)
|
||||
},
|
||||
@@ -310,11 +310,27 @@ public class ProfileFragment extends Fragment
|
||||
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);
|
||||
if(selected == null) return;
|
||||
List<Integer> deselected = data.getIntegerArrayListExtra(ContactMultiSelectionActivity.DESELECTED_CONTACTS_EXTRA);
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
for (Integer contactId : selected) {
|
||||
if (contactId!=null) {
|
||||
dcContext.addContactToChat(chatId, contactId);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -104,26 +104,25 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity implement
|
||||
resolvedExtras = new ArrayList<>();
|
||||
|
||||
List<Uri> streamExtras = new ArrayList<>();
|
||||
if (MailtoUtil.isMailto(getIntent().getData())) {
|
||||
String[] extraEmail = getIntent().getStringArrayExtra(Intent.EXTRA_EMAIL);
|
||||
if (extraEmail == null || extraEmail.length == 0) {
|
||||
getIntent().putExtra(Intent.EXTRA_EMAIL, MailtoUtil.getRecipients(getIntent().getData()));
|
||||
}
|
||||
String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||
if (text == null || text.isEmpty()) {
|
||||
getIntent().putExtra(Intent.EXTRA_TEXT, MailtoUtil.getText(getIntent().getData()));
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Uri uri = getIntent().getData();
|
||||
if (MailtoUtil.isMailto(uri)) {
|
||||
String[] extraEmail = getIntent().getStringArrayExtra(Intent.EXTRA_EMAIL);
|
||||
if (extraEmail == null || extraEmail.length == 0) {
|
||||
getIntent().putExtra(Intent.EXTRA_EMAIL, MailtoUtil.getRecipients(uri));
|
||||
}
|
||||
String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||
if (text == null || text.isEmpty()) {
|
||||
getIntent().putExtra(Intent.EXTRA_TEXT, MailtoUtil.getText(uri));
|
||||
}
|
||||
} else if (uri != null) {
|
||||
streamExtras.add(uri);
|
||||
}
|
||||
} else if (getIntent().getData() != null) {
|
||||
streamExtras.add(getIntent().getData());
|
||||
}
|
||||
|
||||
if (needsFilePermission(streamExtras)) {
|
||||
@@ -265,6 +264,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity implement
|
||||
}
|
||||
|
||||
chatId = dcContext.createChatByContactId(contactId);
|
||||
accId = dcContext.getAccountId();
|
||||
}
|
||||
Intent composeIntent;
|
||||
if (accId != -1 && chatId != -1) {
|
||||
|
||||
@@ -77,7 +77,7 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
||||
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));
|
||||
ViewUtil.applyWindowInsets(findViewById(R.id.content_container), true, true, true, true, true, false);
|
||||
}
|
||||
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
|
||||
@@ -15,6 +15,7 @@ import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
@@ -36,6 +37,7 @@ import org.thoughtcrime.securesms.qr.QrCodeHandler;
|
||||
import org.thoughtcrime.securesms.qr.RegistrationQrActivity;
|
||||
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
||||
import org.thoughtcrime.securesms.service.NotificationController;
|
||||
import org.thoughtcrime.securesms.util.Prefs;
|
||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||
import org.thoughtcrime.securesms.util.StreamUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@@ -67,16 +69,48 @@ public class WelcomeActivity extends BaseActionBarActivity implements DcEventCen
|
||||
// add padding to avoid content hidden behind system bars
|
||||
ViewUtil.applyWindowInsets(findViewById(R.id.content_container));
|
||||
|
||||
findViewById(R.id.signup_button).setOnClickListener((v) -> startInstantOnboardingActivity());
|
||||
findViewById(R.id.add_as_second_device_button).setOnClickListener((v) -> startAddAsSecondDeviceActivity());
|
||||
findViewById(R.id.signup_button).setOnClickListener((v) -> startActivity(new Intent(this, InstantOnboardingActivity.class)));
|
||||
findViewById(R.id.add_as_second_device_button).setOnClickListener((v) -> showSignInDialogWithPermission());
|
||||
findViewById(R.id.backup_button).setOnClickListener((v) -> startImportBackup());
|
||||
|
||||
registerForEvents();
|
||||
initializeActionBar();
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
AccountManager accountManager = AccountManager.getInstance();
|
||||
if (accountManager.canRollbackAccountCreation(WelcomeActivity.this)) {
|
||||
accountManager.rollbackAccountCreation(WelcomeActivity.this);
|
||||
} else {
|
||||
setEnabled(false);
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
DcHelper.maybeShowMigrationError(this);
|
||||
}
|
||||
|
||||
private void showSignInDialogWithPermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||
&& !Prefs.getBooleanPreference(this, Prefs.ASKED_FOR_NOTIFICATION_PERMISSION, false)) {
|
||||
Prefs.setBooleanPreference(this, Prefs.ASKED_FOR_NOTIFICATION_PERMISSION, true);
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.POST_NOTIFICATIONS)
|
||||
.ifNecessary()
|
||||
.onAllGranted(() -> {
|
||||
startAddAsSecondDeviceActivity();
|
||||
})
|
||||
.onAnyDenied(() -> {
|
||||
startAddAsSecondDeviceActivity();
|
||||
})
|
||||
.execute();
|
||||
} else {
|
||||
startAddAsSecondDeviceActivity();
|
||||
}
|
||||
}
|
||||
|
||||
protected void initializeActionBar() {
|
||||
ActionBar supportActionBar = getSupportActionBar();
|
||||
if (supportActionBar == null) throw new AssertionError();
|
||||
@@ -97,7 +131,7 @@ public class WelcomeActivity extends BaseActionBarActivity implements DcEventCen
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -131,12 +165,6 @@ public class WelcomeActivity extends BaseActionBarActivity implements DcEventCen
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
private void startInstantOnboardingActivity() {
|
||||
Intent intent = new Intent(this, InstantOnboardingActivity.class);
|
||||
intent.putExtra(InstantOnboardingActivity.FROM_WELCOME, true);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void startAddAsSecondDeviceActivity() {
|
||||
new IntentIntegrator(this).setCaptureActivity(RegistrationQrActivity.class)
|
||||
.addExtra(RegistrationQrActivity.ADD_AS_SECOND_DEVICE_EXTRA, true)
|
||||
@@ -352,14 +380,4 @@ public class WelcomeActivity extends BaseActionBarActivity implements DcEventCen
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
AccountManager accountManager = AccountManager.getInstance();
|
||||
if (accountManager.canRollbackAccountCreation(this)) {
|
||||
accountManager.rollbackAccountCreation(this);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-15
@@ -47,24 +47,31 @@ import chat.delta.rpc.RpcException;
|
||||
public class AccountSelectionListFragment extends DialogFragment implements DcEventCenter.DcEventDelegate
|
||||
{
|
||||
private static final String TAG = AccountSelectionListFragment.class.getSimpleName();
|
||||
private final ConversationListActivity activity;
|
||||
private static final String ARG_SELECT_ONLY = "select_only";
|
||||
private RecyclerView recyclerView;
|
||||
private AccountSelectionListAdapter adapter;
|
||||
private boolean selectOnly;
|
||||
|
||||
public AccountSelectionListFragment(ConversationListActivity activity) {
|
||||
super();
|
||||
this.activity = activity;
|
||||
public static AccountSelectionListFragment newInstance(boolean selectOnly) {
|
||||
AccountSelectionListFragment fragment = new AccountSelectionListFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putBoolean(ARG_SELECT_ONLY, selectOnly);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
|
||||
selectOnly = getArguments() != null && getArguments().getBoolean(ARG_SELECT_ONLY, false);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.switch_account)
|
||||
.setNeutralButton(R.string.connectivity, ((dialog, which) -> {
|
||||
startActivity(new Intent(getActivity(), ConnectivityActivity.class));
|
||||
}))
|
||||
.setNegativeButton(R.string.cancel, null);
|
||||
if (!selectOnly) {
|
||||
builder.setNeutralButton(R.string.connectivity, ((dialog, which) -> {
|
||||
startActivity(new Intent(getActivity(), ConnectivityActivity.class));
|
||||
}));
|
||||
}
|
||||
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
View view = inflater.inflate(R.layout.account_selection_list_fragment, null);
|
||||
@@ -99,18 +106,20 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv
|
||||
DcAccounts accounts = DcHelper.getAccounts(getActivity());
|
||||
int[] accountIds = accounts.getAll();
|
||||
|
||||
int[] ids = new int[accountIds.length + 1];
|
||||
int[] ids = new int[(selectOnly? 0 : 1) + accountIds.length];
|
||||
int j = 0;
|
||||
for (int accountId : accountIds) {
|
||||
ids[j++] = accountId;
|
||||
}
|
||||
ids[j] = DC_CONTACT_ID_ADD_ACCOUNT;
|
||||
if (!selectOnly) ids[j] = DC_CONTACT_ID_ADD_ACCOUNT;
|
||||
adapter.changeData(ids, accounts.getSelectedAccount().getAccountId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(@NonNull ContextMenu menu, @NonNull View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
if (selectOnly) return;
|
||||
|
||||
requireActivity().getMenuInflater().inflate(R.menu.account_item_context, menu);
|
||||
|
||||
AccountSelectionListItem listItem = (AccountSelectionListItem) v;
|
||||
@@ -173,6 +182,7 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv
|
||||
}
|
||||
|
||||
private void onSetTag(int accountId) {
|
||||
ConversationListActivity activity = (ConversationListActivity)requireActivity();
|
||||
AccountSelectionListFragment.this.dismiss();
|
||||
|
||||
DcContext dcContext = DcHelper.getAccounts(activity).getAccount(accountId);
|
||||
@@ -188,14 +198,15 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv
|
||||
.setPositiveButton(android.R.string.ok, (d, b) -> {
|
||||
String newTag = inputField.getText().toString().trim();
|
||||
dcContext.setConfig(CONFIG_PRIVATE_TAG, newTag);
|
||||
AccountManager.getInstance().showSwitchAccountMenu(activity);
|
||||
AccountManager.getInstance().showSwitchAccountMenu(activity, selectOnly);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (d, b) -> AccountManager.getInstance().showSwitchAccountMenu(activity))
|
||||
.setNegativeButton(R.string.cancel, (d, b) -> AccountManager.getInstance().showSwitchAccountMenu(activity, selectOnly))
|
||||
.show();
|
||||
}
|
||||
|
||||
private void onDeleteProfile(int accountId) {
|
||||
AccountSelectionListFragment.this.dismiss();
|
||||
ConversationListActivity activity = (ConversationListActivity)requireActivity();
|
||||
DcAccounts accounts = DcHelper.getAccounts(activity);
|
||||
Rpc rpc = DcHelper.getRpc(activity);
|
||||
|
||||
@@ -230,7 +241,7 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv
|
||||
AlertDialog dialog = new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.delete_account)
|
||||
.setView(dialogView)
|
||||
.setNegativeButton(R.string.cancel, (d, which) -> AccountManager.getInstance().showSwitchAccountMenu(activity))
|
||||
.setNegativeButton(R.string.cancel, (d, which) -> AccountManager.getInstance().showSwitchAccountMenu(activity, selectOnly))
|
||||
.setPositiveButton(R.string.delete, (d2, w2) -> activity.onDeleteProfile(accountId))
|
||||
.show();
|
||||
Util.redPositiveButton(dialog);
|
||||
@@ -247,13 +258,14 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv
|
||||
|
||||
@Override
|
||||
public void onItemClick(AccountSelectionListItem contact) {
|
||||
Activity activity = requireActivity();
|
||||
AccountSelectionListFragment.this.dismiss();
|
||||
ConversationListActivity activity = (ConversationListActivity)requireActivity();
|
||||
int accountId = contact.getAccountId();
|
||||
if (accountId == DC_CONTACT_ID_ADD_ACCOUNT) {
|
||||
AccountManager.getInstance().switchAccountAndStartActivity(activity, 0);
|
||||
} else if (accountId != DcHelper.getAccounts(activity).getSelectedAccount().getAccountId()) {
|
||||
AccountManager.getInstance().switchAccountAndStartActivity(activity, accountId);
|
||||
AccountManager.getInstance().switchAccount(activity, accountId);
|
||||
activity.onProfileSwitched(accountId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,356 +0,0 @@
|
||||
package org.thoughtcrime.securesms.audio;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||
import com.google.android.exoplayer2.LoadControl;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.PlaybackException;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.guava.Optional;
|
||||
import org.thoughtcrime.securesms.video.exo.AttachmentDataSourceFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class AudioSlidePlayer {
|
||||
|
||||
private static final String TAG = AudioSlidePlayer.class.getSimpleName();
|
||||
|
||||
private static @NonNull Optional<AudioSlidePlayer> playing = Optional.absent();
|
||||
|
||||
private final @NonNull Context context;
|
||||
private final @NonNull AudioSlide slide;
|
||||
private final @NonNull Handler progressEventHandler;
|
||||
|
||||
private @NonNull WeakReference<Listener> listener;
|
||||
private @Nullable SimpleExoPlayer mediaPlayer;
|
||||
private @Nullable SimpleExoPlayer durationCalculator;
|
||||
|
||||
public synchronized static AudioSlidePlayer createFor(@NonNull Context context,
|
||||
@NonNull AudioSlide slide,
|
||||
@NonNull Listener listener)
|
||||
{
|
||||
if (playing.isPresent() && playing.get().getAudioSlide().equals(slide)) {
|
||||
playing.get().setListener(listener);
|
||||
return playing.get();
|
||||
} else {
|
||||
return new AudioSlidePlayer(context, slide, listener);
|
||||
}
|
||||
}
|
||||
|
||||
private AudioSlidePlayer(@NonNull Context context,
|
||||
@NonNull AudioSlide slide,
|
||||
@NonNull Listener listener)
|
||||
{
|
||||
this.context = context;
|
||||
this.slide = slide;
|
||||
this.listener = new WeakReference<>(listener);
|
||||
this.progressEventHandler = new ProgressEventHandler(this);
|
||||
}
|
||||
|
||||
public void requestDuration() {
|
||||
try {
|
||||
LoadControl loadControl = new DefaultLoadControl.Builder().setBufferDurationsMs(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE).build();
|
||||
durationCalculator = new SimpleExoPlayer.Builder(context, new DefaultRenderersFactory(context))
|
||||
.setTrackSelector(new DefaultTrackSelector(context))
|
||||
.setLoadControl(loadControl)
|
||||
.build();
|
||||
durationCalculator.setPlayWhenReady(false);
|
||||
durationCalculator.addListener(new Player.Listener() {
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
if (playbackState == Player.STATE_READY) {
|
||||
Util.runOnMain(() -> {
|
||||
synchronized (AudioSlidePlayer.this) {
|
||||
if (durationCalculator == null) return;
|
||||
Log.d(TAG, "request duration " + durationCalculator.getDuration());
|
||||
getListener().onReceivedDuration(Long.valueOf(durationCalculator.getDuration()).intValue());
|
||||
durationCalculator.release();
|
||||
durationCalculator.removeListener(this);
|
||||
durationCalculator = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
durationCalculator.prepare(createMediaSource(slide.getUri()));
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
getListener().onReceivedDuration(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void play(final double progress) throws IOException {
|
||||
play(progress, false);
|
||||
}
|
||||
|
||||
private void play(final double progress, boolean earpiece) throws IOException {
|
||||
if (this.mediaPlayer != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (slide.getUri() == null) {
|
||||
throw new IOException("Slide has no URI!");
|
||||
}
|
||||
|
||||
LoadControl loadControl = new DefaultLoadControl.Builder().setBufferDurationsMs(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE).build();
|
||||
this.mediaPlayer = new SimpleExoPlayer.Builder(context, new DefaultRenderersFactory(context))
|
||||
.setTrackSelector(new DefaultTrackSelector(context))
|
||||
.setLoadControl(loadControl)
|
||||
.build();
|
||||
|
||||
mediaPlayer.prepare(createMediaSource(slide.getUri()));
|
||||
mediaPlayer.setPlayWhenReady(true);
|
||||
mediaPlayer.setAudioAttributes(new AudioAttributes.Builder()
|
||||
.setContentType(earpiece ? C.AUDIO_CONTENT_TYPE_SPEECH : C.AUDIO_CONTENT_TYPE_MUSIC)
|
||||
.setUsage(earpiece ? C.USAGE_VOICE_COMMUNICATION : C.USAGE_MEDIA)
|
||||
.build(), false);
|
||||
mediaPlayer.addListener(new Player.Listener() {
|
||||
|
||||
boolean started = false;
|
||||
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
Log.d(TAG, "onPlayerStateChanged(" + playWhenReady + ", " + playbackState + ")");
|
||||
switch (playbackState) {
|
||||
case Player.STATE_READY:
|
||||
|
||||
Log.i(TAG, "onPrepared() " + mediaPlayer.getBufferedPercentage() + "% buffered");
|
||||
synchronized (AudioSlidePlayer.this) {
|
||||
if (mediaPlayer == null) return;
|
||||
Log.d(TAG, "DURATION: " + mediaPlayer.getDuration());
|
||||
|
||||
if (started) {
|
||||
Log.d(TAG, "Already started. Ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
started = true;
|
||||
|
||||
if (progress > 0) {
|
||||
mediaPlayer.seekTo((long) (mediaPlayer.getDuration() * progress));
|
||||
}
|
||||
|
||||
setPlaying(AudioSlidePlayer.this);
|
||||
}
|
||||
|
||||
keepScreenOn(true);
|
||||
notifyOnStart();
|
||||
progressEventHandler.sendEmptyMessage(0);
|
||||
break;
|
||||
|
||||
case Player.STATE_ENDED:
|
||||
Log.i(TAG, "onComplete");
|
||||
synchronized (AudioSlidePlayer.this) {
|
||||
getListener().onReceivedDuration(Long.valueOf(mediaPlayer.getDuration()).intValue());
|
||||
mediaPlayer.release();
|
||||
mediaPlayer = null;
|
||||
}
|
||||
|
||||
keepScreenOn(false);
|
||||
notifyOnStop();
|
||||
progressEventHandler.removeMessages(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(PlaybackException error) {
|
||||
Log.w(TAG, "MediaPlayer Error: " + error);
|
||||
|
||||
synchronized (AudioSlidePlayer.this) {
|
||||
mediaPlayer.release();
|
||||
mediaPlayer = null;
|
||||
}
|
||||
|
||||
notifyOnStop();
|
||||
progressEventHandler.removeMessages(0);
|
||||
|
||||
// Failed to play media file, maybe another app can handle it
|
||||
int msgId = getAudioSlide().getDcMsgId();
|
||||
DcHelper.openForViewOrShare(context, msgId, Intent.ACTION_VIEW);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private MediaSource createMediaSource(@NonNull Uri uri) {
|
||||
DefaultDataSourceFactory defaultDataSourceFactory = new DefaultDataSourceFactory(context, "GenericUserAgent", null);
|
||||
AttachmentDataSourceFactory attachmentDataSourceFactory = new AttachmentDataSourceFactory(defaultDataSourceFactory);
|
||||
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
|
||||
|
||||
return new ProgressiveMediaSource.Factory(attachmentDataSourceFactory, extractorsFactory)
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
Log.i(TAG, "Stop called!");
|
||||
|
||||
keepScreenOn(false);
|
||||
removePlaying(this);
|
||||
|
||||
if (this.mediaPlayer != null) {
|
||||
this.mediaPlayer.stop();
|
||||
this.mediaPlayer.release();
|
||||
}
|
||||
|
||||
this.mediaPlayer = null;
|
||||
}
|
||||
|
||||
public static void stopAll() {
|
||||
if (playing.isPresent()) {
|
||||
synchronized (AudioSlidePlayer.class) {
|
||||
if (playing.isPresent()) {
|
||||
playing.get().stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(@NonNull Listener listener) {
|
||||
this.listener = new WeakReference<>(listener);
|
||||
|
||||
if (this.mediaPlayer != null && this.mediaPlayer.getPlaybackState() == Player.STATE_READY) {
|
||||
notifyOnStart();
|
||||
}
|
||||
}
|
||||
|
||||
public @NonNull AudioSlide getAudioSlide() {
|
||||
return slide;
|
||||
}
|
||||
|
||||
|
||||
private Pair<Double, Integer> getProgress() {
|
||||
if (mediaPlayer == null || mediaPlayer.getCurrentPosition() <= 0 || mediaPlayer.getDuration() <= 0) {
|
||||
return new Pair<>(0D, 0);
|
||||
} else {
|
||||
return new Pair<>((double) mediaPlayer.getCurrentPosition() / (double) mediaPlayer.getDuration(),
|
||||
(int) mediaPlayer.getCurrentPosition());
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyOnStart() {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getListener().onStart();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyOnStop() {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getListener().onStop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyOnProgress(final double progress, final long millis) {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getListener().onProgress(slide, progress, millis);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private @NonNull Listener getListener() {
|
||||
Listener listener = this.listener.get();
|
||||
|
||||
if (listener != null) return listener;
|
||||
else return new Listener() {
|
||||
@Override
|
||||
public void onStart() {}
|
||||
@Override
|
||||
public void onStop() {}
|
||||
@Override
|
||||
public void onProgress(AudioSlide slide, double progress, long millis) {}
|
||||
@Override
|
||||
public void onReceivedDuration(int millis) {}
|
||||
};
|
||||
}
|
||||
|
||||
public void keepScreenOn(boolean keepOn) {
|
||||
if (context instanceof Activity) {
|
||||
if (keepOn) {
|
||||
((Activity) context).getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
} else {
|
||||
((Activity) context).getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized static void setPlaying(@NonNull AudioSlidePlayer player) {
|
||||
if (playing.isPresent() && playing.get() != player) {
|
||||
playing.get().notifyOnStop();
|
||||
playing.get().stop();
|
||||
}
|
||||
|
||||
playing = Optional.of(player);
|
||||
}
|
||||
|
||||
private synchronized static void removePlaying(@NonNull AudioSlidePlayer player) {
|
||||
if (playing.isPresent() && playing.get() == player) {
|
||||
playing = Optional.absent();
|
||||
}
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onStart();
|
||||
void onStop();
|
||||
void onProgress(AudioSlide slide, double progress, long millis);
|
||||
void onReceivedDuration(int millis);
|
||||
}
|
||||
|
||||
private static class ProgressEventHandler extends Handler {
|
||||
|
||||
private final WeakReference<AudioSlidePlayer> playerReference;
|
||||
|
||||
private ProgressEventHandler(@NonNull AudioSlidePlayer player) {
|
||||
this.playerReference = new WeakReference<>(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(@NonNull Message msg) {
|
||||
AudioSlidePlayer player = playerReference.get();
|
||||
|
||||
if (player == null || player.mediaPlayer == null || !isPlayerActive(player.mediaPlayer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Pair<Double, Integer> progress = player.getProgress();
|
||||
player.notifyOnProgress(progress.first, progress.second);
|
||||
sendEmptyMessageDelayed(0, 50);
|
||||
}
|
||||
|
||||
private boolean isPlayerActive(@NonNull SimpleExoPlayer player) {
|
||||
return player.getPlaybackState() == Player.STATE_READY || player.getPlaybackState() == Player.STATE_BUFFERING;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,12 +43,14 @@ public class CallActivity extends WebViewActivity implements DcEventCenter.DcEve
|
||||
public static final String EXTRA_CHAT_ID = "chat_id";
|
||||
public static final String EXTRA_CALL_ID = "call_id";
|
||||
public static final String EXTRA_HASH = "hash";
|
||||
public static final String EXTRA_HAS_VIDEO = "has_video";
|
||||
|
||||
private DcContext dcContext;
|
||||
private Rpc rpc;
|
||||
private int accId;
|
||||
private int chatId;
|
||||
private int callId;
|
||||
private boolean hasVideo;
|
||||
private boolean ended = false;
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@@ -58,7 +60,9 @@ public class CallActivity extends WebViewActivity implements DcEventCenter.DcEve
|
||||
|
||||
Bundle bundle = getIntent().getExtras();
|
||||
assert bundle != null;
|
||||
hasVideo = bundle.getBoolean(EXTRA_HAS_VIDEO, true);
|
||||
String hash = bundle.getString(EXTRA_HASH, "");
|
||||
String query = hasVideo? "" : "?noOutgoingVideoInitially";
|
||||
accId = bundle.getInt(EXTRA_ACCOUNT_ID, -1);
|
||||
chatId = bundle.getInt(EXTRA_CHAT_ID, 0);
|
||||
callId = bundle.getInt(EXTRA_CALL_ID, 0);
|
||||
@@ -94,7 +98,7 @@ public class CallActivity extends WebViewActivity implements DcEventCenter.DcEve
|
||||
.withPermanentDenialDialog(getString(R.string.perm_explain_access_to_camera_denied))
|
||||
.onAllGranted(() -> {
|
||||
String url = "file:///android_asset/calls/index.html";
|
||||
webView.loadUrl(url + hash);
|
||||
webView.loadUrl(url + query + hash);
|
||||
}).onAnyDenied(this::finish)
|
||||
.execute();
|
||||
}
|
||||
@@ -168,7 +172,7 @@ public class CallActivity extends WebViewActivity implements DcEventCenter.DcEve
|
||||
@JavascriptInterface
|
||||
public void startCall(String payload) {
|
||||
try {
|
||||
callId = rpc.placeOutgoingCall(accId, chatId, payload);
|
||||
callId = rpc.placeOutgoingCall(accId, chatId, payload, hasVideo);
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "Error", e);
|
||||
}
|
||||
|
||||
@@ -18,28 +18,29 @@ import java.nio.charset.StandardCharsets;
|
||||
public class CallUtil {
|
||||
private static final String TAG = CallUtil.class.getSimpleName();
|
||||
|
||||
public static void startCall(Activity activity, int chatId) {
|
||||
public static void startCall(Activity activity, int chatId, boolean hasVideo) {
|
||||
Permissions.with(activity)
|
||||
.request(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(activity.getString(R.string.perm_explain_access_to_camera_denied))
|
||||
.onAllGranted(() -> {
|
||||
int accId = DcHelper.getContext(activity).getAccountId();
|
||||
startCall(activity, accId, chatId);
|
||||
startCall(activity, accId, chatId, hasVideo);
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
public static void startCall(Context context, int accId, int chatId) {
|
||||
public static void startCall(Context context, int accId, int chatId, boolean hasVideo) {
|
||||
Intent intent = new Intent(context, CallActivity.class);
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.putExtra(CallActivity.EXTRA_ACCOUNT_ID, accId);
|
||||
intent.putExtra(CallActivity.EXTRA_CHAT_ID, chatId);
|
||||
intent.putExtra(CallActivity.EXTRA_HAS_VIDEO, hasVideo);
|
||||
intent.putExtra(CallActivity.EXTRA_HASH, "#startCall");
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
public static void openCall(Context context, int accId, int chatId, int callId, String payload) {
|
||||
public static void openCall(Context context, int accId, int chatId, int callId, String payload, boolean hasVideo) {
|
||||
String base64 = Base64.encodeToString(payload.getBytes(StandardCharsets.UTF_8), Base64.NO_WRAP);
|
||||
String hash = "";
|
||||
try {
|
||||
@@ -53,6 +54,7 @@ public class CallUtil {
|
||||
intent.putExtra(CallActivity.EXTRA_ACCOUNT_ID, accId);
|
||||
intent.putExtra(CallActivity.EXTRA_CHAT_ID, chatId);
|
||||
intent.putExtra(CallActivity.EXTRA_CALL_ID, callId);
|
||||
intent.putExtra(CallActivity.EXTRA_HAS_VIDEO, hasVideo);
|
||||
intent.putExtra(CallActivity.EXTRA_HASH, hash);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@@ -1,301 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioFocusRequest;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener {
|
||||
|
||||
private static final String TAG = AudioView.class.getSimpleName();
|
||||
|
||||
private final @NonNull AnimatingToggle controlToggle;
|
||||
private final @NonNull ImageView playButton;
|
||||
private final @NonNull ImageView pauseButton;
|
||||
private final @NonNull SeekBar seekBar;
|
||||
private final @NonNull TextView timestamp;
|
||||
private final @NonNull TextView title;
|
||||
private final @NonNull View mask;
|
||||
|
||||
private @Nullable AudioSlidePlayer audioSlidePlayer;
|
||||
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener;
|
||||
private int backwardsCounter;
|
||||
|
||||
public AudioView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AudioView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public AudioView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
inflate(context, R.layout.audio_view, this);
|
||||
|
||||
this.controlToggle = (AnimatingToggle) findViewById(R.id.control_toggle);
|
||||
this.playButton = (ImageView) findViewById(R.id.play);
|
||||
this.pauseButton = (ImageView) findViewById(R.id.pause);
|
||||
this.seekBar = (SeekBar) findViewById(R.id.seek);
|
||||
this.timestamp = (TextView) findViewById(R.id.timestamp);
|
||||
this.title = (TextView) findViewById(R.id.title);
|
||||
this.mask = findViewById(R.id.interception_mask);
|
||||
|
||||
this.timestamp.setText("00:00");
|
||||
|
||||
this.playButton.setOnClickListener(new PlayClickedListener());
|
||||
this.pauseButton.setOnClickListener(new PauseClickedListener());
|
||||
this.seekBar.setOnSeekBarChangeListener(new SeekBarModifiedListener());
|
||||
|
||||
this.playButton.setImageDrawable(context.getDrawable(R.drawable.play_icon));
|
||||
this.pauseButton.setImageDrawable(context.getDrawable(R.drawable.pause_icon));
|
||||
this.playButton.setBackground(context.getDrawable(R.drawable.ic_circle_fill_white_48dp));
|
||||
this.pauseButton.setBackground(context.getDrawable(R.drawable.ic_circle_fill_white_48dp));
|
||||
|
||||
setTint(getContext().getResources().getColor(R.color.audio_icon));
|
||||
}
|
||||
|
||||
public void setAudio(final @NonNull AudioSlide audio, int duration)
|
||||
{
|
||||
controlToggle.displayQuick(playButton);
|
||||
seekBar.setEnabled(true);
|
||||
seekBar.setProgress(0);
|
||||
audioSlidePlayer = AudioSlidePlayer.createFor(getContext(), audio, this);
|
||||
timestamp.setText(DateUtils.getFormatedDuration(duration));
|
||||
|
||||
if(audio.asAttachment().isVoiceNote() || !audio.getFileName().isPresent()) {
|
||||
title.setVisibility(View.GONE);
|
||||
}
|
||||
else {
|
||||
title.setText(audio.getFileName().get());
|
||||
title.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClickListener(OnClickListener listener) {
|
||||
super.setOnClickListener(listener);
|
||||
this.mask.setOnClickListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnLongClickListener(OnLongClickListener listener) {
|
||||
super.setOnLongClickListener(listener);
|
||||
this.mask.setOnLongClickListener(listener);
|
||||
this.playButton.setOnLongClickListener(listener);
|
||||
this.pauseButton.setOnLongClickListener(listener);
|
||||
}
|
||||
|
||||
public void togglePlay() {
|
||||
if (this.playButton.getVisibility() == View.VISIBLE) {
|
||||
playButton.performClick();
|
||||
} else {
|
||||
pauseButton.performClick();
|
||||
}
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
String desc;
|
||||
if (this.title.getVisibility() == View.VISIBLE) {
|
||||
desc = getContext().getString(R.string.audio);
|
||||
} else {
|
||||
desc = getContext().getString(R.string.voice_message);
|
||||
}
|
||||
desc += "\n" + this.timestamp.getText();
|
||||
if (title.getVisibility() == View.VISIBLE) {
|
||||
desc += "\n" + this.title.getText();
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
|
||||
public void setDuration(int duration) {
|
||||
if (getProgress()==0)
|
||||
this.timestamp.setText(DateUtils.getFormatedDuration(duration));
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
if (this.audioSlidePlayer != null && pauseButton.getVisibility() == View.VISIBLE) {
|
||||
this.audioSlidePlayer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedDuration(int millis) {
|
||||
this.timestamp.setText(DateUtils.getFormatedDuration(millis));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
if (this.pauseButton.getVisibility() != View.VISIBLE) {
|
||||
togglePlayToPause();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
if (this.playButton.getVisibility() != View.VISIBLE) {
|
||||
togglePauseToPlay();
|
||||
}
|
||||
|
||||
if (seekBar.getProgress() + 5 >= seekBar.getMax()) {
|
||||
backwardsCounter = 4;
|
||||
onProgress(audioSlidePlayer.getAudioSlide(), 0.0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
public void disablePlayer(boolean disable) {
|
||||
this.mask.setVisibility(disable? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(AudioSlide slide, double progress, long millis) {
|
||||
if (!audioSlidePlayer.getAudioSlide().equals(slide)) {
|
||||
return;
|
||||
}
|
||||
int seekProgress = (int) Math.floor(progress * this.seekBar.getMax());
|
||||
|
||||
if (seekProgress > seekBar.getProgress() || backwardsCounter > 3) {
|
||||
backwardsCounter = 0;
|
||||
this.seekBar.setProgress(seekProgress);
|
||||
if (millis != -1) {
|
||||
this.timestamp.setText(DateUtils.getFormatedDuration(millis));
|
||||
}
|
||||
} else {
|
||||
backwardsCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTint(int foregroundTint) {
|
||||
this.playButton.setBackgroundTintList(ColorStateList.valueOf(foregroundTint));
|
||||
this.pauseButton.setBackgroundTintList(ColorStateList.valueOf(foregroundTint));
|
||||
|
||||
this.seekBar.getProgressDrawable().setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
|
||||
|
||||
this.seekBar.getThumb().setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
public void getSeekBarGlobalVisibleRect(@NonNull Rect rect) {
|
||||
seekBar.getGlobalVisibleRect(rect);
|
||||
}
|
||||
|
||||
private double getProgress() {
|
||||
if (this.seekBar.getProgress() <= 0 || this.seekBar.getMax() <= 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return (double)this.seekBar.getProgress() / (double)this.seekBar.getMax();
|
||||
}
|
||||
}
|
||||
|
||||
private void togglePlayToPause() {
|
||||
controlToggle.displayQuick(pauseButton);
|
||||
|
||||
AnimatedVectorDrawable playToPauseDrawable = (AnimatedVectorDrawable) getContext().getDrawable(R.drawable.play_to_pause_animation);
|
||||
pauseButton.setImageDrawable(playToPauseDrawable);
|
||||
playToPauseDrawable.start();
|
||||
}
|
||||
|
||||
private void togglePauseToPlay() {
|
||||
controlToggle.displayQuick(playButton);
|
||||
|
||||
AnimatedVectorDrawable pauseToPlayDrawable = (AnimatedVectorDrawable) getContext().getDrawable(R.drawable.pause_to_play_animation);
|
||||
playButton.setImageDrawable(pauseToPlayDrawable);
|
||||
pauseToPlayDrawable.start();
|
||||
}
|
||||
|
||||
private class PlayClickedListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
try {
|
||||
Log.w(TAG, "playbutton onClick");
|
||||
if (audioSlidePlayer != null) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
if (audioFocusChangeListener == null) {
|
||||
audioFocusChangeListener = focusChange -> {
|
||||
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
|
||||
pauseButton.performClick();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AudioAttributes playbackAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
||||
.build();
|
||||
|
||||
AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
|
||||
.setAudioAttributes(playbackAttributes)
|
||||
.setAcceptsDelayedFocusGain(false)
|
||||
.setWillPauseWhenDucked(false)
|
||||
.setOnAudioFocusChangeListener(audioFocusChangeListener)
|
||||
.build();
|
||||
|
||||
AudioManager audioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.requestAudioFocus(focusRequest);
|
||||
}
|
||||
|
||||
togglePlayToPause();
|
||||
audioSlidePlayer.play(getProgress());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PauseClickedListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Log.w(TAG, "pausebutton onClick");
|
||||
if (audioSlidePlayer != null) {
|
||||
togglePauseToPlay();
|
||||
audioSlidePlayer.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SeekBarModifiedListener implements SeekBar.OnSeekBarChangeListener {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {}
|
||||
|
||||
@Override
|
||||
public synchronized void onStartTrackingTouch(SeekBar seekBar) {
|
||||
if (audioSlidePlayer != null && pauseButton.getVisibility() == View.VISIBLE) {
|
||||
audioSlidePlayer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onStopTrackingTouch(SeekBar seekBar) {
|
||||
try {
|
||||
if (audioSlidePlayer != null && pauseButton.getVisibility() == View.VISIBLE) {
|
||||
audioSlidePlayer.play(getProgress());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,10 +67,14 @@ public class CallItemView extends FrameLayout {
|
||||
title.setText(R.string.canceled_call);
|
||||
} else if (callInfo.state instanceof CallState.Declined) {
|
||||
title.setText(R.string.declined_call);
|
||||
} else if (callInfo.hasVideo) {
|
||||
title.setText(isOutgoing? R.string.outgoing_video_call : R.string.incoming_video_call);
|
||||
} else {
|
||||
title.setText(isOutgoing? R.string.outgoing_call : R.string.incoming_call);
|
||||
title.setText(isOutgoing? R.string.outgoing_audio_call : R.string.incoming_audio_call);
|
||||
}
|
||||
|
||||
icon.setImageResource(callInfo.hasVideo? R.drawable.ic_videocam_white_24dp : R.drawable.baseline_call_24);
|
||||
|
||||
int[] attrs;
|
||||
if (isOutgoing) {
|
||||
attrs = new int[]{
|
||||
|
||||
@@ -15,39 +15,51 @@ import androidx.annotation.Nullable;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
|
||||
public class ConversationItemFooter extends LinearLayout {
|
||||
|
||||
private TextView dateView;
|
||||
private TextView editedView;
|
||||
private TextView viewsLabel;
|
||||
private ImageView viewsIcon;
|
||||
private ImageView bookmarkIndicatorView;
|
||||
private ImageView emailIndicatorView;
|
||||
private ImageView locationIndicatorView;
|
||||
private DeliveryStatusView deliveryStatusView;
|
||||
private Integer textColor = null;
|
||||
private int callDuration = 0;
|
||||
private Context context;
|
||||
private Rpc rpc;
|
||||
|
||||
public ConversationItemFooter(Context context) {
|
||||
super(context);
|
||||
init(null);
|
||||
init(context, null);
|
||||
}
|
||||
|
||||
public ConversationItemFooter(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(attrs);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
public ConversationItemFooter(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(attrs);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
private void init(@Nullable AttributeSet attrs) {
|
||||
private void init(Context context, @Nullable AttributeSet attrs) {
|
||||
this.context = context;
|
||||
this.rpc = DcHelper.getRpc(context);
|
||||
inflate(getContext(), R.layout.conversation_item_footer, this);
|
||||
|
||||
dateView = findViewById(R.id.footer_date);
|
||||
editedView = findViewById(R.id.footer_edited);
|
||||
viewsLabel = findViewById(R.id.footer_views);
|
||||
viewsIcon = findViewById(R.id.footer_views_icon);
|
||||
bookmarkIndicatorView = findViewById(R.id.footer_bookmark_indicator);
|
||||
emailIndicatorView = findViewById(R.id.footer_email_indicator);
|
||||
locationIndicatorView = findViewById(R.id.footer_location_indicator);
|
||||
@@ -79,13 +91,33 @@ public class ConversationItemFooter extends LinearLayout {
|
||||
}
|
||||
|
||||
locationIndicatorView.setVisibility(messageRecord.hasLocation() ? View.VISIBLE : View.GONE);
|
||||
presentDeliveryStatus(messageRecord);
|
||||
|
||||
boolean isOutChannel = DcHelper.getContext(context).getChat(messageRecord.getChatId()).isOutBroadcast();
|
||||
|
||||
if (isOutChannel && messageRecord.isOutgoing()) {
|
||||
try {
|
||||
int accId = rpc.getSelectedAccountId();
|
||||
int count = rpc.getMessageReadReceiptCount(accId, messageRecord.getId());
|
||||
viewsLabel.setText(String.format("%d", count));
|
||||
viewsLabel.setVisibility(View.VISIBLE);
|
||||
viewsIcon.setVisibility(View.VISIBLE);
|
||||
} catch (RpcException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
viewsLabel.setVisibility(View.GONE);
|
||||
viewsIcon.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
presentDeliveryStatus(messageRecord, isOutChannel);
|
||||
}
|
||||
|
||||
public void setTextColor(int color) {
|
||||
textColor = color;
|
||||
dateView.setTextColor(color);
|
||||
editedView.setTextColor(color);
|
||||
viewsLabel.setTextColor(color);
|
||||
viewsIcon.setColorFilter(color);
|
||||
bookmarkIndicatorView.setColorFilter(color);
|
||||
emailIndicatorView.setColorFilter(color);
|
||||
locationIndicatorView.setColorFilter(color);
|
||||
@@ -106,7 +138,7 @@ public class ConversationItemFooter extends LinearLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private void presentDeliveryStatus(@NonNull DcMsg messageRecord) {
|
||||
private void presentDeliveryStatus(@NonNull DcMsg messageRecord, boolean isOutChannel) {
|
||||
// isDownloading is temporary and should be checked first.
|
||||
boolean isDownloading = messageRecord.getDownloadState() == DcMsg.DC_DOWNLOAD_IN_PROGRESS;
|
||||
boolean isCall = messageRecord.getType() == DcMsg.DC_MSG_CALL;
|
||||
@@ -114,17 +146,11 @@ public class ConversationItemFooter extends LinearLayout {
|
||||
if (isDownloading) deliveryStatusView.setDownloading();
|
||||
else if (messageRecord.isPending()) deliveryStatusView.setPending();
|
||||
else if (messageRecord.isFailed()) deliveryStatusView.setFailed();
|
||||
else if (!messageRecord.isOutgoing() || isCall) deliveryStatusView.setNone();
|
||||
else if (!messageRecord.isOutgoing() || isCall || isOutChannel) deliveryStatusView.setNone();
|
||||
else if (messageRecord.isRemoteRead()) deliveryStatusView.setRead();
|
||||
else if (messageRecord.isDelivered()) deliveryStatusView.setSent();
|
||||
else if (messageRecord.isPreparing()) deliveryStatusView.setPreparing();
|
||||
else deliveryStatusView.setPending();
|
||||
|
||||
if (messageRecord.isFailed()) {
|
||||
deliveryStatusView.setTint(Color.RED);
|
||||
} else {
|
||||
deliveryStatusView.setTint(textColor); // Reset the color to the standard color (because the footer is re-used in a RecyclerView)
|
||||
}
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
|
||||
+20
-2
@@ -11,6 +11,9 @@ import android.widget.ImageView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RemovableEditableMediaView extends FrameLayout {
|
||||
|
||||
private final @NonNull ImageView remove;
|
||||
@@ -19,6 +22,7 @@ public class RemovableEditableMediaView extends FrameLayout {
|
||||
private final int removeSize;
|
||||
|
||||
private @Nullable View current;
|
||||
private final List<OnClickListener> removeClickListeners = new ArrayList<>();
|
||||
|
||||
public RemovableEditableMediaView(Context context) {
|
||||
this(context, null);
|
||||
@@ -72,8 +76,22 @@ public class RemovableEditableMediaView extends FrameLayout {
|
||||
return current;
|
||||
}
|
||||
|
||||
public void setRemoveClickListener(View.OnClickListener listener) {
|
||||
this.remove.setOnClickListener(listener);
|
||||
public void addRemoveClickListener(View.OnClickListener listener) {
|
||||
removeClickListeners.add(listener);
|
||||
updateRemoveClickListener();
|
||||
}
|
||||
|
||||
public void removeRemoveClickListener(View.OnClickListener listener) {
|
||||
removeClickListeners.remove(listener);
|
||||
updateRemoveClickListener();
|
||||
}
|
||||
|
||||
private void updateRemoveClickListener() {
|
||||
this.remove.setOnClickListener(v -> {
|
||||
for (OnClickListener listener : removeClickListeners) {
|
||||
listener.onClick(v);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setEditClickListener(View.OnClickListener listener) {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.thoughtcrime.securesms.components.audioplay;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class AudioPlaybackState {
|
||||
private final int msgId;
|
||||
private final @Nullable Uri audioUri;
|
||||
private final PlaybackStatus status;
|
||||
private final long currentPosition;
|
||||
private final long duration;
|
||||
|
||||
public enum PlaybackStatus {
|
||||
IDLE,
|
||||
LOADING,
|
||||
PLAYING,
|
||||
PAUSED,
|
||||
ERROR
|
||||
}
|
||||
|
||||
public AudioPlaybackState(int msgId,
|
||||
@Nullable Uri audioUri,
|
||||
PlaybackStatus status,
|
||||
long currentPosition,
|
||||
long duration) {
|
||||
this.msgId = msgId;
|
||||
this.audioUri = audioUri;
|
||||
this.status = status;
|
||||
this.currentPosition = currentPosition;
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public static AudioPlaybackState idle() {
|
||||
return new AudioPlaybackState(0, null, PlaybackStatus.IDLE, 0, 0);
|
||||
}
|
||||
|
||||
public int getMsgId() {
|
||||
return msgId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Uri getAudioUri() {
|
||||
return audioUri;
|
||||
}
|
||||
|
||||
public PlaybackStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public long getCurrentPosition() {
|
||||
return currentPosition;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
}
|
||||
+318
@@ -0,0 +1,318 @@
|
||||
package org.thoughtcrime.securesms.components.audioplay;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.session.MediaController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
|
||||
public class AudioPlaybackViewModel extends ViewModel {
|
||||
private static final String TAG = AudioPlaybackViewModel.class.getSimpleName();
|
||||
|
||||
private static final int NON_MESSAGE_AUDIO_MSG_ID = 0; // Audios not attached to a message doesn't have message id.
|
||||
|
||||
private final MutableLiveData<AudioPlaybackState> playbackState;
|
||||
|
||||
private final MutableLiveData<Map<Integer, Long>> durations = new MutableLiveData<>(new HashMap<>());
|
||||
private final Set<Integer> extractionInProgress = new HashSet<>();
|
||||
private final ExecutorService extractionExecutor = Executors.newFixedThreadPool(2);
|
||||
|
||||
private @Nullable MediaController mediaController;
|
||||
private final Handler handler;
|
||||
private boolean isUserSeeking = false;
|
||||
|
||||
public AudioPlaybackViewModel() {
|
||||
playbackState = new MutableLiveData<>(AudioPlaybackState.idle());
|
||||
handler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
public LiveData<AudioPlaybackState> getPlaybackState() {
|
||||
return playbackState;
|
||||
}
|
||||
|
||||
public void setMediaController(@Nullable MediaController controller) {
|
||||
this.mediaController = controller;
|
||||
if (mediaController != null && mediaController.isPlaying()) {
|
||||
startUpdateProgress();
|
||||
}
|
||||
updateCurrentState(true);
|
||||
setupPlayerListener();
|
||||
}
|
||||
|
||||
// Public methods
|
||||
public void loadAudioAndPlay(int msgId, Uri audioUri) {
|
||||
if (mediaController == null) return;
|
||||
|
||||
// Set media item if we have a different audio.
|
||||
if (isDifferentAudio(msgId, audioUri)) {
|
||||
updateState(msgId, audioUri, AudioPlaybackState.PlaybackStatus.LOADING, 0, 0);
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setMediaId(String.valueOf(msgId))
|
||||
.setUri(audioUri)
|
||||
.build();
|
||||
mediaController.setMediaItem(mediaItem);
|
||||
mediaController.prepare();
|
||||
}
|
||||
|
||||
play(msgId, audioUri);
|
||||
}
|
||||
private boolean isSameAudio(int msgId, Uri audioUri) {
|
||||
return !isDifferentAudio(msgId, audioUri);
|
||||
}
|
||||
|
||||
private boolean isDifferentAudio(int msgId, Uri audioUri) {
|
||||
AudioPlaybackState currentState = playbackState.getValue();
|
||||
|
||||
return currentState != null && (
|
||||
msgId != currentState.getMsgId() ||
|
||||
currentState.getAudioUri() == null ||
|
||||
currentState.getAudioUri() != null && !currentState.getAudioUri().equals(audioUri));
|
||||
}
|
||||
|
||||
public LiveData<Map<Integer, Long>> getDurations() {
|
||||
return durations;
|
||||
}
|
||||
|
||||
public void ensureDurationLoaded(Context context, int msgId, Uri audioUri) {
|
||||
// Check cache
|
||||
Map<Integer, Long> currentDurations = durations.getValue();
|
||||
if (currentDurations != null && currentDurations.containsKey(msgId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check extracting
|
||||
synchronized (extractionInProgress) {
|
||||
if (extractionInProgress.contains(msgId)) {
|
||||
return;
|
||||
}
|
||||
extractionInProgress.add(msgId);
|
||||
}
|
||||
|
||||
// Extract in background
|
||||
extractionExecutor.execute(() -> {
|
||||
long duration = extractDurationFromAudio(context, audioUri);
|
||||
|
||||
handler.post(() -> {
|
||||
Map<Integer, Long> updatedDurations = new HashMap<>(durations.getValue());
|
||||
updatedDurations.put(msgId, duration);
|
||||
durations.setValue(updatedDurations);
|
||||
});
|
||||
|
||||
synchronized (extractionInProgress) {
|
||||
extractionInProgress.remove(msgId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private long extractDurationFromAudio(Context context, Uri audioUri) {
|
||||
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
||||
try {
|
||||
retriever.setDataSource(context, audioUri);
|
||||
String durationStr = retriever.extractMetadata(
|
||||
MediaMetadataRetriever.METADATA_KEY_DURATION
|
||||
);
|
||||
return durationStr != null ? Long.parseLong(durationStr) : 0;
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
} finally {
|
||||
try {
|
||||
retriever.release();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public void pause(int msgId, Uri audioUri) {
|
||||
if (mediaController != null && isSameAudio(msgId, audioUri)) {
|
||||
mediaController.pause();
|
||||
}
|
||||
}
|
||||
|
||||
public void play(int msgId, Uri audioUri) {
|
||||
if (mediaController != null && isSameAudio(msgId, audioUri)) {
|
||||
mediaController.play();
|
||||
}
|
||||
}
|
||||
|
||||
public void seekTo(long position, int msgId, Uri audioUri) {
|
||||
if (mediaController != null && isSameAudio(msgId, audioUri)) {
|
||||
mediaController.seekTo(position);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop(int msgId, Uri audioUri) {
|
||||
if (mediaController != null && isSameAudio(msgId, audioUri)) {
|
||||
mediaController.stop();
|
||||
stopUpdateProgress();
|
||||
playbackState.setValue(AudioPlaybackState.idle());
|
||||
}
|
||||
}
|
||||
|
||||
public void stopNonMessageAudioPlayback() {
|
||||
stopByIds(NON_MESSAGE_AUDIO_MSG_ID);
|
||||
}
|
||||
|
||||
// A special method for deleting message, where we only use message Ids
|
||||
public void stopByIds(int... msgIds) {
|
||||
AudioPlaybackState currentState = playbackState.getValue();
|
||||
|
||||
if (mediaController != null && currentState != null) {
|
||||
for (int msgId : msgIds) {
|
||||
if (msgId == currentState.getMsgId()) {
|
||||
mediaController.stop();
|
||||
stopUpdateProgress();
|
||||
playbackState.setValue(AudioPlaybackState.idle());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setUserSeeking(boolean isUserSeeking) {
|
||||
this.isUserSeeking = isUserSeeking;
|
||||
}
|
||||
|
||||
// Private methods
|
||||
private void setupPlayerListener() {
|
||||
if (mediaController == null) return;
|
||||
|
||||
mediaController.addListener(new Player.Listener() {
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
if (events.containsAny(Player.EVENT_IS_PLAYING_CHANGED)) {
|
||||
if (player.isPlaying()) {
|
||||
startUpdateProgress();
|
||||
} else {
|
||||
stopUpdateProgress();
|
||||
}
|
||||
updateCurrentState(false);
|
||||
}
|
||||
if (events.containsAny(Player.EVENT_PLAYBACK_STATE_CHANGED)) {
|
||||
if (player.getPlaybackState() == Player.STATE_READY) {
|
||||
updateCurrentState(false);
|
||||
} else if (player.getPlaybackState() == Player.STATE_ENDED) {
|
||||
// This is to prevent automatically playing after the audio
|
||||
// has been play to the end once, then user dragged the seek bar again
|
||||
mediaController.setPlayWhenReady(false);
|
||||
}
|
||||
}
|
||||
if (events.containsAny(Player.EVENT_PLAYER_ERROR)) {
|
||||
updateCurrentAudioState(AudioPlaybackState.PlaybackStatus.ERROR, 0, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateCurrentState(boolean queryPlaying) {
|
||||
if (mediaController == null) return;
|
||||
|
||||
AudioPlaybackState.PlaybackStatus status;
|
||||
if (mediaController.isPlaying()) {
|
||||
status = AudioPlaybackState.PlaybackStatus.PLAYING;
|
||||
} else if (mediaController.getPlaybackState() == Player.STATE_READY
|
||||
|| mediaController.getPlaybackState() == Player.STATE_ENDED) {
|
||||
status = AudioPlaybackState.PlaybackStatus.PAUSED;
|
||||
} else {
|
||||
status = AudioPlaybackState.PlaybackStatus.IDLE;
|
||||
}
|
||||
|
||||
Uri currentUri = null;
|
||||
int currentMsgId = 0;
|
||||
if (playbackState.getValue() != null) {
|
||||
currentMsgId = playbackState.getValue().getMsgId();
|
||||
currentUri = playbackState.getValue().getAudioUri();
|
||||
}
|
||||
if (queryPlaying || playbackState.getValue() == null) {
|
||||
MediaItem item = mediaController.getCurrentMediaItem();
|
||||
if (item != null) {
|
||||
try {
|
||||
currentMsgId = Integer.parseInt(item.mediaId);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Invalid integer", e);
|
||||
}
|
||||
if (item.localConfiguration != null) {
|
||||
currentUri = item.localConfiguration.uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
updateState(
|
||||
currentMsgId,
|
||||
currentUri,
|
||||
status,
|
||||
mediaController.getCurrentPosition(),
|
||||
mediaController.getDuration());
|
||||
}
|
||||
|
||||
private void updateState(int msgId,
|
||||
Uri audioUri,
|
||||
AudioPlaybackState.PlaybackStatus status,
|
||||
long position,
|
||||
long duration) {
|
||||
// Sanitize longs
|
||||
if (position < 0 || position > Integer.MAX_VALUE) {
|
||||
position = 0;
|
||||
}
|
||||
if (duration < 0 || duration > Integer.MAX_VALUE) {
|
||||
duration = 0;
|
||||
}
|
||||
|
||||
playbackState.setValue(new AudioPlaybackState(
|
||||
msgId, audioUri, status, position, duration
|
||||
));
|
||||
}
|
||||
|
||||
private void updateCurrentAudioState(AudioPlaybackState.PlaybackStatus status,
|
||||
long position,
|
||||
long duration) {
|
||||
AudioPlaybackState current = playbackState.getValue();
|
||||
|
||||
if (current != null) {
|
||||
updateState(current.getMsgId(), current.getAudioUri(), status, position, duration);
|
||||
}
|
||||
}
|
||||
|
||||
// Progress tracking
|
||||
private final Runnable progressRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mediaController != null && mediaController.isPlaying() && !isUserSeeking) {
|
||||
updateCurrentAudioState(AudioPlaybackState.PlaybackStatus.PLAYING,
|
||||
mediaController.getCurrentPosition(),
|
||||
mediaController.getDuration());
|
||||
handler.postDelayed(this, 100);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void startUpdateProgress() {
|
||||
stopUpdateProgress();
|
||||
handler.post(progressRunnable);
|
||||
}
|
||||
|
||||
private void stopUpdateProgress() {
|
||||
handler.removeCallbacks(progressRunnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
stopUpdateProgress();
|
||||
extractionExecutor.shutdown();
|
||||
super.onCleared();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
package org.thoughtcrime.securesms.components.audioplay;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
|
||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class AudioView extends FrameLayout {
|
||||
|
||||
private static final String TAG = AudioView.class.getSimpleName();
|
||||
|
||||
private final @NonNull ImageView playPauseButton;
|
||||
private final AnimatedVectorDrawableCompat playToPauseDrawable;
|
||||
private final AnimatedVectorDrawableCompat pauseToPlayDrawable;
|
||||
private final Drawable playDrawable;
|
||||
private final Drawable pauseDrawable;
|
||||
private final Animatable2Compat.AnimationCallback animationCallback;
|
||||
private final @NonNull SeekBar seekBar;
|
||||
private final @NonNull TextView timestamp;
|
||||
private final @NonNull TextView title;
|
||||
private final @NonNull View mask;
|
||||
private OnActionListener listener;
|
||||
|
||||
private int msgId = -1;
|
||||
private Uri audioUri;
|
||||
private int progress;
|
||||
private int duration;
|
||||
private AudioPlaybackViewModel viewModel;
|
||||
private final Observer<AudioPlaybackState> stateObserver = this::onPlaybackStateChanged;
|
||||
private final Observer<Map<Integer, Long>> durationObserver = this::onDurationsChanged;
|
||||
private boolean isPlaying;
|
||||
|
||||
public AudioView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AudioView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public AudioView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
inflate(context, R.layout.audio_view, this);
|
||||
|
||||
this.playPauseButton = findViewById(R.id.play_pause);
|
||||
this.seekBar = findViewById(R.id.seek);
|
||||
this.timestamp = findViewById(R.id.timestamp);
|
||||
this.title = findViewById(R.id.title);
|
||||
this.mask = findViewById(R.id.interception_mask);
|
||||
|
||||
updateTimestampsAndSeekBar();
|
||||
|
||||
// Load drawables once
|
||||
this.playToPauseDrawable = AnimatedVectorDrawableCompat.create(
|
||||
getContext(), R.drawable.play_to_pause_animation);
|
||||
this.pauseToPlayDrawable = AnimatedVectorDrawableCompat.create(
|
||||
getContext(), R.drawable.pause_to_play_animation);
|
||||
this.playDrawable = AppCompatResources.getDrawable(getContext(), R.drawable.play_icon);
|
||||
this.pauseDrawable = AppCompatResources.getDrawable(getContext(), R.drawable.pause_icon);
|
||||
|
||||
this.animationCallback = new Animatable2Compat.AnimationCallback() {
|
||||
@Override
|
||||
public void onAnimationEnd(Drawable drawable) {
|
||||
Drawable endState = isPlaying ? pauseDrawable : playDrawable;
|
||||
playPauseButton.setImageDrawable(endState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
setupControls();
|
||||
}
|
||||
|
||||
private void setupControls() {
|
||||
// Set up observer in a very specific case when the view is detached and then re-attached,
|
||||
// but binding from adapter has not happened yet
|
||||
if (viewModel != null) {
|
||||
viewModel.getPlaybackState().removeObserver(stateObserver);
|
||||
viewModel.getPlaybackState().observeForever(stateObserver);
|
||||
|
||||
viewModel.getDurations().removeObserver(durationObserver);
|
||||
viewModel.getDurations().observeForever(durationObserver);
|
||||
}
|
||||
|
||||
playPauseButton.setOnClickListener(v -> {
|
||||
Log.w(TAG, "playPauseButton onClick");
|
||||
|
||||
if (viewModel == null || audioUri == null) return;
|
||||
|
||||
AudioPlaybackState state = viewModel.getPlaybackState().getValue();
|
||||
|
||||
if (state != null && msgId == state.getMsgId() && audioUri.equals(state.getAudioUri())) {
|
||||
// Same audio
|
||||
if (state.getStatus() == AudioPlaybackState.PlaybackStatus.PLAYING) {
|
||||
viewModel.pause(msgId, audioUri);
|
||||
} else {
|
||||
viewModel.play(msgId, audioUri);
|
||||
}
|
||||
} else {
|
||||
// Different audio
|
||||
// Note: they can be the same *physical* file, but in different messages
|
||||
viewModel.loadAudioAndPlay(msgId, audioUri);
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
listener.onPlayPauseButtonClicked(v);
|
||||
}
|
||||
});
|
||||
|
||||
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
if (fromUser) {
|
||||
AudioView.this.progress = progress;
|
||||
updateTimestampsAndSeekBar();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
viewModel.setUserSeeking(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
viewModel.setUserSeeking(false);
|
||||
viewModel.seekTo(seekBar.getProgress(), msgId, audioUri);
|
||||
}
|
||||
});
|
||||
|
||||
if (playToPauseDrawable != null) {
|
||||
playToPauseDrawable.registerAnimationCallback(animationCallback);
|
||||
}
|
||||
if (pauseToPlayDrawable != null) {
|
||||
pauseToPlayDrawable.registerAnimationCallback(animationCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
if (viewModel != null) {
|
||||
viewModel.getPlaybackState().removeObserver(stateObserver);
|
||||
viewModel.getDurations().removeObserver(durationObserver);
|
||||
}
|
||||
if (playToPauseDrawable != null) {
|
||||
playToPauseDrawable.clearAnimationCallbacks();
|
||||
}
|
||||
if (pauseToPlayDrawable != null) {
|
||||
pauseToPlayDrawable.clearAnimationCallbacks();
|
||||
}
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
public void setPlaybackViewModel(AudioPlaybackViewModel viewModel) {
|
||||
if (this.viewModel != null) {
|
||||
this.viewModel.getPlaybackState().removeObserver(stateObserver);
|
||||
this.viewModel.getDurations().removeObserver(durationObserver);
|
||||
}
|
||||
|
||||
// ViewModel is used directly for simplicity, since there is no reuse yet
|
||||
this.viewModel = viewModel;
|
||||
|
||||
if (viewModel != null) {
|
||||
viewModel.getPlaybackState().observeForever(stateObserver);
|
||||
viewModel.getDurations().observeForever(durationObserver);
|
||||
}
|
||||
}
|
||||
|
||||
public void setAudio(final @NonNull AudioSlide audio)
|
||||
{
|
||||
msgId = audio.getDcMsgId();
|
||||
audioUri = audio.getUri();
|
||||
playPauseButton.setImageDrawable(playDrawable);
|
||||
|
||||
seekBar.setEnabled(true);
|
||||
|
||||
// Get duration
|
||||
Map<Integer, Long> durations = viewModel.getDurations().getValue();
|
||||
if (durations != null && durations.containsKey(msgId)) {
|
||||
this.duration = Math.toIntExact(durations.get(msgId));
|
||||
updateTimestampsAndSeekBar();
|
||||
} else {
|
||||
viewModel.ensureDurationLoaded(getContext(), msgId, audioUri);
|
||||
}
|
||||
|
||||
if(audio.asAttachment().isVoiceNote() || !audio.getFileName().isPresent()) {
|
||||
title.setVisibility(View.GONE);
|
||||
}
|
||||
else {
|
||||
title.setText(audio.getFileName().get());
|
||||
title.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClickListener(OnClickListener listener) {
|
||||
super.setOnClickListener(listener);
|
||||
this.mask.setOnClickListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnLongClickListener(OnLongClickListener listener) {
|
||||
super.setOnLongClickListener(listener);
|
||||
this.mask.setOnLongClickListener(listener);
|
||||
this.playPauseButton.setOnLongClickListener(listener);
|
||||
}
|
||||
|
||||
public int getMsgId() {
|
||||
return msgId;
|
||||
}
|
||||
|
||||
public Uri getAudioUri() {
|
||||
return audioUri;
|
||||
}
|
||||
|
||||
public interface OnActionListener {
|
||||
void onPlayPauseButtonClicked(View view);
|
||||
}
|
||||
|
||||
public void setOnActionListener(OnActionListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void togglePlay() {
|
||||
playPauseButton.performClick();
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
String desc;
|
||||
if (this.title.getVisibility() == View.VISIBLE) {
|
||||
desc = getContext().getString(R.string.audio);
|
||||
} else {
|
||||
desc = getContext().getString(R.string.voice_message);
|
||||
}
|
||||
desc += "\n" + this.timestamp.getText();
|
||||
if (title.getVisibility() == View.VISIBLE) {
|
||||
desc += "\n" + this.title.getText();
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
|
||||
private void updateProgress(AudioPlaybackState state) {
|
||||
int duration = Math.toIntExact(state.getDuration());
|
||||
int position = Math.toIntExact(state.getCurrentPosition());
|
||||
|
||||
if (duration > 0) {
|
||||
this.progress = position;
|
||||
this.duration = duration;
|
||||
updateTimestampsAndSeekBar();
|
||||
}
|
||||
}
|
||||
|
||||
public void disablePlayer(boolean disable) {
|
||||
this.mask.setVisibility(disable? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
public void getSeekBarGlobalVisibleRect(@NonNull Rect rect) {
|
||||
seekBar.getGlobalVisibleRect(rect);
|
||||
}
|
||||
|
||||
private void togglePlayPause(boolean expectedPlaying) {
|
||||
isPlaying = expectedPlaying;
|
||||
Drawable expectedDrawable = expectedPlaying ? pauseDrawable : playDrawable;
|
||||
|
||||
boolean isAnimating = false;
|
||||
Drawable currentDrawable = playPauseButton.getDrawable();
|
||||
if (currentDrawable instanceof AnimatedVectorDrawableCompat) {
|
||||
isAnimating = ((AnimatedVectorDrawableCompat) currentDrawable).isRunning();
|
||||
}
|
||||
if (!isAnimating && playPauseButton.getDrawable() != expectedDrawable) {
|
||||
AnimatedVectorDrawableCompat animDrawable = expectedPlaying ? playToPauseDrawable : pauseToPlayDrawable;
|
||||
String contentDescription = getContext().getString(
|
||||
expectedPlaying ? R.string.menu_pause : R.string.menu_play);
|
||||
|
||||
if (animDrawable != null) {
|
||||
playPauseButton.setImageDrawable(animDrawable);
|
||||
playPauseButton.setContentDescription(contentDescription);
|
||||
|
||||
animDrawable.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onPlaybackStateChanged(AudioPlaybackState state) {
|
||||
if (audioUri == null || state == null) return;
|
||||
|
||||
// Check if this state is about this message
|
||||
boolean isThisMessage = msgId == state.getMsgId() && audioUri.equals(state.getAudioUri());
|
||||
|
||||
if (isThisMessage) {
|
||||
updateUIForPlaybackState(state);
|
||||
} else {
|
||||
togglePlayPause(false);
|
||||
|
||||
// Also clear progress to avoid confusion
|
||||
this.progress = 0;
|
||||
updateTimestampsAndSeekBar();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUIForPlaybackState(AudioPlaybackState state) {
|
||||
switch (state.getStatus()) {
|
||||
case PLAYING:
|
||||
togglePlayPause(true);
|
||||
updateProgress(state);
|
||||
break;
|
||||
|
||||
case PAUSED:
|
||||
togglePlayPause(false);
|
||||
updateProgress(state);
|
||||
break;
|
||||
|
||||
case LOADING:
|
||||
case ERROR:
|
||||
// No special handling yet
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onDurationsChanged(Map<Integer, Long> durations) {
|
||||
AudioPlaybackState state = viewModel.getPlaybackState().getValue();
|
||||
|
||||
// When there is no playback happening, msgId can be -1 and audioUri is null
|
||||
if (state != null &&
|
||||
msgId >= 0 && msgId == state.getMsgId() &&
|
||||
audioUri != null && audioUri.equals(state.getAudioUri())) {
|
||||
return; // Is playing this message
|
||||
}
|
||||
|
||||
Long duration = durations.get(msgId);
|
||||
if (duration != null && seekBar.getMax() <= 100) {
|
||||
this.duration = Math.toIntExact(duration);
|
||||
updateTimestampsAndSeekBar();
|
||||
seekBar.setMax(this.duration);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTimestampsAndSeekBar() {
|
||||
String progressText = DateUtils.getFormatedDuration(progress);
|
||||
String durationText = DateUtils.getFormatedDuration(duration);
|
||||
timestamp.setText(String.format("%s / %s", progressText, durationText));
|
||||
seekBar.setProgress(progress);
|
||||
seekBar.setMax(duration);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
@@ -14,7 +13,6 @@ import com.b44t.messenger.DcContext;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.ConversationListActivity;
|
||||
import org.thoughtcrime.securesms.InstantOnboardingActivity;
|
||||
import org.thoughtcrime.securesms.WelcomeActivity;
|
||||
import org.thoughtcrime.securesms.accounts.AccountSelectionListFragment;
|
||||
|
||||
@@ -123,10 +121,6 @@ public class AccountManager {
|
||||
}
|
||||
|
||||
public void switchAccountAndStartActivity(Activity activity, int destAccountId) {
|
||||
switchAccountAndStartActivity(activity, destAccountId, null);
|
||||
}
|
||||
|
||||
private void switchAccountAndStartActivity(Activity activity, int destAccountId, @Nullable String backupQr) {
|
||||
if (destAccountId==0) {
|
||||
beginAccountCreation(activity);
|
||||
} else {
|
||||
@@ -134,12 +128,8 @@ public class AccountManager {
|
||||
}
|
||||
|
||||
activity.finishAffinity();
|
||||
if (destAccountId==0) {
|
||||
Intent intent = new Intent(activity, WelcomeActivity.class);
|
||||
if (backupQr != null) {
|
||||
intent.putExtra(WelcomeActivity.BACKUP_QR_EXTRA, backupQr);
|
||||
}
|
||||
activity.startActivity(intent);
|
||||
if (destAccountId == 0) {
|
||||
activity.startActivity(new Intent(activity, WelcomeActivity.class));
|
||||
} else {
|
||||
activity.startActivity(new Intent(activity.getApplicationContext(), ConversationListActivity.class));
|
||||
}
|
||||
@@ -147,12 +137,21 @@ public class AccountManager {
|
||||
|
||||
// ui
|
||||
|
||||
public void showSwitchAccountMenu(ConversationListActivity activity) {
|
||||
AccountSelectionListFragment dialog = new AccountSelectionListFragment(activity);
|
||||
public void showSwitchAccountMenu(ConversationListActivity activity, boolean selectOnly) {
|
||||
AccountSelectionListFragment dialog = AccountSelectionListFragment.newInstance(selectOnly);
|
||||
dialog.show(((FragmentActivity) activity).getSupportFragmentManager(), null);
|
||||
}
|
||||
|
||||
public void addAccountFromSecondDevice(Activity activity, String backupQr) {
|
||||
switchAccountAndStartActivity(activity, 0, backupQr);
|
||||
DcAccounts accounts = DcHelper.getAccounts(activity);
|
||||
if (accounts.getSelectedAccount().isConfigured() == 1) {
|
||||
// the selected account is already configured, create a new one
|
||||
beginAccountCreation(activity);
|
||||
}
|
||||
|
||||
activity.finishAffinity();
|
||||
Intent intent = new Intent(activity, WelcomeActivity.class);
|
||||
intent.putExtra(WelcomeActivity.BACKUP_QR_EXTRA, backupQr);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,23 @@ public class DcEventCenter {
|
||||
});
|
||||
}
|
||||
|
||||
public void handleLogging(@NonNull DcEvent event) {
|
||||
final String logPrefix = "[accId="+event.getAccountId()+ "] ";
|
||||
switch (event.getId()) {
|
||||
case DcContext.DC_EVENT_INFO:
|
||||
Log.i("DeltaChat", logPrefix + event.getData2Str());
|
||||
break;
|
||||
|
||||
case DcContext.DC_EVENT_WARNING:
|
||||
Log.w("DeltaChat", logPrefix + event.getData2Str());
|
||||
break;
|
||||
|
||||
case DcContext.DC_EVENT_ERROR:
|
||||
Log.e("DeltaChat", logPrefix + event.getData2Str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public long handleEvent(@NonNull DcEvent event) {
|
||||
int accountId = event.getAccountId();
|
||||
int id = event.getId();
|
||||
@@ -205,20 +222,7 @@ public class DcEventCenter {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final String logPrefix = "[accId="+accountId + "] ";
|
||||
switch (id) {
|
||||
case DcContext.DC_EVENT_INFO:
|
||||
Log.i("DeltaChat", logPrefix + event.getData2Str());
|
||||
break;
|
||||
|
||||
case DcContext.DC_EVENT_WARNING:
|
||||
Log.w("DeltaChat", logPrefix + event.getData2Str());
|
||||
break;
|
||||
|
||||
case DcContext.DC_EVENT_ERROR:
|
||||
Log.e("DeltaChat", logPrefix + event.getData2Str());
|
||||
break;
|
||||
}
|
||||
handleLogging(event);
|
||||
|
||||
if (accountId != context.getDcContext().getAccountId()) {
|
||||
return 0;
|
||||
|
||||
@@ -132,7 +132,7 @@ public class DcHelper {
|
||||
dcContext.setStockTranslation(68, context.getString(R.string.device_talk));
|
||||
dcContext.setStockTranslation(69, context.getString(R.string.saved_messages));
|
||||
dcContext.setStockTranslation(70, context.getString(R.string.device_talk_explain));
|
||||
dcContext.setStockTranslation(71, context.getString(R.string.device_welcome_message, "https://i.delta.chat/#0A45953086F0C166D3BAF1D4BB2025496E4C2704&x=MVPi07rQBEmHO4FRb3brpwDe&j=n8mkKqu42WAKKUCx1bQOVh23&s=RxuXoa0vhvTs0QLsWM45Ues0&a=adb%40arcanechat.me&n=adb&b=ArcaneChat+Channel"));
|
||||
dcContext.setStockTranslation(71, context.getString(R.string.device_welcome_message, "https://i.delta.chat/#0A45953086F0C166D3BAF1D4BB2025496E4C2704&x=3KvvQZfzU4t-9u5s0PF3USGp&i=X-QDZ681F6Plz_uBu47CKdg4&s=IwE4LraLDcdPBW597wB7DBnI&a=arcanechat%40arcanechat.me&n=ArcaneChat&g=ArcaneChat+Community"));
|
||||
dcContext.setStockTranslation(73, context.getString(R.string.systemmsg_subject_for_new_contact));
|
||||
dcContext.setStockTranslation(74, context.getString(R.string.systemmsg_failed_sending_to));
|
||||
dcContext.setStockTranslation(84, context.getString(R.string.configuration_failed_with_error));
|
||||
@@ -210,8 +210,6 @@ public class DcHelper {
|
||||
dcContext.setStockTranslation(178, context.getString(R.string.member_x_removed));
|
||||
dcContext.setStockTranslation(190, context.getString(R.string.secure_join_wait));
|
||||
dcContext.setStockTranslation(193, context.getString(R.string.donate_device_msg));
|
||||
dcContext.setStockTranslation(194, context.getString(R.string.outgoing_call));
|
||||
dcContext.setStockTranslation(195, context.getString(R.string.incoming_call));
|
||||
dcContext.setStockTranslation(196, context.getString(R.string.declined_call));
|
||||
dcContext.setStockTranslation(197, context.getString(R.string.canceled_call));
|
||||
dcContext.setStockTranslation(198, context.getString(R.string.missed_call));
|
||||
@@ -223,6 +221,12 @@ public class DcHelper {
|
||||
dcContext.setStockTranslation(220, context.getString(R.string.proxy_enabled));
|
||||
dcContext.setStockTranslation(221, context.getString(R.string.proxy_enabled_hint));
|
||||
dcContext.setStockTranslation(230, context.getString(R.string.chat_unencrypted_explanation));
|
||||
dcContext.setStockTranslation(232, context.getString(R.string.outgoing_audio_call));
|
||||
dcContext.setStockTranslation(233, context.getString(R.string.outgoing_video_call));
|
||||
dcContext.setStockTranslation(234, context.getString(R.string.incoming_audio_call));
|
||||
dcContext.setStockTranslation(235, context.getString(R.string.incoming_video_call));
|
||||
dcContext.setStockTranslation(240, context.getString(R.string.chat_description_changed_by_you));
|
||||
dcContext.setStockTranslation(241, context.getString(R.string.chat_description_changed_by_other));
|
||||
}
|
||||
|
||||
public static File getImexDir() {
|
||||
|
||||
@@ -101,6 +101,10 @@ public class ThreadRecord {
|
||||
return spannable;
|
||||
}
|
||||
|
||||
public boolean isOutChannel() {
|
||||
return recipient.getChat() != null && recipient.getChat().isOutBroadcast();
|
||||
}
|
||||
|
||||
public int getUnreadCount() {
|
||||
return unreadCount;
|
||||
}
|
||||
|
||||
@@ -49,13 +49,13 @@ import org.thoughtcrime.securesms.WebxdcActivity;
|
||||
import org.thoughtcrime.securesms.WebxdcStoreActivity;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
|
||||
import org.thoughtcrime.securesms.components.AudioView;
|
||||
import org.thoughtcrime.securesms.components.DocumentView;
|
||||
import org.thoughtcrime.securesms.components.RemovableEditableMediaView;
|
||||
import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.components.VcardView;
|
||||
import org.thoughtcrime.securesms.components.WebxdcView;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioView;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.geolocation.DcLocationManager;
|
||||
@@ -121,7 +121,7 @@ public class AttachmentManager {
|
||||
//this.mapView = ViewUtil.findById(root, R.id.attachment_location);
|
||||
this.removableMediaView = ViewUtil.findById(root, R.id.removable_media_view);
|
||||
|
||||
removableMediaView.setRemoveClickListener(new RemoveButtonListener());
|
||||
removableMediaView.addRemoveClickListener(new RemoveButtonListener());
|
||||
removableMediaView.setEditClickListener(new EditButtonListener());
|
||||
thumbnail.setOnClickListener(new ThumbnailClickListener());
|
||||
}
|
||||
@@ -152,8 +152,6 @@ public class AttachmentManager {
|
||||
|
||||
markGarbage(getSlideUri());
|
||||
slide = Optional.absent();
|
||||
|
||||
audioView.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +233,8 @@ public class AttachmentManager {
|
||||
@NonNull final MediaType mediaType,
|
||||
final int width,
|
||||
final int height,
|
||||
final int chatId)
|
||||
final int chatId,
|
||||
AudioPlaybackViewModel playbackViewModel)
|
||||
{
|
||||
inflateStub();
|
||||
|
||||
@@ -285,26 +284,12 @@ public class AttachmentManager {
|
||||
setAttachmentPresent(true);
|
||||
|
||||
if (slide.hasAudio()) {
|
||||
class SetDurationListener implements AudioSlidePlayer.Listener {
|
||||
@Override
|
||||
public void onStart() {}
|
||||
|
||||
@Override
|
||||
public void onStop() {}
|
||||
|
||||
@Override
|
||||
public void onProgress(AudioSlide slide, double progress, long millis) {}
|
||||
|
||||
@Override
|
||||
public void onReceivedDuration(int millis) {
|
||||
((AudioView) removableMediaView.getCurrent()).setDuration(millis);
|
||||
}
|
||||
}
|
||||
AudioSlidePlayer audioSlidePlayer = AudioSlidePlayer.createFor(context, (AudioSlide) slide, new SetDurationListener());
|
||||
audioSlidePlayer.requestDuration();
|
||||
|
||||
audioView.setAudio((AudioSlide) slide, 0);
|
||||
audioView.setPlaybackViewModel(playbackViewModel);
|
||||
audioView.setAudio((AudioSlide) slide);
|
||||
removableMediaView.display(audioView, false);
|
||||
removableMediaView.addRemoveClickListener(v -> {
|
||||
playbackViewModel.stop(audioView.getMsgId(), audioView.getAudioUri());
|
||||
});
|
||||
result.set(true);
|
||||
} else if (slide.isVcard()) {
|
||||
vcardView.setVcard(glideRequests, (VcardSlide)slide, DcHelper.getRpc(context));
|
||||
|
||||
@@ -61,6 +61,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import chat.delta.rpc.RpcException;
|
||||
|
||||
public class NotificationCenter {
|
||||
private static final String TAG = NotificationCenter.class.getSimpleName();
|
||||
@NonNull private final ApplicationContext context;
|
||||
@@ -164,7 +166,7 @@ public class NotificationCenter {
|
||||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | IntentUtils.FLAG_MUTABLE());
|
||||
}
|
||||
|
||||
public PendingIntent getOpenCallIntent(ChatData chatData, int callId, String payload, boolean autoAccept) {
|
||||
public PendingIntent getOpenCallIntent(ChatData chatData, int callId, String payload, boolean autoAccept, boolean hasVideo) {
|
||||
final Intent chatIntent = new Intent(context, ConversationActivity.class)
|
||||
.putExtra(ConversationActivity.ACCOUNT_ID_EXTRA, chatData.accountId)
|
||||
.putExtra(ConversationActivity.CHAT_ID_EXTRA, chatData.chatId)
|
||||
@@ -184,6 +186,7 @@ public class NotificationCenter {
|
||||
intent.putExtra(CallActivity.EXTRA_CHAT_ID, chatData.chatId);
|
||||
intent.putExtra(CallActivity.EXTRA_CALL_ID, callId);
|
||||
intent.putExtra(CallActivity.EXTRA_HASH, hash);
|
||||
intent.putExtra(CallActivity.EXTRA_HAS_VIDEO, hasVideo);
|
||||
intent.setPackage(context.getPackageName());
|
||||
return TaskStackBuilder.create(context)
|
||||
.addNextIntentWithParentStack(chatIntent)
|
||||
@@ -427,6 +430,13 @@ public class NotificationCenter {
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||
DcContext dcContext = context.getDcAccounts().getAccount(accId);
|
||||
boolean hasVideo;
|
||||
try {
|
||||
hasVideo = context.getRpc().callInfo(accId, callId).hasVideo;
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "Rpc.callInfo() failed", e);
|
||||
hasVideo = false;
|
||||
}
|
||||
int chatId = dcContext.getMsg(callId).getChatId();
|
||||
DcChat dcChat = dcContext.getChat(chatId);
|
||||
String name = dcChat.getName();
|
||||
@@ -434,7 +444,7 @@ public class NotificationCenter {
|
||||
String notificationChannel = getCallNotificationChannel(notificationManager, chatData, name);
|
||||
|
||||
PendingIntent declineCallIntent = getDeclineCallIntent(chatData, callId);
|
||||
PendingIntent openCallIntent = getOpenCallIntent(chatData, callId, payload, false);
|
||||
PendingIntent openCallIntent = getOpenCallIntent(chatData, callId, payload, false, hasVideo);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, notificationChannel)
|
||||
.setSmallIcon(R.drawable.icon_notification)
|
||||
@@ -459,7 +469,7 @@ public class NotificationCenter {
|
||||
new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_videocam_white_24dp,
|
||||
context.getString(R.string.answer_call),
|
||||
getOpenCallIntent(chatData, callId, payload, true)).build());
|
||||
getOpenCallIntent(chatData, callId, payload, true, hasVideo)).build());
|
||||
|
||||
Bitmap bitmap = getAvatar(dcChat);
|
||||
if (bitmap != null) {
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.io.IOException;
|
||||
|
||||
public class AvatarHelper {
|
||||
/* the maximum width/height an avatar should have */
|
||||
public static final int AVATAR_SIZE = 640;
|
||||
public static final int AVATAR_SIZE = 512;
|
||||
|
||||
public static void setGroupAvatar(Context context, int chatId, Bitmap bitmap) {
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
@@ -56,6 +57,8 @@ public class BackupTransferActivity extends BaseActionBarActivity {
|
||||
private boolean notificationControllerClosed = false;
|
||||
public boolean warnAboutCopiedQrCodeOnAbort = false;
|
||||
|
||||
private OnBackPressedCallback backPressedCallback;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
@@ -89,6 +92,15 @@ public class BackupTransferActivity extends BaseActionBarActivity {
|
||||
|
||||
// add padding to avoid content hidden behind system bars
|
||||
ViewUtil.applyWindowInsets(findViewById(R.id.backup_provider_fragment));
|
||||
|
||||
backPressedCallback = new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
finishOrAskToFinish();
|
||||
}
|
||||
};
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, backPressedCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,18 +119,13 @@ public class BackupTransferActivity extends BaseActionBarActivity {
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
finishOrAskToFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
super.onOptionsItemSelected(item);
|
||||
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == android.R.id.home) {
|
||||
finishOrAskToFinish();
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
return true;
|
||||
} else if (itemId == R.id.troubleshooting) {
|
||||
DcHelper.openHelp(this, "#multiclient");
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.thoughtcrime.securesms.qr;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.journeyapps.barcodescanner.BarcodeResult;
|
||||
import com.journeyapps.barcodescanner.CaptureManager;
|
||||
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
|
||||
|
||||
public class CustomCaptureManager extends CaptureManager {
|
||||
|
||||
private OnResultInterceptor interceptor;
|
||||
|
||||
public CustomCaptureManager(Activity activity, DecoratedBarcodeView barcodeView) {
|
||||
super(activity, barcodeView);
|
||||
}
|
||||
|
||||
public void setResultInterceptor(OnResultInterceptor interceptor) {
|
||||
this.interceptor = interceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void returnResult(BarcodeResult rawResult) {
|
||||
if (interceptor != null) {
|
||||
interceptor.onResult(rawResult, () -> {
|
||||
super.returnResult(rawResult);
|
||||
});
|
||||
} else {
|
||||
super.returnResult(rawResult);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnResultInterceptor {
|
||||
void onResult(BarcodeResult result, Runnable finishCallback);
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,9 @@ public class QrCodeHandler {
|
||||
public void handleOnlySecureJoinQr(String rawString, SecurejoinSource source, SecurejoinUiPath uiPath) {
|
||||
final DcLot qrParsed = dcContext.checkQr(rawString);
|
||||
if (!handleSecureJoinQr(qrParsed, rawString, source, uiPath)) {
|
||||
handleDefault(new AlertDialog.Builder(activity), rawString, qrParsed);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
handleDefault(builder, rawString, qrParsed);
|
||||
builder.create().show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +124,9 @@ public class QrCodeHandler {
|
||||
public void handleOnlyAddRelayQr(String rawString) {
|
||||
final DcLot qrParsed = dcContext.checkQr(rawString);
|
||||
if (!handleAddRelayQr(qrParsed, rawString)) {
|
||||
handleDefault(new AlertDialog.Builder(activity), rawString, qrParsed);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
handleDefault(builder, rawString, qrParsed);
|
||||
builder.create().show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,25 +325,29 @@ public class QrCodeHandler {
|
||||
}
|
||||
builder.setMessage(msg);
|
||||
builder.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
|
||||
try {
|
||||
int newChatId = DcHelper.getRpc(activity).secureJoinWithUxInfo(dcContext.getAccountId(), qrRawString, source, uipath);
|
||||
if (newChatId == 0) throw new Exception("Securejoin failed to create a chat");
|
||||
|
||||
Intent intent = new Intent(activity, ConversationActivity.class);
|
||||
intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, newChatId);
|
||||
activity.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
AlertDialog.Builder builder1 = new AlertDialog.Builder(activity);
|
||||
builder1.setMessage(e.getMessage());
|
||||
builder1.setPositiveButton(android.R.string.ok, null);
|
||||
builder1.create().show();
|
||||
}
|
||||
secureJoinByQr(qrRawString, source, uipath);
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
}
|
||||
|
||||
public void addRelay(String qrData) {
|
||||
public void secureJoinByQr(String qrRawString, SecurejoinSource source, SecurejoinUiPath uipath) {
|
||||
try {
|
||||
int newChatId = DcHelper.getRpc(activity).secureJoinWithUxInfo(dcContext.getAccountId(), qrRawString, source, uipath);
|
||||
if (newChatId == 0) throw new Exception("Securejoin failed to create a chat");
|
||||
|
||||
Intent intent = new Intent(activity, ConversationActivity.class);
|
||||
intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, newChatId);
|
||||
activity.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
AlertDialog.Builder builder1 = new AlertDialog.Builder(activity);
|
||||
builder1.setMessage(e.getMessage());
|
||||
builder1.setPositiveButton(android.R.string.ok, null);
|
||||
builder1.create().show();
|
||||
}
|
||||
}
|
||||
|
||||
public void addRelay(String qrData) {
|
||||
ProgressDialog progressDialog = new ProgressDialog(activity);
|
||||
progressDialog.setMessage(activity.getResources().getString(R.string.one_moment));
|
||||
progressDialog.setCanceledOnTouchOutside(false);
|
||||
|
||||
@@ -10,8 +10,12 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.journeyapps.barcodescanner.CaptureManager;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcLot;
|
||||
import com.journeyapps.barcodescanner.CompoundBarcodeView;
|
||||
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity;
|
||||
@@ -26,10 +30,12 @@ public class RegistrationQrActivity extends BaseActionBarActivity {
|
||||
public static final String ADD_AS_SECOND_DEVICE_EXTRA = "add_as_second_device";
|
||||
public static final String QRDATA_EXTRA = "qrdata";
|
||||
|
||||
private CaptureManager capture;
|
||||
private CustomCaptureManager capture;
|
||||
|
||||
private CompoundBarcodeView barcodeScannerView;
|
||||
|
||||
private DcContext dcContext;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -66,6 +72,8 @@ public class RegistrationQrActivity extends BaseActionBarActivity {
|
||||
.onAnyResult(this::handleQrScanWithPermissions)
|
||||
.onAnyDenied(this::handleQrScanWithDeniedPermission)
|
||||
.execute();
|
||||
|
||||
dcContext = DcHelper.getContext(this);
|
||||
}
|
||||
|
||||
private void handleQrScanWithPermissions() {
|
||||
@@ -89,23 +97,70 @@ public class RegistrationQrActivity extends BaseActionBarActivity {
|
||||
DcHelper.openHelp(this, "#multiclient");
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_paste) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(QRDATA_EXTRA, Util.getTextFromClipboard(this));
|
||||
setResult(Activity.RESULT_OK, intent);
|
||||
finish();
|
||||
String rawQr = Util.getTextFromClipboard(this);
|
||||
|
||||
Runnable okCallback = () -> {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(QRDATA_EXTRA, rawQr);
|
||||
setResult(Activity.RESULT_OK, intent);
|
||||
finish();
|
||||
};
|
||||
|
||||
showConfirmDialog(rawQr, okCallback, null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
private void showConfirmDialog(String rawQr, @NonNull Runnable okCallback, @Nullable Runnable cancelCallback) {
|
||||
DcLot qrParsed = dcContext.checkQr(rawQr);
|
||||
|
||||
String dialogMsg = "";
|
||||
if (qrParsed.getState() == DcContext.DC_QR_ASK_VERIFYCONTACT) {
|
||||
String name = dcContext.getContact(qrParsed.getId()).getDisplayName();
|
||||
dialogMsg = getString(R.string.instant_onboarding_confirm_contact, name);
|
||||
} else if (qrParsed.getState() == DcContext.DC_QR_ASK_VERIFYGROUP) {
|
||||
String groupName = qrParsed.getText1();
|
||||
dialogMsg = getString(R.string.instant_onboarding_confirm_group, groupName);
|
||||
}
|
||||
|
||||
if (qrParsed.getState() == DcContext.DC_QR_ASK_VERIFYCONTACT
|
||||
|| qrParsed.getState() == DcContext.DC_QR_ASK_VERIFYGROUP) {
|
||||
AlertDialog confirmDialog = new AlertDialog.Builder(this)
|
||||
.setMessage(dialogMsg)
|
||||
.setPositiveButton("OK", (dialog, which) -> {
|
||||
okCallback.run();
|
||||
})
|
||||
.setNegativeButton("Cancel", (dialog, which) -> {
|
||||
if (cancelCallback != null) {
|
||||
cancelCallback.run();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
} else {
|
||||
okCallback.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
private void init(CompoundBarcodeView barcodeScannerView, Intent intent, Bundle savedInstanceState) {
|
||||
capture = new CaptureManager(this, barcodeScannerView);
|
||||
capture = new CustomCaptureManager(this, barcodeScannerView);
|
||||
|
||||
capture.setResultInterceptor((result, finishCallback) -> {
|
||||
String rawQr = result.getText();
|
||||
|
||||
showConfirmDialog(rawQr, finishCallback, () -> {
|
||||
barcodeScannerView.resume();
|
||||
capture.decode();
|
||||
});
|
||||
});
|
||||
|
||||
capture.initializeFromIntent(intent, savedInstanceState);
|
||||
capture.decode();
|
||||
}
|
||||
|
||||
@@ -36,19 +36,24 @@ import chat.delta.rpc.types.Reactions;
|
||||
|
||||
public class ReactionsDetailsFragment extends DialogFragment implements DcEventCenter.DcEventDelegate {
|
||||
private static final String TAG = ReactionsDetailsFragment.class.getSimpleName();
|
||||
private static final String ARG_MSG_ID = "msg_id";
|
||||
|
||||
private RecyclerView recyclerView;
|
||||
private ReactionRecipientsAdapter adapter;
|
||||
private final int msgId;
|
||||
private int msgId;
|
||||
|
||||
public ReactionsDetailsFragment(int msgId) {
|
||||
super();
|
||||
this.msgId = msgId;
|
||||
public static ReactionsDetailsFragment newInstance(int msgId) {
|
||||
ReactionsDetailsFragment fragment = new ReactionsDetailsFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_MSG_ID, msgId);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
msgId = getArguments() != null? getArguments().getInt(ARG_MSG_ID, 0) : 0;
|
||||
adapter = new ReactionRecipientsAdapter(requireActivity(), GlideApp.with(requireActivity()), new ListClickListener());
|
||||
|
||||
LayoutInflater inflater = requireActivity().getLayoutInflater();
|
||||
|
||||
@@ -3,7 +3,9 @@ package org.thoughtcrime.securesms.relay;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
@@ -47,6 +49,9 @@ public class RelayListActivity extends BaseActionBarActivity
|
||||
/** QR provided via Intent extras needs to be saved to pass it to QrCodeHandler when authorization finishes */
|
||||
private String qrData = null;
|
||||
|
||||
/** Relay selected for context menu via onRelayLongClick() */
|
||||
private EnteredLoginParam contextMenuRelay = null;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -143,14 +148,54 @@ public class RelayListActivity extends BaseActionBarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRelayEdit(EnteredLoginParam relay) {
|
||||
public void onRelayLongClick(View view, EnteredLoginParam relay) {
|
||||
contextMenuRelay = relay;
|
||||
registerForContextMenu(view);
|
||||
openContextMenu(view);
|
||||
unregisterForContextMenu(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
getMenuInflater().inflate(R.menu.relay_item_context, menu);
|
||||
|
||||
boolean nonNullAddr = contextMenuRelay != null && contextMenuRelay.addr != null;
|
||||
boolean isMain = nonNullAddr && contextMenuRelay.addr.equals(adapter.getMainRelay());
|
||||
menu.findItem(R.id.menu_delete_relay).setVisible(!isMain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContextMenuClosed(android.view.Menu menu) {
|
||||
super.onContextMenuClosed(menu);
|
||||
contextMenuRelay = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(@NonNull MenuItem item) {
|
||||
if (contextMenuRelay == null) return super.onContextItemSelected(item);
|
||||
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == R.id.menu_edit_relay) {
|
||||
onRelayEdit(contextMenuRelay);
|
||||
contextMenuRelay = null;
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_delete_relay) {
|
||||
onRelayDelete(contextMenuRelay);
|
||||
contextMenuRelay = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
|
||||
private void onRelayEdit(EnteredLoginParam relay) {
|
||||
Intent intent = new Intent(this, EditRelayActivity.class);
|
||||
intent.putExtra(EditRelayActivity.EXTRA_ADDR, relay.addr);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRelayDelete(EnteredLoginParam relay) {
|
||||
private void onRelayDelete(EnteredLoginParam relay) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.remove_transport)
|
||||
.setMessage(getString(R.string.confirm_remove_transport, relay.addr))
|
||||
|
||||
@@ -25,8 +25,7 @@ public class RelayListAdapter extends RecyclerView.Adapter<RelayListAdapter.Rela
|
||||
|
||||
public interface OnRelayClickListener {
|
||||
void onRelayClick(EnteredLoginParam relay);
|
||||
void onRelayEdit(EnteredLoginParam relay);
|
||||
void onRelayDelete(EnteredLoginParam relay);
|
||||
void onRelayLongClick(View view, EnteredLoginParam relay);
|
||||
}
|
||||
|
||||
public RelayListAdapter(OnRelayClickListener listener) {
|
||||
@@ -67,16 +66,12 @@ public class RelayListAdapter extends RecyclerView.Adapter<RelayListAdapter.Rela
|
||||
private final TextView titleText;
|
||||
private final TextView subtitleText;
|
||||
private final ImageView mainIndicator;
|
||||
private final ImageView editButton;
|
||||
private final ImageView deleteButton;
|
||||
|
||||
public RelayViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
titleText = itemView.findViewById(R.id.title);
|
||||
subtitleText = itemView.findViewById(R.id.subtitle);
|
||||
mainIndicator = itemView.findViewById(R.id.main_indicator);
|
||||
editButton = itemView.findViewById(R.id.edit_button);
|
||||
deleteButton = itemView.findViewById(R.id.delete_button);
|
||||
}
|
||||
|
||||
public void bind(EnteredLoginParam relay, boolean isMain, OnRelayClickListener listener) {
|
||||
@@ -84,7 +79,6 @@ public class RelayListAdapter extends RecyclerView.Adapter<RelayListAdapter.Rela
|
||||
titleText.setText(parts.length == 2? parts[1] : parts[0]);
|
||||
subtitleText.setText(parts.length == 2? parts[0] : "");
|
||||
mainIndicator.setVisibility(isMain ? View.VISIBLE : View.INVISIBLE);
|
||||
deleteButton.setVisibility(isMain ? View.GONE : View.VISIBLE);
|
||||
|
||||
itemView.setOnClickListener(v -> {
|
||||
if (listener != null) {
|
||||
@@ -92,16 +86,11 @@ public class RelayListAdapter extends RecyclerView.Adapter<RelayListAdapter.Rela
|
||||
}
|
||||
});
|
||||
|
||||
editButton.setOnClickListener(v -> {
|
||||
itemView.setOnLongClickListener(v -> {
|
||||
if (listener != null) {
|
||||
listener.onRelayEdit(relay);
|
||||
}
|
||||
});
|
||||
|
||||
deleteButton.setOnClickListener(v -> {
|
||||
if (listener != null) {
|
||||
listener.onRelayDelete(relay);
|
||||
listener.onRelayLongClick(v, relay);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,4 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity {
|
||||
boolean cropAvatar = getIntent().getBooleanExtra(CROP_AVATAR, false);
|
||||
imageEditorFragment = initFragment(R.id.scribble_container, ImageEditorFragment.newInstance(getIntent().getData(), cropAvatar));
|
||||
}
|
||||
|
||||
/* @Override
|
||||
public void onBackPressed() {
|
||||
if (!imageEditorFragment.onBackPressed()) {
|
||||
super.onBackPressed();
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class StickerSelectActivity extends FragmentActivity implements StickerSe
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.session.MediaSession;
|
||||
import androidx.media3.session.MediaSessionService;
|
||||
import androidx.media3.session.SessionCommand;
|
||||
import androidx.media3.session.SessionCommands;
|
||||
import androidx.media3.session.SessionResult;
|
||||
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import org.thoughtcrime.securesms.ConversationListActivity;
|
||||
|
||||
public class AudioPlaybackService extends MediaSessionService {
|
||||
|
||||
private static final String TAG = AudioPlaybackService.class.getSimpleName();
|
||||
|
||||
private ExoPlayer player;
|
||||
private MediaSession session;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(C.USAGE_MEDIA) // USAGE_VOICE_COMMUNICATION is for VoIP calls
|
||||
.setContentType(C.AUDIO_CONTENT_TYPE_SPEECH)
|
||||
.build();
|
||||
|
||||
player = new ExoPlayer.Builder(this)
|
||||
.setAudioAttributes(audioAttributes, true)
|
||||
.setHandleAudioBecomingNoisy(true)
|
||||
.build();
|
||||
|
||||
// This is for click on the notification to go back to app
|
||||
Intent intent = new Intent(this, ConversationListActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
PendingIntent initialIntent = PendingIntent.getActivity(
|
||||
this, 0, intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
session = new MediaSession.Builder(this, player)
|
||||
.setSessionActivity(initialIntent)
|
||||
.setCallback(new MediaSession.Callback() {
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
@Override
|
||||
public MediaSession.ConnectionResult onConnect(
|
||||
MediaSession session,
|
||||
MediaSession.ControllerInfo controller
|
||||
) {
|
||||
SessionCommands sessionCommands = MediaSession
|
||||
.ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
|
||||
.add(new SessionCommand("UPDATE_ACTIVITY_CONTEXT", new Bundle()))
|
||||
.build();
|
||||
|
||||
return new MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
||||
.setAvailableSessionCommands(sessionCommands)
|
||||
.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ListenableFuture<SessionResult> onCustomCommand(
|
||||
MediaSession session,
|
||||
MediaSession.ControllerInfo controller,
|
||||
SessionCommand customCommand,
|
||||
Bundle args
|
||||
) {
|
||||
if ("UPDATE_ACTIVITY_CONTEXT".equals(customCommand.customAction)) {
|
||||
updateSessionActivity(args);
|
||||
}
|
||||
return Futures.immediateFuture(
|
||||
new SessionResult(SessionResult.RESULT_SUCCESS));
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
private void updateSessionActivity(Bundle args) {
|
||||
try {
|
||||
// Put all the original extras back into the intent
|
||||
if (args != null && !args.isEmpty()) {
|
||||
String activityClassName = args.getString("activity_class");
|
||||
args.remove("activity_class");
|
||||
|
||||
if (activityClassName != null) {
|
||||
Class<?> activityClass = Class.forName(activityClassName);
|
||||
Intent intent = new Intent(this, activityClass);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
intent.putExtras(args);
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
session.setSessionActivity(pendingIntent);
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
Log.e(TAG, "Activity class not found", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
|
||||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (session != null) {
|
||||
session.release();
|
||||
session = null;
|
||||
}
|
||||
if (player != null) {
|
||||
player.release();
|
||||
player = null;
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import static org.thoughtcrime.securesms.util.ShareUtil.getForwardedMessageAccountId;
|
||||
import static org.thoughtcrime.securesms.util.ShareUtil.getForwardedMessageIDs;
|
||||
import static org.thoughtcrime.securesms.util.ShareUtil.getSharedText;
|
||||
import static org.thoughtcrime.securesms.util.ShareUtil.getSharedUris;
|
||||
@@ -29,6 +30,11 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
|
||||
public class SendRelayedMessageUtil {
|
||||
private static final String TAG = SendRelayedMessageUtil.class.getSimpleName();
|
||||
@@ -40,12 +46,27 @@ public class SendRelayedMessageUtil {
|
||||
public static void immediatelyRelay(Activity activity, final Long[] chatIds) {
|
||||
ConversationListRelayingActivity.finishActivity();
|
||||
if (isForwarding(activity)) {
|
||||
int forwardedMsgAccId = getForwardedMessageAccountId(activity);
|
||||
int[] forwardedMessageIDs = getForwardedMessageIDs(activity);
|
||||
resetRelayingMessageContent(activity);
|
||||
if (forwardedMessageIDs == null) return;
|
||||
if (forwardedMessageIDs == null || forwardedMsgAccId <= 0) return;
|
||||
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
DcContext dcContext = DcHelper.getContext(activity);
|
||||
int accId = dcContext.getAccountId();
|
||||
if (forwardedMsgAccId != accId) {
|
||||
Rpc rpc = DcHelper.getRpc(activity);
|
||||
List<Integer> list = Util.toList(forwardedMessageIDs);
|
||||
for (long longChatId : chatIds) {
|
||||
try {
|
||||
rpc.forwardMessagesToAccount(forwardedMsgAccId, list, accId, (int)longChatId);
|
||||
} catch (RpcException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (long longChatId : chatIds) {
|
||||
int chatId = (int) longChatId;
|
||||
if (dcContext.getChat(chatId).isSelfTalk()) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.annotation.NonNull;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ShareUtil {
|
||||
private static final String FORWARDED_MESSAGE_ACCID = "forwarded_message_accid";
|
||||
private static final String FORWARDED_MESSAGE_IDS = "forwarded_message_ids";
|
||||
private static final String SHARED_URIS = "shared_uris";
|
||||
private static final String SHARED_CONTACT_ID = "shared_contact_id";
|
||||
@@ -27,11 +28,7 @@ public class ShareUtil {
|
||||
}
|
||||
|
||||
public static boolean isForwarding(Activity activity) {
|
||||
try {
|
||||
return activity.getIntent().getIntArrayExtra(FORWARDED_MESSAGE_IDS) != null;
|
||||
} catch (NullPointerException npe) {
|
||||
return false;
|
||||
}
|
||||
return getForwardedMessageAccountId(activity) > 0;
|
||||
}
|
||||
|
||||
public static boolean isSharing(Activity activity) {
|
||||
@@ -66,6 +63,14 @@ public class ShareUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static int getForwardedMessageAccountId(Activity activity) {
|
||||
try {
|
||||
return activity.getIntent().getIntExtra(FORWARDED_MESSAGE_ACCID, -1);
|
||||
} catch (NullPointerException npe) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int[] getForwardedMessageIDs(Activity activity) {
|
||||
try {
|
||||
return activity.getIntent().getIntArrayExtra(FORWARDED_MESSAGE_IDS);
|
||||
@@ -137,6 +142,7 @@ public class ShareUtil {
|
||||
|
||||
public static void resetRelayingMessageContent(Activity activity) {
|
||||
try {
|
||||
activity.getIntent().removeExtra(FORWARDED_MESSAGE_ACCID);
|
||||
activity.getIntent().removeExtra(FORWARDED_MESSAGE_IDS);
|
||||
activity.getIntent().removeExtra(SHARED_URIS);
|
||||
activity.getIntent().removeExtra(SHARED_CONTACT_ID);
|
||||
@@ -153,7 +159,8 @@ public class ShareUtil {
|
||||
|
||||
public static void acquireRelayMessageContent(Activity currentActivity, @NonNull Intent newActivityIntent) {
|
||||
if (isForwarding(currentActivity)) {
|
||||
newActivityIntent.putExtra(FORWARDED_MESSAGE_IDS, getForwardedMessageIDs(currentActivity));
|
||||
int accId = getForwardedMessageAccountId(currentActivity);
|
||||
setForwardingMessageIds(newActivityIntent, getForwardedMessageIDs(currentActivity), accId);
|
||||
} else if (isSharing(currentActivity)) {
|
||||
newActivityIntent.putExtra(IS_SHARING, true);
|
||||
if (isDirectSharing(currentActivity)) {
|
||||
@@ -180,7 +187,8 @@ public class ShareUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static void setForwardingMessageIds(Intent composeIntent, int[] messageIds) {
|
||||
public static void setForwardingMessageIds(Intent composeIntent, int[] messageIds, int accId) {
|
||||
composeIntent.putExtra(FORWARDED_MESSAGE_ACCID, accId);
|
||||
composeIntent.putExtra(FORWARDED_MESSAGE_IDS, messageIds);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.text.BidiFormatter;
|
||||
|
||||
public class TextUtil {
|
||||
@NonNull
|
||||
public static String bidiAppend(String text, String suffix) {
|
||||
return BidiFormatter.getInstance().unicodeWrap(text) + suffix;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String markAsExternal(String text) {
|
||||
return bidiAppend(text, " ↗");
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,9 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@@ -115,6 +117,14 @@ public class Util {
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Integer> toList(int[] array) {
|
||||
ArrayList<Integer> list = new ArrayList<>(array.length);
|
||||
for (int i : array) {
|
||||
list.add(i);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
|
||||
if (cur == null) {
|
||||
return new int[] { val };
|
||||
|
||||
@@ -323,7 +323,15 @@ public class ViewUtil {
|
||||
* @param view The view to apply insets to
|
||||
*/
|
||||
public static void applyWindowInsetsAsMargin(@NonNull View view) {
|
||||
applyWindowInsetsAsMargin(view, true, true, true, true);
|
||||
applyWindowInsetsAsMargin(view, true, true, true, true, false);
|
||||
}
|
||||
|
||||
public static void applyWindowInsetsAsMargin(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom) {
|
||||
applyWindowInsetsAsMargin(view, left, top, right, bottom, false);
|
||||
}
|
||||
|
||||
public static void forceApplyWindowInsetsAsMargin(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom) {
|
||||
applyWindowInsetsAsMargin(view, left, top, right, bottom, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -337,8 +345,9 @@ public class ViewUtil {
|
||||
* @param top Whether to apply top inset
|
||||
* @param right Whether to apply right inset
|
||||
* @param bottom Whether to apply bottom inset
|
||||
* @param forceDispatch Whether to force application of insets
|
||||
*/
|
||||
public static void applyWindowInsetsAsMargin(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom) {
|
||||
public static void applyWindowInsetsAsMargin(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom, boolean forceDispatch) {
|
||||
// Only enable on API 30+ where WindowInsets APIs work correctly
|
||||
if (!isEdgeToEdgeSupported()) return;
|
||||
|
||||
@@ -371,10 +380,10 @@ public class ViewUtil {
|
||||
ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
|
||||
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
||||
ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) layoutParams;
|
||||
marginParams.leftMargin = baseMarginLeft + insets.left;
|
||||
marginParams.topMargin = baseMarginTop + insets.top;
|
||||
marginParams.rightMargin = baseMarginRight + insets.right;
|
||||
marginParams.bottomMargin = baseMarginBottom + insets.bottom;
|
||||
marginParams.leftMargin = left ? baseMarginLeft + insets.left : baseMarginLeft;
|
||||
marginParams.topMargin = top ? baseMarginTop + insets.top : baseMarginTop;
|
||||
marginParams.rightMargin = right ? baseMarginRight + insets.right : baseMarginRight;
|
||||
marginParams.bottomMargin = bottom ? baseMarginBottom + insets.bottom : baseMarginBottom;
|
||||
v.setLayoutParams(marginParams);
|
||||
}
|
||||
|
||||
@@ -383,18 +392,54 @@ public class ViewUtil {
|
||||
|
||||
// Request the initial insets to be dispatched if the view is attached
|
||||
if (view.isAttachedToWindow()) {
|
||||
ViewCompat.requestApplyInsets(view);
|
||||
if (forceDispatch) {
|
||||
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(view);
|
||||
if (insets != null) {
|
||||
ViewCompat.dispatchApplyWindowInsets(view, insets);
|
||||
}
|
||||
} else {
|
||||
ViewCompat.requestApplyInsets(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply window insets to a view by adding padding to avoid drawing elements behind system bars.
|
||||
* Convenience method that applies insets to all sides.
|
||||
* IME insets are propagated to child views.
|
||||
*
|
||||
* @param view The view to apply insets to
|
||||
*/
|
||||
public static void applyWindowInsets(@NonNull View view) {
|
||||
applyWindowInsets(view, true, true, true, true);
|
||||
applyWindowInsets(view, true, true, true, true, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply window insets to a view by adding padding to avoid drawing elements behind system bars.
|
||||
*
|
||||
* IME insets are propagated to child views.
|
||||
*
|
||||
* @param view The view to apply insets to
|
||||
* @param left Whether to apply left inset
|
||||
* @param top Whether to apply top inset
|
||||
* @param right Whether to apply right inset
|
||||
* @param bottom Whether to apply bottom inset
|
||||
*/
|
||||
public static void applyWindowInsets(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom) {
|
||||
applyWindowInsets(view, left, top, right, bottom, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force applying window insets to a view by adding padding to avoid drawing elements behind system bars.
|
||||
*
|
||||
* @param view The view to apply insets to
|
||||
* @param left Whether to apply left inset
|
||||
* @param top Whether to apply top inset
|
||||
* @param right Whether to apply right inset
|
||||
* @param bottom Whether to apply bottom inset
|
||||
*/
|
||||
public static void forceApplyWindowInsets(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom) {
|
||||
applyWindowInsets(view, left, top, right, bottom, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -408,8 +453,10 @@ public class ViewUtil {
|
||||
* @param top Whether to apply top inset
|
||||
* @param right Whether to apply right inset
|
||||
* @param bottom Whether to apply bottom inset
|
||||
* @param consumeImeInsets Whether to consume IME insets so they don't propagate to child views
|
||||
* @param forceDispatch Force application of Insets, regardless if system think it shall dispatch
|
||||
*/
|
||||
public static void applyWindowInsets(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom) {
|
||||
public static void applyWindowInsets(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom, boolean consumeImeInsets, boolean forceDispatch) {
|
||||
// Only enable on API 30+ where WindowInsets APIs work correctly
|
||||
if (!isEdgeToEdgeSupported()) return;
|
||||
|
||||
@@ -442,12 +489,24 @@ public class ViewUtil {
|
||||
bottom ? basePaddingBottom + insets.bottom : basePaddingBottom
|
||||
);
|
||||
|
||||
if (consumeImeInsets) {
|
||||
windowInsets = new WindowInsetsCompat.Builder(windowInsets)
|
||||
.setInsets(WindowInsetsCompat.Type.ime(), Insets.NONE)
|
||||
.build();
|
||||
}
|
||||
return windowInsets;
|
||||
});
|
||||
|
||||
// Request the initial insets to be dispatched if the view is attached
|
||||
if (view.isAttachedToWindow()) {
|
||||
ViewCompat.requestApplyInsets(view);
|
||||
if (forceDispatch) {
|
||||
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(view);
|
||||
if (insets != null) {
|
||||
ViewCompat.dispatchApplyWindowInsets(view, insets);
|
||||
}
|
||||
} else {
|
||||
ViewCompat.requestApplyInsets(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user