Compare commits

..

4 Commits

Author SHA1 Message Date
adbenitez 98d711e0ca tweak createForExternal 2025-12-22 18:23:08 +01:00
copilot-swe-agent[bot] 99c848b1b7 Add clarifying comment about fallback logic
Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2025-12-22 16:58:38 +00:00
copilot-swe-agent[bot] fe26af88b4 Fix quick-camera button crash on devices with external SD cards
Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2025-12-22 16:55:15 +00:00
copilot-swe-agent[bot] 6af6ff80ba Initial plan 2025-12-22 16:51:35 +00:00
170 changed files with 4066 additions and 6194 deletions
+1 -5
View File
@@ -59,8 +59,6 @@ jobs:
rm build/outputs/apk/foss/release/*universal*
./gradlew assembleGplayRelease
mv build/outputs/apk/gplay/release/*universal* build/outputs/apk/foss/release/ArcaneChat-gplay.apk
mv build/outputs/mapping/fossRelease/mapping.txt build/outputs/mapping/fossRelease/mapping-foss.txt
mv build/outputs/mapping/gplayRelease/mapping.txt build/outputs/mapping/fossRelease/mapping-gplay.txt
- name: Release on GitHub
uses: softprops/action-gh-release@v1
@@ -69,9 +67,7 @@ jobs:
body: '[<img src="store/get-it-on-gplay.png" alt="Get it on Google Play" height="48">](https://play.google.com/store/apps/details?id=com.github.arcanechat) [<img src="store/get-it-on-fdroid.png" alt="Get it on F-Droid" height="48">](https://f-droid.org/packages/chat.delta.lite) [<img src="store/get-it-on-github.png" alt="Get it on GitHub" height="48">](https://github.com/ArcaneChat/android/releases/latest/download/ArcaneChat-gplay.apk)'
prerelease: ${{ contains(github.event.ref, '-beta') }}
fail_on_unmatched_files: true
files: |
build/outputs/apk/foss/release/*.apk
build/outputs/mapping/fossRelease/mapping-*.txt
files: build/outputs/apk/foss/release/*.apk
- name: Release on ZapStore
run: |
-37
View File
@@ -1,42 +1,5 @@
# Delta Chat Android Changelog
## v2.40.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
* 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
* 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
* 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.40.0
## v2.35.0
2026-01
* Protect profile deletion and relays management with system lock/pin
* Fix: Remove address from profile switcher
* Fix: Avoid crash if the system doesn't allow to start foreground service
* Remove deprecated "real-time apps" switch
* Update to core 2.35.0
## v2.34.0
2025-12
+3 -11
View File
@@ -33,8 +33,8 @@ android {
useLibrary 'org.apache.http.legacy'
defaultConfig {
versionCode 30000737
versionName "2.40.0"
versionCode 30000735
versionName "2.34.0"
applicationId "chat.delta.lite"
multiDexEnabled true
@@ -68,7 +68,6 @@ android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
coreLibraryDesugaringEnabled true
}
packagingOptions {
jniLibs {
@@ -212,10 +211,6 @@ dependencies {
implementation "io.noties.markwon:inline-parser:$markwon_version"
implementation 'com.airbnb.android:lottie:4.2.2' // Lottie animations support.
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
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'
@@ -236,11 +231,8 @@ 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' // FIXME: exoplayer dependencies kept for Video, but we shall migrate them at some point
implementation 'com.google.android.exoplayer:exoplayer-core:2.19.1' // plays video and audio
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 446 KiB

After

Width:  |  Height:  |  Size: 447 KiB

+3 -39
View File
@@ -153,42 +153,6 @@ 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
******************************************************************************/
@@ -208,11 +172,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, jobject chanObj)
JNIEXPORT jlong Java_com_b44t_messenger_DcAccounts_createAccountsCPtr(JNIEnv *env, jobject obj, jstring dir)
{
CHAR_REF(dir);
int writable = 1;
jlong accountsCPtr = (jlong)dc_accounts_new_with_event_channel(dirPtr, writable, get_dc_event_channel(env, chanObj));
jlong accountsCPtr = (jlong)dc_accounts_new(dirPtr, writable);
CHAR_UNREF(dir);
return accountsCPtr;
}
@@ -1993,7 +1957,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_create_qr_svg(dc_backup_provider_get_qr(get_dc_backup_provider(env, obj)));
char* temp = dc_backup_provider_get_qr_svg(get_dc_backup_provider(env, obj));
jstring ret = JSTRING_NEW(temp);
dc_str_unref(temp);
return ret;
@@ -91,19 +91,7 @@ public class FcmReceiveService extends FirebaseMessagingService {
@Override
public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
Log.i(TAG, "FCM push notification received");
// Note: The system can downgrade the high priority messages to normal priority
// if the app is not using the high priority messages for surfacing time sensitive
// content to the user. If the message's priority is downgraded, your app cannot
// start a foreground service and attempting to start one results in a
// ForegroundServiceStartNotAllowedException.
// So, it's recommended to check the result of RemoteMessage.getPriority() and
// confirm it's PRIORITY_HIGH() before attempting to start a foreground service.
// source: https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start
if (remoteMessage.getPriority() == RemoteMessage.PRIORITY_HIGH) {
FetchForegroundService.start(this);
} else {
FetchForegroundService.fetchSynchronously();
}
FetchForegroundService.start(this);
}
@Override
+18 -16
View File
@@ -38,7 +38,6 @@
<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"/>
@@ -55,6 +54,7 @@
android:requestLegacyExternalStorage="true"
tools:targetApi="TIRAMISU"
android:hasFragileUserData="true"
android:enableOnBackInvokedCallback="false"
>
<!-- android car support, see https://developer.android.com/training/auto/start/,
@@ -93,7 +93,6 @@
<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>
@@ -209,7 +208,16 @@
<activity android:name=".NewConversationActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="true">
<intent-filter>
<data android:scheme="mailto"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
<activity android:name=".ContactMultiSelectionActivity"
@@ -221,14 +229,15 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".WelcomeActivity"
android:launchMode="singleTop"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".relay.EditRelayActivity"
android:launchMode="singleTask"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="true">
</activity>
<activity android:name=".relay.RelayListActivity"
@@ -356,13 +365,15 @@
<activity android:name=".calls.CallActivity"
android:label=""
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode">
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:exported="true">
</activity>
<activity android:name=".WebxdcActivity"
android:label=""
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode">
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:exported="true">
</activity>
<activity android:name=".WebxdcStoreActivity"
@@ -392,15 +403,6 @@
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
+101 -117
View File
@@ -15,7 +15,7 @@
<li><a href="#what-do-the-ticks-shown-beside-outgoing-messages-mean">What do the ticks shown beside outgoing messages mean?</a></li>
<li><a href="#edit">Correct typos and delete messages after sending</a></li>
<li><a href="#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="#delold">What happens if I turn on “Delete old messages from device”?</a></li>
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
</ul>
</li>
@@ -26,7 +26,6 @@
<li><a href="#kdyź-se-nedopatřením-odstraníš">Kdyź se nedopatřením odstraníš.</a></li>
<li><a href="#nechci-již-přijímat-zprávy-ze-skupiny">Nechci již přijímat zprávy ze skupiny.</a></li>
<li><a href="#cloning-a-group">Cloning a group</a></li>
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
</ul>
</li>
<li><a href="#webxdc">In-chat apps</a>
@@ -55,7 +54,7 @@
</li>
<li><a href="#advanced">Advanced</a>
<ul>
<li><a href="#experiments">Experimental Features</a></li>
<li><a href="#experimental-features">Experimental Features</a></li>
<li><a href="#relays">What are Relays?</a></li>
<li><a href="#can-i-use-a-classic-email-address-with-delta-chat">Can I use a classic email address with Delta Chat?</a></li>
<li><a href="#classic-email">How can I configure a chat profile with a classic email address as relay?</a></li>
@@ -77,7 +76,6 @@
<li><a href="#tls">Are messages marked with the mail icon exposed on the Internet?</a></li>
<li><a href="#message-metadata">How does Delta Chat protect metadata in messages?</a></li>
<li><a href="#device-seizure">How to protect metadata and contacts when a device is seized?</a></li>
<li><a href="#who-sees-my-ip-address">Who sees my IP Address?</a></li>
<li><a href="#sealedsender">Does Delta Chat support “Sealed Sender”?</a></li>
<li><a href="#pfs">Does Delta Chat support Perfect Forward Secrecy?</a></li>
<li><a href="#pqc">Does Delta Chat support Post-Quantum-Cryptography?</a></li>
@@ -187,8 +185,7 @@ If you add each other to <a href="#groups">groups</a>, end-to-end encryption wil
<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>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>Your friends may share your contact with other friends, this appears as a <strong>request</strong>.</p>
<ul>
<li>
@@ -226,10 +223,15 @@ and can tap it to start chatting with the first contact.</p>
</h3>
<p>Yes. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
<ul>
<li>
<p>Yes. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
<p>For performance, images are optimized and sent at a smaller size by default, but you can send it as a “file” to preserve the original.</p>
</li>
<li>
<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>
</li>
</ul>
<h3 id="multiple-accounts">
@@ -260,11 +262,14 @@ or to <strong>Switch Profiles</strong>.</p>
</h3>
<p>Profilový obrázek lze zvolit v nastavení. Když napíšeš svému kontaktu,
nebo přidáš nový vyfocením QR kódu, tyto kontakty automaticky uvidí tvůj profilový obrázek.</p>
<ul>
<li>Z důvodu soukromí nikdo nevidí tvůj profilový obrázek dokud jim nenapíšeš.</li>
<li>
<p>Profilový obrázek lze zvolit v nastavení. Když napíšeš svému kontaktu,
nebo přidáš nový vyfocením QR kódu, tyto kontakty automaticky uvidí tvůj profilový obrázek.</p>
</li>
<li>
<p>Z důvodu soukromí nikdo nevidí tvůj profilový obrázek dokud jim nenapíšeš.</p>
</li>
</ul>
<h3 id="signature">
@@ -299,8 +304,7 @@ they will see it when they view your contact details.</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>
Archived chats remain accessible above the chat list or via search.</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.
@@ -335,7 +339,7 @@ By tapping <img style="vertical-align:middle; width:1.2em; margin:1px" src="../g
you can go back to the original message in the original chat</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>Finally, you can also use “Save Messages” to take <strong>personal notes</strong> - open the chat, type something, add a photo or a voice message etc.</p>
</li>
<li>
<p>As “Saved Message” are synced, they can become very handy for transferring data between devices</p>
@@ -372,18 +376,22 @@ 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>
means that the message was sent successfully to your provider.</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>
mean that at least one recipients device
reported back to having received the message.</p>
</li>
<li>
<p>Recipients may have disabled read-receipts,
so even if you see only one tick, the message may have been read.</p>
</li>
<li>
<p>The other way round, two ticks do not automatically mean
that a human has read or understood the message ;)</p>
</li>
</ul>
<p>In <a href="#groups">groups</a> the second tick means that at least one member has reported back having read the message.</p>
<p>You will only get the second tick if both you and one of the recipients who read the message
has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
<h3 id="edit">
@@ -448,18 +456,19 @@ the (anyway encrypted) messages may take longer to get deleted from their server
<h3 id="delold">
What happens if I turn on “Delete Messages from Device”? <a href="#delold" class="anchor"></a>
What happens if I turn on “Delete old messages from device”? <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>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”;
<ul>
<li>If you want to save storage on your device, you can choose to delete old
messages automatically.</li>
<li>To turn it on, go to “delete old messages from device” in the “Chats &amp; Media”
settings. 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>
older than that.</li>
</ul>
<h3 id="remove-account">
@@ -507,15 +516,9 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<ul>
<li>
<p>Z menu v pravém horním rohu, nebo stiskem příslušného tlačítka na Androidu / iOS vyber <strong>Nový hovor</strong> a pak <strong>Nová skupina</strong>.</p>
</li>
<li>
<p>Na další obrazovce, vyber <strong>členy skupiny</strong> a zadej <strong>Název skupiny</strong>. Také můžeš vybrat  <strong>obrázek skupiny</strong>.</p>
</li>
<li>
<p>Jakmile do skupiny pošleš <strong>první zprávu</strong>, všichni členové budou vyrozuměni o nové skupině a mohou do ní také psát (dokud nepošleš první zprávu členové skupiny o ní nebudou vědět).</p>
</li>
<li>Z menu v pravém horním rohu, nebo stiskem příslušného tlačítka na Androidu / iOS vyber <strong>Nový hovor</strong> a pak <strong>Nová skupina</strong>.</li>
<li>Na další obrazovce, vyber <strong>členy skupiny</strong> a zadej <strong>Název skupiny</strong>. Také můžeš vybrat <strong>obrázek skupiny</strong>.</li>
<li>Jakmile do skupiny pošleš <strong>první zprávu</strong>, všichni členové budou vyrozuměni o nové skupině a mohou do ní také psát (dokud nepošleš první zprávu členové skupiny o ní nebudou vědět).</li>
</ul>
<h3 id="addmembers">
@@ -526,10 +529,11 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
<ul>
<li>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
</li>
<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>
</li>
@@ -557,8 +561,10 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<p>Když nejsi členem skupiny nelze se znovu připojit. Nicméně, není to velká potíž -
požádej běžnou zprávou jiného člena skupiny o znovupřipojení.</p>
<ul>
<li>Když nejsi členem skupiny nelze se znovu připojit. Nicméně, není to velká potíž -
požádej běžnou zprávou jiného člena skupiny o znovupřipojení.</li>
</ul>
<h3 id="nechci-již-přijímat-zprávy-ze-skupiny">
@@ -569,12 +575,15 @@ požádej běžnou zprávou jiného člena skupiny o znovupřipojení.</p>
</h3>
<ul>
<li>Buď se odeber ze seznamu členů a nebo vymaž celý skupinový hovor.
K opětovnému připojení v budoucnu požádej nějakého člena skupiny o znovupřidání.</li>
</ul>
<p>Jiná možnost je “Umlčení” skupiny, což znamená nadále přijímat a také posílat zprávy,
<li>
<p>Buď se odeber ze seznamu členů a nebo vymaž celý skupinový hovor.
K opětovnému připojení v budoucnu požádej nějakého člena skupiny o znovupřidání.</p>
</li>
<li>
<p>Jiná možnost je “Umlčení” skupiny, což znamená nadále přijímat a také posílat zprávy,
ale nebudeš dostávat upozrnění na nově příchozí zprávy.</p>
</li>
</ul>
<h3 id="cloning-a-group">
@@ -600,21 +609,6 @@ or right-click the group in the chat list (Desktop).</p>
<p>The new group is <strong>fully independent</strong> from the original,
which continues to work as before.</p>
<h3 id="how-many-members-can-participate-in-a-single-group">
How many members can participate in a single group? <a href="#how-many-members-can-participate-in-a-single-group" class="anchor"></a>
</h3>
<p>There is no strict technical limit,
but more than 150 is not recommended.</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">Dunbars number</a> for more insights.</p>
<h2 id="webxdc">
@@ -903,7 +897,7 @@ One device is not needed for the other to work.</p>
<p>Double-check both devices are in the <strong>same Wi-Fi or network</strong></p>
</li>
<li>
<p>On <strong>Windows</strong>, go to Control Panel / Network and Internet
<p>On <strong>Windows</strong>, go to <strong>Control Panel / Network and Internet</strong>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
@@ -997,10 +991,10 @@ Všechny softwarové balíčky jsou na <a href="https://get.delta.chat">get.delt
</h2>
<h3 id="experiments">
<h3 id="experimental-features">
Experimental Features <a href="#experiments" class="anchor"></a>
Experimental Features <a href="#experimental-features" class="anchor"></a>
</h3>
@@ -1036,7 +1030,7 @@ you can configure relays at At <strong>Settings → Advanced → Relays</strong>
<li>
<p>You can <strong>add</strong> a relay by scanning its QR code;
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
If you have multiple relays, you will receive messages on all of them.</p>
If you have multiple relays, your will receive messages on all of them.</p>
</li>
<li>
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
@@ -1159,7 +1153,9 @@ weekly statistics will be automatically sent to a bot.</p>
</h3>
<p>Dobrý začátek je <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Standards used in Delta Chat</a>.</p>
<ul>
<li>Dobrý začátek je <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Standards used in Delta Chat</a>.</li>
</ul>
<h2 id="e2ee">
@@ -1398,32 +1394,6 @@ 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>
<h3 id="who-sees-my-ip-address">
Who sees my IP Address? <a href="#who-sees-my-ip-address" class="anchor"></a>
</h3>
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
as well as sometimes your contacts devices if you have a <a href="#experiments">call</a>
or use <a href="#webxdc">apps</a> together.</p>
<p>IP Addresses are needed for connectivity and efficiency.
They are neither persisted nor exposed.
Note that the IP Address
is not like a detailed address you give to a delivery service,
but much more coarse, often defining region or country only.</p>
<p>As this is just how the internet and other messengers work by default,
we do not offer options here or ask upfront questions.</p>
<p>If you see your IP Address as a security or privacy risk,
we recommend to use a VPN, in combination with system lockdown mode.
Hunting down options in all apps on your system will leave gaps.
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
<h3 id="sealedsender">
@@ -1663,38 +1633,52 @@ ordered chronologically:</p>
<ul>
<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>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>
<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>
</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>
<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>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>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>
</li>
<li>
<p>Sometimes we receive one-time donations from private individuals.
+120 -139
View File
@@ -15,7 +15,7 @@
<li><a href="#was-bedeuten-die-häkchen-neben-den-ausgehenden-nachrichten">Was bedeuten die Häkchen neben den ausgehenden Nachrichten?</a></li>
<li><a href="#edit">Schreibfehler korrigieren und Nachrichten nach dem Senden löschen</a></li>
<li><a href="#ephemeralmsgs">Wie funktionieren “Verschwindende Nachrichten”?</a></li>
<li><a href="#delold">Was passiert, wenn ich “Nachrichten vom Gerät löschen” aktiviere?</a></li>
<li><a href="#delold">Was passiert, wenn ich “Alte Nachrichten vom Gerät löschen” aktiviere?</a></li>
<li><a href="#remove-account">Wie kann ich mein Chat-Profil löschen?</a></li>
</ul>
</li>
@@ -26,7 +26,6 @@
<li><a href="#ich-habe-mich-selbst-versehentlich-gelöscht">Ich habe mich selbst versehentlich gelöscht.</a></li>
<li><a href="#ich-möchte-keine-nachrichten-einer-gruppe-mehr-empfangen">Ich möchte keine Nachrichten einer Gruppe mehr empfangen.</a></li>
<li><a href="#eine-gruppe-klonen">Eine Gruppe klonen</a></li>
<li><a href="#wie-viele-mitglieder-können-in-einer-einzelnen-gruppe-sein">Wie viele Mitglieder können in einer einzelnen Gruppe sein?</a></li>
</ul>
</li>
<li><a href="#webxdc">In-Chat-Apps</a>
@@ -55,7 +54,7 @@
</li>
<li><a href="#erweitert">Erweitert</a>
<ul>
<li><a href="#experiments">Experimentelle Features</a></li>
<li><a href="#experimentelle-features">Experimentelle Features</a></li>
<li><a href="#relays">Was sind Relays?</a></li>
<li><a href="#kann-ich-eine-klassische-e-mail-adresse-mit-delta-chat-verwenden">Kann ich eine klassische E-Mail-Adresse mit Delta Chat verwenden?</a></li>
<li><a href="#classic-email">Wie kann ich ein Chat-Profil mit einer klassischen E-Mail-Adresse als Relay konfigurieren?</a></li>
@@ -77,7 +76,6 @@
<li><a href="#tls">Sind mit dem Mail-Symbol markierte Nachrichten im Internet sichtbar?</a></li>
<li><a href="#message-metadata">Wie schützt Delta Chat Metadaten in Nachrichten?</a></li>
<li><a href="#device-seizure">Wie schützt man Metadaten und Kontakte, wenn ein Gerät beschlagnahmt wird?</a></li>
<li><a href="#wer-sieht-meine-ip-adresse">Wer sieht meine IP-Adresse?</a></li>
<li><a href="#sealedsender">Unterstützt Delta Chat „Sealed Sender“?</a></li>
<li><a href="#pfs">Unterstützt Delta Chat “Perfect Forward Secrecy”?</a></li>
<li><a href="#pqc">Unterstützt Delta Chat Post-Quantum-Verschlüsselung?</a></li>
@@ -179,7 +177,7 @@ wird eine Ende-zu-Ende-Verschlüsselung zwischen allen Mitgliedern eingerichtet.
<p>Da Delta Chat ein privater Messenger ist, können dir zunächst nur Freunde und Familienmitglieder, denen du deinen <a href="#howtoe2ee">QR-Code oder Einladungslink</a> schickst, schreiben.</p>
<p>Deine Freunde können deine Kontaktdaten dann mit anderen Freunden teilen. Dies wird als <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Anfrage</b> angezeigt.</p>
<p>Deine Freunde können deine Kontaktdaten dann mit anderen Freunden teilen. Dies wird als <strong>Anfrage</strong> angezeigt.</p>
<ul>
<li>
@@ -217,10 +215,15 @@ kann darauf tippen, um mit dem ersten Kontakt zu chatten.</p>
</h3>
<p>Ja. Bilder, Videos, Dateien, Sprachnachrichten und mehr können über die <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Anhang-</strong>
bzw. <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Sprachnachricht</strong>-Buttons hinzugefügt werden</p>
<p>Um die Leistung zu verbessern, werden die Bilder standardmäßig optimiert und in einer kleineren Größe gesendet, aber du kannst sie auch als “Datei” senden, um das Original zu erhalten.</p>
<ul>
<li>
<p>Ja. Bilder, Videos, Dateien, Sprachnachrichten und mehr können über die <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Anhang-</strong>
bzw. <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Sprachnachricht</strong>-Buttons hinzugefügt werden</p>
</li>
<li>
<p>Um die Leistung zu verbessern, werden die Bilder standardmäßig optimiert und in einer kleineren Größe gesendet, aber du kannst sie auch als “Datei” senden, um das Original zu erhalten.</p>
</li>
</ul>
<h3 id="multiple-accounts">
@@ -251,9 +254,14 @@ oder <strong>Profile zu wechseln</strong>.</p>
</h3>
<p>Du kannst ein Profilbild in den Einstellungen hinzufügen. Wenn du deinen Kontakten eine Nachricht sendest oder sie über einen QR-Code hinzufügst, sehen diese automatisch dein Profilbild.</p>
<p>Aus Datenschutzgründen sieht niemand dein Profilbild, dem du nicht zuvor eine Nachricht gesendet hast.</p>
<ul>
<li>
<p>Du kannst ein Profilbild in den Einstellungen hinzufügen. Wenn du deinen Kontakten eine Nachricht sendest oder sie über einen QR-Code hinzufügst, sehen diese automatisch dein Profilbild.</p>
</li>
<li>
<p>Aus Datenschutzgründen sieht niemand dein Profilbild, dem du nicht zuvor eine Nachricht gesendet hast.</p>
</li>
</ul>
<h3 id="signature">
@@ -285,7 +293,7 @@ Sobald du eine Nachricht an einen Kontakt sendest, kann dieser deine Signatur in
<p><strong>Stummgeschaltete Chats</strong> erhalten keine Benachrichtigungen, bleiben ansonsten aber an ihrem Platz. Du kannst auch stummgeschaltete Chats anheften.</p>
</li>
<li>
<p><strong>Archiviere Chats</strong>, wenn du diese nicht mehr in deiner Chatliste sehen möchtest; sie bleiben oberhalb der Chatliste oder über die Suche zugänglich und werden als <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Archiviert</b> gekennzeichnet</p>
<p><strong>Archiviere Chats</strong>, wenn du diese nicht mehr in deiner Chatliste sehen möchtest. Archivierte Chats bleiben oberhalb der Chatliste oder über die Suche zugänglich.</p>
</li>
<li>
<p>Wenn ein archivierter Chat eine neue Nachricht erhält, wird er, sofern er nicht stummgeschaltet ist, <strong>wieder in die normale Chatliste verschoben</strong>. <strong>Stummgeschaltete Chats bleiben archiviert</strong>, bis du sie manuell aus dem Archiv entfernst.</p>
@@ -351,16 +359,18 @@ sei es durch den <a href="#edit">Absender</a>, durch <a href="#delold">Automatis
<ul>
<li>
<p><strong>Ein Häkchen</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick1.png" alt="" /> bedeutet, dass die Nachricht erfolgreich versandt wurde und das <a href="#relays">Relay</a> erreicht hat.</p>
<p><strong>Ein Häkchen</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick1.png" alt="" /> bedeutet, dass die Nachricht erfolgreich versandt wurde.</p>
</li>
<li>
<p><strong>Zwei Häkchen</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick2.png" alt="" /> bedeuten, dass der Empfänger die Nachricht gelesen hat.</p>
<p><strong>Zwei Häkchen</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick2.png" alt="" /> bedeuten, dass mindestens ein Gerät des Empfängers zurückgemeldet hat, die Nachricht empfangen zu haben.</p>
</li>
<li>
<p>Lesebestätigungen können deaktiviert werden. D.h. auch wenn du nur ein Häkchen siehst, kann die Nachricht gelesen worden sein.</p>
</li>
<li>
<p>Umgekehrt bedeuten zwei Häkchen nicht automatisch, dass ein Mensch die Nachricht gelesen oder verstanden hat ;)</p>
</li>
</ul>
<p>In <a href="#groups">Gruppen</a> bedeutet das zweite Häkchen, dass die Nachricht von mindestens einem Mitglied gelesen wurde.</p>
<p>Du erhälst nur dann das zweite Häkchen, wenn sowohl du als auch einer der Empfänger, die die Nachricht gelesen haben, <strong>Einstellungen → Chats → Lesebestätigungen</strong> aktiviert haben.</p>
<h3 id="edit">
@@ -420,14 +430,15 @@ oder auf andere Weise Nachrichten vor dem Löschen speichern, kopieren oder weit
<h3 id="delold">
Was passiert, wenn ich “Nachrichten vom Gerät löschen” aktiviere? <a href="#delold" class="anchor"></a>
Was passiert, wenn ich “Alte Nachrichten vom Gerät löschen” aktiviere? <a href="#delold" class="anchor"></a>
</h3>
<p>Wenn du Speicherplatz auf deinem Gerät sparen möchtest, kannst du alte Nachrichten automatisch löschen lassen.</p>
<p>Hierzu, öffne <strong>Einstellungen → Chats → Nachrichten vom Gerät löschen</strong>. Du kannst einen Zeitraum zwischen “1 Stunde” und “1 Jahr” festlegen; auf diese Weise werden alleNachrichten von deinem Gerät gelöscht, sobald sie älter als angegeben sind.</p>
<ul>
<li>Wenn du Speicherplatz auf deinem Gerät sparen möchtest, kannst du alte Nachrichten automatisch löschen lassen.</li>
<li>Hierzu, öffne die “Chats und Medien”-Einstellungen und dort “Alte Nachrichten vom Gerät löschen. Du kannst einen Zeitraum zwischen “1 Stunde” und “1 Jahr” festlegen; auf diese Weise werden <em>alle</em> Nachrichten von deinem Gerät gelöscht, sobald sie älter als angegeben sind.</li>
</ul>
<h3 id="remove-account">
@@ -474,15 +485,9 @@ und seine <a href="#edit">eigenen Nachrichten von Geräten der Mitglieder lösch
</h3>
<ul>
<li>
<p>Wähle <strong>Neuer Chat</strong> und dann <strong>Neue Gruppe</strong> aus dem Menü oben rechts oder über das entsprechende Symbol unter Android/iOS.</p>
</li>
<li>
<p>Wähle auf dem folgenden Bildschirm die <strong>Gruppenmitglieder</strong> aus und klicke auf das Häkchen in der oberen rechten Ecke. Danach kannst du einen <strong>Gruppennamen</strong> und auch einen <strong>Gruppenbild</strong>  festlegen.</p>
</li>
<li>
<p>Sobald du die <strong>erste Nachricht</strong> in die Gruppe schreibst, werden alle Mitglieder über die neue Gruppe informiert und können in der Gruppe antworten (solange du keine Nachricht in die Gruppe schreibst, ist die Gruppe für die Gruppenmitglieder nicht sichtbar).</p>
</li>
<li>Wähle <strong>Neuer Chat</strong> und dann <strong>Neue Gruppe</strong> aus dem Menü oben rechts oder über das entsprechende Symbol unter Android/iOS.</li>
<li>Wähle auf dem folgenden Bildschirm die <strong>Gruppenmitglieder</strong> aus und klicke auf das Häkchen in der oberen rechten Ecke. Danach kannst du einen <strong>Gruppennamen</strong> und auch einen <strong>Gruppenbild</strong> festlegen.</li>
<li>Sobald du die <strong>erste Nachricht</strong> in die Gruppe schreibst, werden alle Mitglieder über die neue Gruppe informiert und können in der Gruppe antworten (solange du keine Nachricht in die Gruppe schreibst, ist die Gruppe für die Gruppenmitglieder nicht sichtbar).</li>
</ul>
<h3 id="addmembers">
@@ -493,9 +498,10 @@ und seine <a href="#edit">eigenen Nachrichten von Geräten der Mitglieder lösch
</h3>
<p>Alle Gruppenmitglieder haben <strong>dieselben Rechte</strong>. Jeder kann daher jeden löschen oder weitere Mitglieder hinzufügen.</p>
<ul>
<li>
<p>Alle Gruppenmitglieder haben <strong>dieselben Rechte</strong>. Jeder kann daher jeden löschen oder weitere Mitglieder hinzufügen.</p>
</li>
<li>
<p>Um <strong>Mitglieder hinzuzufügen oder zu entfernen</strong>, tippe im Chat auf den Gruppennamen und wähle das Mitglied aus, das du hinzufügen oder entfernen möchtest.</p>
</li>
@@ -517,8 +523,10 @@ und seine <a href="#edit">eigenen Nachrichten von Geräten der Mitglieder lösch
</h3>
<p>Da du kein Gruppenmitglied mehr bist, kannst du sich selbst nicht mehr hinzufügen.
Kein Problem, bitte einfach ein anderes Gruppenmitglied in einem normalen Chat, dich hinzuzufügen.</p>
<ul>
<li>Da du kein Gruppenmitglied mehr bist, kannst du sich selbst nicht mehr hinzufügen.
Kein Problem, bitte einfach ein anderes Gruppenmitglied in einem normalen Chat, dich hinzuzufügen.</li>
</ul>
<h3 id="ich-möchte-keine-nachrichten-einer-gruppe-mehr-empfangen">
@@ -529,11 +537,14 @@ Kein Problem, bitte einfach ein anderes Gruppenmitglied in einem normalen Chat,
</h3>
<ul>
<li>Lösche dich entweder aus der Mitgliederliste oder lösche den gesamten Chat.
Wenn du der Gruppe später erneut beitreten möchtest, bitten ein anderes Gruppenmitglied, dich hinzuzufügen.</li>
<li>
<p>Lösche dich entweder aus der Mitgliederliste oder lösche den gesamten Chat.
Wenn du der Gruppe später erneut beitreten möchtest, bitten ein anderes Gruppenmitglied, dich hinzuzufügen.</p>
</li>
<li>
<p>Alternativ kannst du eine Gruppe auch “stummschalten” - dies bedeutet, dass du weiterhin alle Nachrichten erhälst und neue schreiben kannst, aber nicht mehr über neue Nachrichten informiert wirst.</p>
</li>
</ul>
<p>Alternativ kannst du eine Gruppe auch “stummschalten” - dies bedeutet, dass du weiterhin alle Nachrichten erhälst und neue schreiben kannst, aber nicht mehr über neue Nachrichten informiert wirst.</p>
<h3 id="eine-gruppe-klonen">
@@ -559,19 +570,6 @@ oder klicken mit der rechten Maustaste auf die Gruppe in der Chat-Liste (Desktop
<p>Die neue Gruppe ist <strong>völlig unabhängig</strong> von der ursprünglichen,
die weiterhin wie bisher funktioniert.</p>
<h3 id="wie-viele-mitglieder-können-in-einer-einzelnen-gruppe-sein">
Wie viele Mitglieder können in einer einzelnen Gruppe sein? <a href="#wie-viele-mitglieder-können-in-einer-einzelnen-gruppe-sein" class="anchor"></a>
</h3>
<p>Es gibt keine technische Begrenzung,
aber mehr als 150 sind nicht empfohlen.</p>
<p>Wenn Gruppen größer werden, können sie sozial instabil werden und benötigen möglicherweise eine Hierarchie - und Delta Chat ist ein privater Messenger für Chats mit <a href="#groups">gleichen Rechten</a>. Vgl. <a href="https://de.wikipedia.org/wiki/Dunbar-Zahl">Dunbar-Zahl</a>.</p>
<h2 id="webxdc">
@@ -843,7 +841,7 @@ Einschließlich dem Chatmail-Server, <a href="https://delta.chat/chatmail#selfho
<p>Vergewissere dich, dass beide Geräte mit dem <strong>gleichen Wi-Fi, WLAN oder Netzwerk</strong> verbunden sind.</p>
</li>
<li>
<p>Unter <strong>Windows</strong>, Systemsteuerung / Netzwerk und Internet öffnen
<p>Unter <strong>Windows</strong>, <strong>Systemsteuerung / Netzwerk und Internet</strong> öffnen
und sicherstellen, dass <strong>Privates Netzwerk</strong> als “Netzwerkprofiltyp” ausgewählt ist.
(nach der Übertragung kann wieder der ursprüngliche Wert verwendet werden)</p>
</li>
@@ -922,10 +920,10 @@ Wenn du iOS verwendest und auf Schwierigkeiten stößt, hilft dir vielleicht <a
</h2>
<h3 id="experiments">
<h3 id="experimentelle-features">
Experimentelle Features <a href="#experiments" class="anchor"></a>
Experimentelle Features <a href="#experimentelle-features" class="anchor"></a>
</h3>
@@ -983,23 +981,24 @@ Im Zweifelsfall entferne das Relay später.</p>
</h3>
<p>Ja, aber nur, wenn die E-Mail-Adresse ausschließlich von <a href="https://chatmail.at/clients">Chatmail-Clients</a> verwendet wird.</p>
<p>Yes, but only if the email address is used exclusively by <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
<p>Die gemeinsame Nutzung einer E-Mail-Adresse mit Nicht-Chatmail-Apps oder webbasierten Mailprogrammen wird aus folgenden Gründen nicht unterstützt:</p>
<p>It is not supported to share usage of an email address with non-chatmail apps or web-based mailers,
for the following reasons:</p>
<ul>
<li>
<p>Nicht-Chatmail-Apps bieten ihren Nutzern größtenteils keine automatische End-to-End-Verschlüsselung,
während Chatmail-Apps und Relays durchgängig End-to-End-Verschlüsselung und Sicherheitsstandards durchsetzen.</p>
<p>Non-chatmail apps are largely not accomplishing automatic end-to-end email encryption for their users,
while chatmail apps and relays pervasively enforce end-to-end encryption and security standards.</p>
</li>
<li>
<p>Nicht-Chatmail-Anwendungen nutzen E-Mail-Server als langfristiges Nachrichtenarchiv,
während Chatmail-Clients E-Mail-Server für die kurzlebige Weiterleitung von Nachrichten verwenden.</p>
<p>Non-chatmail apps use email servers as a long-term message archive
while chatmail clients use email servers for ephemeral instant message relay.</p>
</li>
<li>
<p>Die Unterstützung der gesamten Bandbreite klassischer E-Mail-Konfigurationen
würde einen erheblichen Entwicklungs- und Wartungsaufwand erfordern
und Chatmail-basiertes Messaging weniger robust, zuverlässig und schnell machen.</p>
<p>Supporting the full variety of classic email setups
would require considerable development and maintenance efforts,
and complicate making chatmail-based messaging more resilient, reliable and fast.</p>
</li>
</ul>
@@ -1011,15 +1010,17 @@ und Chatmail-basiertes Messaging weniger robust, zuverlässig und schnell machen
</h3>
<p>Zunächst einmal, <strong>verwenden bitte nicht dieselbe klassische E-Mail-Adresse auch in anderen klassischen E-Mail-Anwendungen</strong>,
es sei denn, du bist sind bereit, dich mit verschlüsselten Nachrichten im Posteingang,
doppelten Benachrichtigungen, versehentlich gelöschten E-Mails oder ähnlichen Ärgernissen auseinanderzusetzen.</p>
<p>First off, <strong>please do not use the same classic email address also from non-chatmail classic email apps</strong>
unless you are prepared to deal with encrypted messages in the inbox,
double notifications, accidentally deleted emails or similar annoyances.</p>
<p>Sie können eine E-Mail-Adresse unter <strong>Neues Profil → Anderen Server verwenden → Klassische E-Mail als Relay</strong> konfigurieren.
Beachten Sie, dass klassische E-Mail-Anbieter in der Regel keine <a href="#instant-delivery">Push-Benachrichtigungen</a> unterstützen
und andere Einschränkungen haben, siehe <a href="https://providers.delta.chat">Provider-Overview</a>.
Chatmail verwendet den Standard-INBOX für die Weiterleitung; stellen Sie sicher, dass dies auch bei der Einrichtung Ihres Anbieters der Fall ist.
Ein Chat-Profil mit klassischer E-Mail-Adresse, ermöglicht das Senden und Empfangen unverschlüsselter Nachrichten; diese sind mit dem E-Mail-Symbol <img style="vertical-align:middle; width:1.2em; margin:1px" src="../email-icon.png" alt="email" /> gekennzeichnet.</p>
<p>You can configure a email address for chatting at <strong>New ProfileUse Other Server → Use Classic Mail as Relay</strong>.
Note that classic email providers will generally not support <a href="#instant-delivery">Push Notifications</a>
and have other limitations, see <a href="https://providers.delta.chat">Provider Overview</a>.
Chatmail uses the default INBOX for relay; ensure the provider setup does too.
A chat profile using a classic email address allows to to send and receive unencrypted messages.
These messages, and the chats they appear in, are marked with an email icon
<img style="vertical-align:middle; width:1.2em; margin:1px" src="../email-icon.png" alt="email" />.</p>
<h3 id="ich-möchte-meinen-eigenen-server-für-delta-chat-verwalten-gibt-es-empfehlungen">
@@ -1029,13 +1030,13 @@ Ein Chat-Profil mit klassischer E-Mail-Adresse, ermöglicht das Senden und Empfa
</h3>
<p>Jede gut funktionierende E-Mail-Server-Konfiguration ist geeignet,
es sei denn, die Geräte Ihrer Benutzer erfordern Google/Apple <a href="#instant-delivery">Push-Benachrichtigungen</a>, um ordnungsgemäß zu funktionieren.</p>
<p>Any well behaving email server setup will do fine
except if your users devices require Google/Apple <a href="#instant-delivery">Push Notifications</a> to work properly.</p>
<p>Wir empfehlen generell, <a href="https://chatmail.at/doc/relay/getting_started.html">ein Chatmail-Relay einzurichten</a>.
<a href="https://chatmail.at">Chatmail</a> ist ein Community-basiertes Projekt, das sowohl die Einrichtung von Relays
als auch <a href="https://github.com/chatmail/core">Entwicklungen in Rust</a>
für die <a href="https://chatmail.at/clients">Chatmail-Clients</a> umfasst, von denen Delta Chat der bekannteste ist.</p>
<p>We generally recommend to <a href="https://chatmail.at/doc/relay/getting_started.html">set up a chatmail relay</a>.
<a href="https://chatmail.at">Chatmail</a> is a community-driven project that encompasses both the setup of relays
and <a href="https://github.com/chatmail/core">core Rust developments</a>
that power <a href="https://chatmail.at/clients">chatmail clients</a> of which Delta Chat is the most well known.</p>
<h3 id="statssending">
@@ -1079,7 +1080,9 @@ weekly statistics will be automatically sent to a bot.</p>
</h3>
<p>Siehe hierzu <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">in Delta Chat genutzte Standards</a>.</p>
<ul>
<li>Siehe hierzu <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">in Delta Chat genutzte Standards</a>.</li>
</ul>
<h2 id="e2ee">
@@ -1146,15 +1149,16 @@ Seit der Veröffentlichung von Delta Chat Version 2 (Juli 2025) gibt es keine Sc
</h3>
<p>Ein Kontaktprofile kann ein grünes Häkchen
<p>A contact profile might show a green checkmark
<img style="vertical-align:middle; width:1.5em; margin:1px" src="../green-checkmark.png" alt="green checkmark" />
und “Eingeführt von” enthalten.
Jeder so markierte Kontakt hat entweder einen direkten <a href="#howtoe2ee">QR-Scan</a> mit Ihnen durchgeführt
oder wurde von einem anderen Kontakt mit grünem Häkchen eingeführt.
Das Einführen geschieht automatisch, wenn Sie Mitglieder zu Gruppen hinzufügen.
Wer einen Kontakt mit grünem Häkchen zu einer Gruppe hinzufügt, wird zum Einführenden.
In einem Kontaktprofil können Sie wiederholt auf den Text “Eingeführt von” tippen
bis Sie zu demjenigen gelangen, mit dem Sie einen direkten <a href="#howtoe2ee">QR-Scan</a> gemacht haben.</p>
and an “Introduced by” line.
Every green-checkmarked contact either did a direct <a href="#howtoe2ee">QR-scan</a> with you
or was introduced by a another green-checkmarked contact.
Introductions happen automatically when adding members to groups.
Whoever adds a green-checkmarked contact to a group with only green-checkmarked members
becomes an introducer.
In a contact profile you can tap on the “Introduced by …” text repeatedly
until you get to the one with whom you directly did a <a href="#howtoe2ee">QR-scan</a>.</p>
<p>Für eine ausführlichere Diskussion der “Garantierten Ende-zu-Ende-Verschlüsselung”,
siehe <a href="https://securejoin.delta.chat/en/latest/new.html">Secure-Join-Protokolle</a>
@@ -1309,32 +1313,6 @@ 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>
<h3 id="wer-sieht-meine-ip-adresse">
Wer sieht meine IP-Adresse? <a href="#wer-sieht-meine-ip-adresse" class="anchor"></a>
</h3>
<p>Das verwendete <a href="#relays">Relay</a> muss Ihre IP-Adresse kennen,
sowie manchmal auch die Geräte Ihrer Kontakte, wenn Sie einen <a href="#experiments">Anruf</a> tätigen
oder gemeinsam <a href="#webxdc">Apps</a> verwenden.</p>
<p>IP-Adressen sind für Verbindungen und für Effizienz erforderlich.
Sie werden weder gespeichert noch offengelegt.
Beachten Sie, dass die IP-Adresse
nicht mit einer Adresse, die Sie einem Lieferdienst geben, vergleichbar ist -
sondern viel gröber ist und oft nur die Region oder das Land angibt.</p>
<p>Da dies die Standardfunktion des Internets und anderer Messenger ist,
bieten wir hier keine Optionen an und stellen auch keine Fragen im Voraus.</p>
<p>Wenn Sie Ihre IP-Adresse als Sicherheits- oder Datenschutzrisiko betrachten,
empfehlen wir Ihnen, ein VPN in Kombination mit dem System-Lockdown-Modus zu verwenden.
Alle einzelnen Apps auf Ihrem System nach IP-Optionen abzusuchen wird nicht zufriedenstellen sein;
beispielsweise legt das Antippen eines Links IP-Adressen gegenüber unbekannten Parteien offen und stellt hier das weitaus größere Risiko dar.</p>
<h3 id="sealedsender">
@@ -1343,7 +1321,7 @@ beispielsweise legt das Antippen eines Links IP-Adressen gegenüber unbekannten
</h3>
<p>Nein, noch nicht.</p>
<p>Nein, noch nichts.</p>
<p>Der Signal-Messenger führte 2018 <a href="https://signal.org/blog/sealed-sender/">“Sealed Sender”</a> ein
um seine Serverinfrastruktur darüber im Unklaren zu lassen, wer eine Nachricht an eine Gruppe von Empfängern sendet.
@@ -1364,7 +1342,7 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>Nein, noch nicht.</p>
<p>Nein, noch nichts.</p>
<p>Delta Chat today doesnt support Perfect Forward Secrecy (PFS).
This means that if your private decryption key is leaked,
@@ -1390,7 +1368,7 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
</h3>
<p>Nein, noch nicht.</p>
<p>Nein, noch nichts.</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.
@@ -1487,10 +1465,10 @@ Es wurden keine kritischen Probleme gefunden, aber zwei Probleme mit hohem Schwe
</h3>
<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>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>Siehe <a href="https://delta.chat/de/gdpr#24-berechtigungen-der-app">Datenschutzhinweise</a> für eine detaillierte Übersicht.</p>
<p>See <a href="https://delta.chat/en/gdpr#24-app-permissions">Privacy Policy</a> for a detailed overview.</p>
<h3 id="wo-können-meine-freunde-delta-chat-finden">
@@ -1535,34 +1513,37 @@ Wir nutzen vielmehr öffentliche Finanzierungsquellen, die bisher aus der EU und
<ul>
<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>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>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>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>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>
<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>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>
<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,
zusammen mit Kooperationspartnern, die an
<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> und
<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>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.
+102 -116
View File
@@ -15,7 +15,7 @@
<li><a href="#what-do-the-ticks-shown-beside-outgoing-messages-mean">What do the ticks shown beside outgoing messages mean?</a></li>
<li><a href="#edit">Correct typos and delete messages after sending</a></li>
<li><a href="#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="#delold">What happens if I turn on “Delete old messages from device”?</a></li>
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
</ul>
</li>
@@ -26,7 +26,6 @@
<li><a href="#i-have-deleted-myself-by-accident">I have deleted myself by accident.</a></li>
<li><a href="#i-do-not-want-to-receive-the-messages-of-a-group-any-longer">I do not want to receive the messages of a group any longer.</a></li>
<li><a href="#cloning-a-group">Cloning a group</a></li>
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
</ul>
</li>
<li><a href="#webxdc">In-chat apps</a>
@@ -55,7 +54,7 @@
</li>
<li><a href="#advanced">Advanced</a>
<ul>
<li><a href="#experiments">Experimental Features</a></li>
<li><a href="#experimental-features">Experimental Features</a></li>
<li><a href="#relays">What are Relays?</a></li>
<li><a href="#can-i-use-a-classic-email-address-with-delta-chat">Can I use a classic email address with Delta Chat?</a></li>
<li><a href="#classic-email">How can I configure a chat profile with a classic email address as relay?</a></li>
@@ -77,7 +76,6 @@
<li><a href="#tls">Are messages marked with the mail icon exposed on the Internet?</a></li>
<li><a href="#message-metadata">How does Delta Chat protect metadata in messages?</a></li>
<li><a href="#device-seizure">How to protect metadata and contacts when a device is seized?</a></li>
<li><a href="#who-sees-my-ip-address">Who sees my IP Address?</a></li>
<li><a href="#sealedsender">Does Delta Chat support “Sealed Sender”?</a></li>
<li><a href="#pfs">Does Delta Chat support Perfect Forward Secrecy?</a></li>
<li><a href="#pqc">Does Delta Chat support Post-Quantum-Cryptography?</a></li>
@@ -187,8 +185,7 @@ If you add each other to <a href="#groups">groups</a>, end-to-end encryption wil
<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>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>Your friends may share your contact with other friends, this appears as a <strong>request</strong>.</p>
<ul>
<li>
@@ -226,10 +223,15 @@ and can tap it to start chatting with the first contact.</p>
</h3>
<p>Yes. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
<ul>
<li>
<p>Yes. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
<p>For performance, images are optimized and sent at a smaller size by default, but you can send it as a “file” to preserve the original.</p>
</li>
<li>
<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>
</li>
</ul>
<h3 id="multiple-accounts">
@@ -260,11 +262,16 @@ or to <strong>Switch Profiles</strong>.</p>
</h3>
<p>You can add a profile picture in your settings. If you write to your contacts
<ul>
<li>
<p>You can add a profile picture in your settings. If you write to your contacts
or add them via QR code, they automatically see it as your profile picture.</p>
<p>For privacy reasons, no one sees your profile picture until you write a
</li>
<li>
<p>For privacy reasons, no one sees your profile picture until you write a
message to them.</p>
</li>
</ul>
<h3 id="signature">
@@ -298,8 +305,7 @@ they will see it when they view your contact details.</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>
Archived chats remain accessible above the chat list or via search.</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.
@@ -334,7 +340,7 @@ By tapping <img style="vertical-align:middle; width:1.2em; margin:1px" src="../g
you can go back to the original message in the original chat</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>Finally, you can also use “Save Messages” to take <strong>personal notes</strong> - open the chat, type something, add a photo or a voice message etc.</p>
</li>
<li>
<p>As “Saved Message” are synced, they can become very handy for transferring data between devices</p>
@@ -371,18 +377,22 @@ 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>
means that the message was sent successfully to your provider.</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>
mean that at least one recipients device
reported back to having received the message.</p>
</li>
<li>
<p>Recipients may have disabled read-receipts,
so even if you see only one tick, the message may have been read.</p>
</li>
<li>
<p>The other way round, two ticks do not automatically mean
that a human has read or understood the message ;)</p>
</li>
</ul>
<p>In <a href="#groups">groups</a> the second tick means that at least one member has reported back having read the message.</p>
<p>You will only get the second tick if both you and one of the recipients who read the message
has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
<h3 id="edit">
@@ -447,18 +457,19 @@ the (anyway encrypted) messages may take longer to get deleted from their server
<h3 id="delold">
What happens if I turn on “Delete Messages from Device”? <a href="#delold" class="anchor"></a>
What happens if I turn on “Delete old messages from device”? <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>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”;
<ul>
<li>If you want to save storage on your device, you can choose to delete old
messages automatically.</li>
<li>To turn it on, go to “delete old messages from device” in the “Chats &amp; Media”
settings. 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>
older than that.</li>
</ul>
<h3 id="remove-account">
@@ -506,15 +517,9 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<ul>
<li>
<p>Select <strong>New chat</strong> and then <strong>New group</strong> from the menu in the upper right corner or hit the corresponding button on Android/iOS.</p>
</li>
<li>
<p>On the following screen, select the <strong>group members</strong> and define a <strong>group name</strong>. You can also select a <strong>group avatar</strong>.</p>
</li>
<li>
<p>As soon as you write the <strong>first message</strong> in the group, all members are informed about the new group and can answer in the group (as long as you do not write a message in the group the group is invisible to the members).</p>
</li>
<li>Select <strong>New chat</strong> and then <strong>New group</strong> from the menu in the upper right corner or hit the corresponding button on Android/iOS.</li>
<li>On the following screen, select the <strong>group members</strong> and define a <strong>group name</strong>. You can also select a <strong>group avatar</strong>.</li>
<li>As soon as you write the <strong>first message</strong> in the group, all members are informed about the new group and can answer in the group (as long as you do not write a message in the group the group is invisible to the members).</li>
</ul>
<h3 id="addmembers">
@@ -525,10 +530,11 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
<ul>
<li>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
</li>
<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>
</li>
@@ -556,8 +562,10 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<p>As youre no longer a group member, you cannot add yourself again.
However, no problem, just ask any other group member in a normal chat to re-add you.</p>
<ul>
<li>As youre no longer a group member, you cannot add yourself again.
However, no problem, just ask any other group member in a normal chat to re-add you.</li>
</ul>
<h3 id="i-do-not-want-to-receive-the-messages-of-a-group-any-longer">
@@ -568,12 +576,15 @@ However, no problem, just ask any other group member in a normal chat to re-add
</h3>
<ul>
<li>Either delete yourself from the member list or delete the whole chat.
If you want to join the group again later on, ask another group member to add you again.</li>
</ul>
<p>As an alternative, you can also “Mute” a group - doing so means you get all messages and
<li>
<p>Either delete yourself from the member list or delete the whole chat.
If you want to join the group again later on, ask another group member to add you again.</p>
</li>
<li>
<p>As an alternative, you can also “Mute” a group - doing so means you get all messages and
can still write, but are no longer notified of any new messages.</p>
</li>
</ul>
<h3 id="cloning-a-group">
@@ -599,21 +610,6 @@ or right-click the group in the chat list (Desktop).</p>
<p>The new group is <strong>fully independent</strong> from the original,
which continues to work as before.</p>
<h3 id="how-many-members-can-participate-in-a-single-group">
How many members can participate in a single group? <a href="#how-many-members-can-participate-in-a-single-group" class="anchor"></a>
</h3>
<p>There is no strict technical limit,
but more than 150 is not recommended.</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">Dunbars number</a> for more insights.</p>
<h2 id="webxdc">
@@ -902,7 +898,7 @@ One device is not needed for the other to work.</p>
<p>Double-check both devices are in the <strong>same Wi-Fi or network</strong></p>
</li>
<li>
<p>On <strong>Windows</strong>, go to Control Panel / Network and Internet
<p>On <strong>Windows</strong>, go to <strong>Control Panel / Network and Internet</strong>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
@@ -997,10 +993,10 @@ or the AppImage for Linux. You can find them on
</h2>
<h3 id="experiments">
<h3 id="experimental-features">
Experimental Features <a href="#experiments" class="anchor"></a>
Experimental Features <a href="#experimental-features" class="anchor"></a>
</h3>
@@ -1036,7 +1032,7 @@ you can configure relays at At <strong>Settings → Advanced → Relays</strong>
<li>
<p>You can <strong>add</strong> a relay by scanning its QR code;
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
If you have multiple relays, you will receive messages on all of them.</p>
If you have multiple relays, your will receive messages on all of them.</p>
</li>
<li>
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
@@ -1159,7 +1155,9 @@ weekly statistics will be automatically sent to a bot.</p>
</h3>
<p>See <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Standards used in Delta Chat</a>.</p>
<ul>
<li>See <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Standards used in Delta Chat</a>.</li>
</ul>
<h2 id="e2ee">
@@ -1398,32 +1396,6 @@ 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>
<h3 id="who-sees-my-ip-address">
Who sees my IP Address? <a href="#who-sees-my-ip-address" class="anchor"></a>
</h3>
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
as well as sometimes your contacts devices if you have a <a href="#experiments">call</a>
or use <a href="#webxdc">apps</a> together.</p>
<p>IP Addresses are needed for connectivity and efficiency.
They are neither persisted nor exposed.
Note that the IP Address
is not like a detailed address you give to a delivery service,
but much more coarse, often defining region or country only.</p>
<p>As this is just how the internet and other messengers work by default,
we do not offer options here or ask upfront questions.</p>
<p>If you see your IP Address as a security or privacy risk,
we recommend to use a VPN, in combination with system lockdown mode.
Hunting down options in all apps on your system will leave gaps.
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
<h3 id="sealedsender">
@@ -1663,38 +1635,52 @@ ordered chronologically:</p>
<ul>
<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>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>
<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>
</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>
<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>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>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>
</li>
<li>
<p>Sometimes we receive one-time donations from private individuals.
+100 -112
View File
@@ -26,7 +26,6 @@
<li><a href="#me-he-eliminado-por-accidente">Me he eliminado por accidente.</a></li>
<li><a href="#no-quiero-recibir-más-los-mensajes-de-un-grupo">No quiero recibir más los mensajes de un 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>
</ul>
</li>
<li><a href="#webxdc">In-chat apps</a>
@@ -55,7 +54,7 @@
</li>
<li><a href="#advanced">Advanced</a>
<ul>
<li><a href="#experiments">Experimental Features</a></li>
<li><a href="#experimental-features">Experimental Features</a></li>
<li><a href="#relays">What are Relays?</a></li>
<li><a href="#can-i-use-a-classic-email-address-with-delta-chat">Can I use a classic email address with Delta Chat?</a></li>
<li><a href="#classic-email">How can I configure a chat profile with a classic email address as relay?</a></li>
@@ -77,7 +76,6 @@
<li><a href="#tls">Are messages marked with the mail icon exposed on the Internet?</a></li>
<li><a href="#message-metadata">¿Cómo Delta Chat protege los metadatos en los mensajes?</a></li>
<li><a href="#device-seizure">¿Cómo proteger los metadatos y los contactos cuando se incauta un dispositivo?</a></li>
<li><a href="#who-sees-my-ip-address">Who sees my IP Address?</a></li>
<li><a href="#sealedsender">Does Delta Chat support “Sealed Sender”?</a></li>
<li><a href="#pfs">¿Soporta Delta Chat Perfect Forward Secrecy?</a></li>
<li><a href="#pqc">Does Delta Chat support Post-Quantum-Cryptography?</a></li>
@@ -187,8 +185,7 @@ If you add each other to <a href="#groups">groups</a>, end-to-end encryption wil
<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>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>Your friends may share your contact with other friends, this appears as a <strong>request</strong>.</p>
<ul>
<li>
@@ -224,10 +221,15 @@ and can tap it to start chatting with the first contact.</p>
</h3>
<p>Yes. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
<ul>
<li>
<p>Yes. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
<p>Para mejorar el rendimiento, las imágenes se optimizan y se envían en un tamaño más pequeño de forma predeterminada, pero puedes enviarla como un “archivo” para conservar la original.</p>
</li>
<li>
<p>Para mejorar el rendimiento, las imágenes se optimizan y se envían en un tamaño más pequeño de forma predeterminada, pero puedes enviarla como un “archivo” para conservar la original.</p>
</li>
</ul>
<h3 id="multiple-accounts">
@@ -258,10 +260,15 @@ o para <strong>Cambiar perfiles</strong>.</p>
</h3>
<p>Puede agregar una foto de perfil en su configuración. Si escribe a sus contactos
<ul>
<li>
<p>Puede agregar una foto de perfil en su configuración. Si escribe a sus contactos
o los agrega a través de un código QR, ellos lo verán automáticamente como su foto de perfil.</p>
<p>Por cuestiones de privacidad, nadie verá su foto de perfil hasta que les escriba un mensaje.</p>
</li>
<li>
<p>Por cuestiones de privacidad, nadie verá su foto de perfil hasta que les escriba un mensaje.</p>
</li>
</ul>
<h3 id="signature">
@@ -294,7 +301,8 @@ they will see it when they view your contact details.</p>
<p><strong>Chats muteados</strong> si no quieres recibir notificaciones de ellos. Chats muteados se mantienen en su lugar e inclusive puedes fijarlos.</p>
</li>
<li>
<p><strong>Archivar chats</strong> si no deseas verlos en tu lista de chats. Los chats archivados siguen siendo accesibles arriba de la lista de chats o a través de la búsqueda.</p>
<p><strong>Archivar chats</strong> si no deseas verlos en tu lista de chats.
Los chats archivados siguen siendo accesibles arriba de la lista de chats o a través de la búsqueda.</p>
</li>
<li>
<p>Cuando un chat archivado recibe un nuevo mensaje, a menos que esté silenciado, <strong>saldrá del archivo</strong> y volverá a aparecer en tu lista de chats.
@@ -367,18 +375,22 @@ 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>
means that the message was sent successfully to your provider.</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>
mean that at least one recipients device
reported back to having received the message.</p>
</li>
<li>
<p>Recipients may have disabled read-receipts,
so even if you see only one tick, the message may have been read.</p>
</li>
<li>
<p>The other way round, two ticks do not automatically mean
that a human has read or understood the message ;)</p>
</li>
</ul>
<p>In <a href="#groups">groups</a> the second tick means that at least one member has reported back having read the message.</p>
<p>You will only get the second tick if both you and one of the recipients who read the message
has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
<h3 id="edit">
@@ -448,13 +460,14 @@ the (anyway encrypted) messages may take longer to get deleted from their server
</h3>
<p>If you want to save storage on your device, you can choose to delete old
messages automatically.</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”;
<ul>
<li>If you want to save storage on your device, you can choose to delete old
messages automatically.</li>
<li>To turn it on, go to “delete old messages from device” in the “Chats &amp; Media”
settings. 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>
older than that.</li>
</ul>
<h3 id="remove-account">
@@ -502,15 +515,9 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<ul>
<li>
<p>Selecciona <strong>Nuevo chat</strong> y luego <strong>Nuevo grupo</strong> del menu en el sector superior derecho o toca en el botón correspondiente en Android/iOS.</p>
</li>
<li>
<p>En la siguiente pantalla selecciona a los <strong>miembros del grupo</strong> y define un <strong>nombre de grupo</strong>. Tambien puedes seleccionar un <strong>avatar de grupo</strong>.</p>
</li>
<li>
<p>Tan pronto escribas el <strong>primer mensaje</strong> en el grupo, todos los miembros serán informados sobre el nuevo grupo y podrán responder en él (mientras no escribas un mensaje será invisible para los miembros).</p>
</li>
<li>Selecciona <strong>Nuevo chat</strong> y luego <strong>Nuevo grupo</strong> del menu en el sector superior derecho o toca en el botón correspondiente en Android/iOS.</li>
<li>En la siguiente pantalla selecciona a los <strong>miembros del grupo</strong> y define un <strong>nombre de grupo</strong>. Tambien puedes seleccionar un <strong>avatar de grupo</strong>.</li>
<li>Tan pronto escribas el <strong>primer mensaje</strong> en el grupo, todos los miembros serán informados sobre el nuevo grupo y podrán responder en él (mientras no escribas un mensaje será invisible para los miembros).</li>
</ul>
<h3 id="addmembers">
@@ -521,10 +528,11 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
<ul>
<li>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
</li>
<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>
</li>
@@ -552,8 +560,10 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<p>Como ya no eres miembro del grupo, no puedes volver a agregarte.
Sin embargo, no hay problema, solo pídale a cualquier otro miembro del grupo en un chat normal que lo vuelva a agregar.</p>
<ul>
<li>Como ya no eres miembro del grupo, no puedes volver a agregarte.
Sin embargo, no hay problema, solo pídale a cualquier otro miembro del grupo en un chat normal que lo vuelva a agregar.</li>
</ul>
<h3 id="no-quiero-recibir-más-los-mensajes-de-un-grupo">
@@ -564,12 +574,15 @@ Sin embargo, no hay problema, solo pídale a cualquier otro miembro del grupo en
</h3>
<ul>
<li>Elimínate de la lista de miembros o elimina todo el chat.
Si desea unirse al grupo nuevamente más tarde, pídale a otro miembro del grupo que lo agregue nuevamente.</li>
</ul>
<p>Como alternativa, también puede “silenciar” a un grupo, lo que significa que recibirá todos los mensajes y
<li>
<p>Elimínate de la lista de miembros o elimina todo el chat.
Si desea unirse al grupo nuevamente más tarde, pídale a otro miembro del grupo que lo agregue nuevamente.</p>
</li>
<li>
<p>Como alternativa, también puede “silenciar” a un grupo, lo que significa que recibirá todos los mensajes y
aún puede escribir, pero ya no se le notifican nuevos mensajes.</p>
</li>
</ul>
<h3 id="cloning-a-group">
@@ -595,21 +608,6 @@ or right-click the group in the chat list (Desktop).</p>
<p>The new group is <strong>fully independent</strong> from the original,
which continues to work as before.</p>
<h3 id="how-many-members-can-participate-in-a-single-group">
How many members can participate in a single group? <a href="#how-many-members-can-participate-in-a-single-group" class="anchor"></a>
</h3>
<p>There is no strict technical limit,
but more than 150 is not recommended.</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">Dunbars number</a> for more insights.</p>
<h2 id="webxdc">
@@ -898,7 +896,7 @@ No es necesario un dispositivo para que el otro funcione.</p>
<p>Vuelve a verificar que ambos dispositivos estén en la <strong>misma Wi-Fi o red</strong></p>
</li>
<li>
<p>On <strong>Windows</strong>, go to Control Panel / Network and Internet
<p>On <strong>Windows</strong>, go to <strong>Control Panel / Network and Internet</strong>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
@@ -993,10 +991,10 @@ o el AppImage para Linux. Puedes encontrarlos en
</h2>
<h3 id="experiments">
<h3 id="experimental-features">
Experimental Features <a href="#experiments" class="anchor"></a>
Experimental Features <a href="#experimental-features" class="anchor"></a>
</h3>
@@ -1032,7 +1030,7 @@ you can configure relays at At <strong>Settings → Advanced → Relays</strong>
<li>
<p>You can <strong>add</strong> a relay by scanning its QR code;
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
If you have multiple relays, you will receive messages on all of them.</p>
If you have multiple relays, your will receive messages on all of them.</p>
</li>
<li>
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
@@ -1155,7 +1153,9 @@ weekly statistics will be automatically sent to a bot.</p>
</h3>
<p>Visita la página <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Estándares usados en Delta Chat</a>.</p>
<ul>
<li>Visita la página <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Estándares usados en Delta Chat</a>.</li>
</ul>
<h2 id="e2ee">
@@ -1391,32 +1391,6 @@ 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>
<h3 id="who-sees-my-ip-address">
Who sees my IP Address? <a href="#who-sees-my-ip-address" class="anchor"></a>
</h3>
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
as well as sometimes your contacts devices if you have a <a href="#experiments">call</a>
or use <a href="#webxdc">apps</a> together.</p>
<p>IP Addresses are needed for connectivity and efficiency.
They are neither persisted nor exposed.
Note that the IP Address
is not like a detailed address you give to a delivery service,
but much more coarse, often defining region or country only.</p>
<p>As this is just how the internet and other messengers work by default,
we do not offer options here or ask upfront questions.</p>
<p>If you see your IP Address as a security or privacy risk,
we recommend to use a VPN, in combination with system lockdown mode.
Hunting down options in all apps on your system will leave gaps.
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
<h3 id="sealedsender">
@@ -1652,38 +1626,52 @@ ordered chronologically:</p>
<ul>
<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>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>
<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>
</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>
<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>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>
<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>
</li>
<li>
<p>Sometimes we receive one-time donations from private individuals.
+87 -107
View File
@@ -26,7 +26,6 @@
<li><a href="#jai-quitté-un-groupe-par-accident">Jai quitté un groupe par accident.</a></li>
<li><a href="#je-ne-souhaite-plus-recevoir-les-messages-dun-groupe">Je ne souhaite plus recevoir les messages dun groupe.</a></li>
<li><a href="#cloning-a-group">Cloning a group</a></li>
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
</ul>
</li>
<li><a href="#webxdc">In-chat apps</a>
@@ -55,7 +54,7 @@
</li>
<li><a href="#advanced">Advanced</a>
<ul>
<li><a href="#experiments">Experimental Features</a></li>
<li><a href="#experimental-features">Experimental Features</a></li>
<li><a href="#relays">What are Relays?</a></li>
<li><a href="#can-i-use-a-classic-email-address-with-delta-chat">Can I use a classic email address with Delta Chat?</a></li>
<li><a href="#classic-email">How can I configure a chat profile with a classic email address as relay?</a></li>
@@ -77,7 +76,6 @@
<li><a href="#tls">Are messages marked with the mail icon exposed on the Internet?</a></li>
<li><a href="#message-metadata">How does Delta Chat protect metadata in messages?</a></li>
<li><a href="#device-seizure">How to protect metadata and contacts when a device is seized?</a></li>
<li><a href="#who-sees-my-ip-address">Who sees my IP Address?</a></li>
<li><a href="#sealedsender">Does Delta Chat support “Sealed Sender”?</a></li>
<li><a href="#pfs">Does Delta Chat support Perfect Forward Secrecy?</a></li>
<li><a href="#pqc">Does Delta Chat support Post-Quantum-Cryptography?</a></li>
@@ -187,8 +185,7 @@ If you add each other to <a href="#groups">groups</a>, end-to-end encryption wil
<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>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>Your friends may share your contact with other friends, this appears as a <strong>request</strong>.</p>
<ul>
<li>
@@ -226,10 +223,15 @@ and can tap it to start chatting with the first contact.</p>
</h3>
<p>Oui. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
<ul>
<li>
<p>Oui. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
<p>Pour améliorer les performances, les images sont redimensionnées et envoyées en taille réduite par défaut ; mais vous pouvez les envoyer en tant que “fichier” pour en conserver la taille originale.</p>
</li>
<li>
<p>Pour améliorer les performances, les images sont redimensionnées et envoyées en taille réduite par défaut ; mais vous pouvez les envoyer en tant que “fichier” pour en conserver la taille originale.</p>
</li>
</ul>
<h3 id="multiple-accounts">
@@ -262,7 +264,9 @@ or to <strong>Switch Profiles</strong>.</p>
<p>Dans les paramètres vous pouvez ajouter une photo de profil. Si vous écrivez à vos contacts ou que vous les ajoutez via le QR code, ils la verront automatiquement comme votre photo de profil.</p>
<p>Pour des raisons de confidentialité, personne ne peut voir votre photo de profil sans que vous ayez dabord entamé une discussion.</p>
<ul>
<li>Pour des raisons de confidentialité, personne ne peut voir votre photo de profil sans que vous ayez dabord entamé une discussion.</li>
</ul>
<h3 id="signature">
@@ -295,7 +299,8 @@ they will see it when they view your contact details.</p>
<p>Utilisez la <strong>sourdine</strong> pour les discussions dont vous ne voulez pas recevoir les notifications. Les discussions en sourdine figurent toujours dans votre liste et peuvent aussi être les épinglées.</p>
</li>
<li>
<p><strong>Archivez les discussions</strong> si vous ne voulez plus les voir apparaître dans votre liste de discussions. Les discussions archivées restent accessibles au-dessus de la liste de discussions ou via la recherche.</p>
<p><strong>Archivez les discussions</strong> si vous ne voulez plus les voir apparaître dans votre liste de discussions.
Les discussions archivées restent accessibles au-dessus de la liste de discussions ou via la recherche.</p>
</li>
<li>
<p>Lorsquun nouveau message est envoyé sur une discussion que vous avez archivée, et que vous navez pas mise en sourdine, la discussion <strong>sort des archives</strong> et reprend sa place dans votre liste de discussions.
@@ -331,7 +336,7 @@ By tapping <img style="vertical-align:middle; width:1.2em; margin:1px" src="../g
you can go back to the original message in the original chat</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>Finally, you can also use “Save Messages” to take <strong>personal notes</strong> - open the chat, type something, add a photo or a voice message etc.</p>
</li>
<li>
<p>As “Saved Message” are synced, they can become very handy for transferring data between devices</p>
@@ -368,18 +373,22 @@ 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>
means that the message was sent successfully to your provider.</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>
mean that at least one recipients device
reported back to having received the message.</p>
</li>
<li>
<p>Recipients may have disabled read-receipts,
so even if you see only one tick, the message may have been read.</p>
</li>
<li>
<p>The other way round, two ticks do not automatically mean
that a human has read or understood the message ;)</p>
</li>
</ul>
<p>In <a href="#groups">groups</a> the second tick means that at least one member has reported back having read the message.</p>
<p>You will only get the second tick if both you and one of the recipients who read the message
has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
<h3 id="edit">
@@ -449,9 +458,10 @@ the (anyway encrypted) messages may take longer to get deleted from their server
</h3>
<p>Vous pouvez choisir de supprimer automatiquement les anciens messages pour libérer de lespace de stockage sur votre appareil.</p>
<p>Pour activer cette option, ouvrez les paramètres des “Discussions et fichiers multimédias” et cliquez sur “Supprimer les anciens messages de lappareil”. Vous pouvez définir le délai après lequel <em>tous</em> les messages seront supprimés de votre appareil, parmi plusieurs choix allant de “Immédiatement” à “Après 1 année”.</p>
<ul>
<li>Vous pouvez choisir de supprimer automatiquement les anciens messages pour libérer de lespace de stockage sur votre appareil.</li>
<li>Pour activer cette option, ouvrez les paramètres des “Discussions et fichiers multimédias” et cliquez sur “Supprimer les anciens messages de lappareil”. Vous pouvez définir le délai après lequel <em>tous</em> les messages seront supprimés de votre appareil, parmi plusieurs choix allant de “Immédiatement” à “Après 1 année”.</li>
</ul>
<h3 id="remove-account">
@@ -499,15 +509,9 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<ul>
<li>
<p>Sélectionnez <strong>Nouvelle discussion</strong> puis <strong>Nouveau groupe</strong> dans le menu à trois points situé en haut à droite de la fenêtre ou son équivalent sous Android et iOS.</p>
</li>
<li>
<p>Sur l’écran suivant, sélectionnez <strong>Ajouter des participants</strong> et choisissez un <strong>Nom du groupe</strong>. Vous pouvez aussi choisir une <strong>image de groupe</strong>.</p>
</li>
<li>
<p>Lorsque vous enverrez le <strong>premier message</strong> dans le groupe, tous les membres en seront informés et pourront répondre. Le groupe est invisible aux autres membres si vous n’écrivez pas de premier message.</p>
</li>
<li>Sélectionnez <strong>Nouvelle discussion</strong> puis <strong>Nouveau groupe</strong> dans le menu à trois points situé en haut à droite de la fenêtre ou son équivalent sous Android et iOS.</li>
<li>Sur l’écran suivant, sélectionnez <strong>Ajouter des participants</strong> et choisissez un <strong>Nom du groupe</strong>. Vous pouvez aussi choisir une <strong>image de groupe</strong>.</li>
<li>Lorsque vous enverrez le <strong>premier message</strong> dans le groupe, tous les membres en seront informés et pourront répondre. Le groupe est invisible aux autres membres si vous n’écrivez pas de premier message.</li>
</ul>
<h3 id="addmembers">
@@ -518,10 +522,11 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
<ul>
<li>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
</li>
<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>
</li>
@@ -549,8 +554,10 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<p>Comme vous n’êtes plus membre du groupe, vous ne pouvez pas vous y ajouter vous-même.
Contactez nimporte quel autre membre de ce groupe dans une discussion directe pour lui demander de vous y ré-inviter.</p>
<ul>
<li>Comme vous n’êtes plus membre du groupe, vous ne pouvez pas vous y ajouter vous-même.
Contactez nimporte quel autre membre de ce groupe dans une discussion directe pour lui demander de vous y ré-inviter.</li>
</ul>
<h3 id="je-ne-souhaite-plus-recevoir-les-messages-dun-groupe">
@@ -561,11 +568,14 @@ Contactez nimporte quel autre membre de ce groupe dans une discussion directe
</h3>
<ul>
<li>Supprimez-vous de la liste des membres ou supprimez la discussion entière.
Si souhaitez rejoindre le groupe plus tard, demandez à un autre membre du groupe de vous ré-inviter.</li>
<li>
<p>Supprimez-vous de la liste des membres ou supprimez la discussion entière.
Si souhaitez rejoindre le groupe plus tard, demandez à un autre membre du groupe de vous ré-inviter.</p>
</li>
<li>
<p>Vous pouvez également mettre un groupe en “Sourdine” : vous recevrez tous les messages et pourrez toujours écrire, mais naurez plus les notifications des nouveaux messages.</p>
</li>
</ul>
<p>Vous pouvez également mettre un groupe en “Sourdine” : vous recevrez tous les messages et pourrez toujours écrire, mais naurez plus les notifications des nouveaux messages.</p>
<h3 id="cloning-a-group">
@@ -591,21 +601,6 @@ or right-click the group in the chat list (Desktop).</p>
<p>The new group is <strong>fully independent</strong> from the original,
which continues to work as before.</p>
<h3 id="how-many-members-can-participate-in-a-single-group">
How many members can participate in a single group? <a href="#how-many-members-can-participate-in-a-single-group" class="anchor"></a>
</h3>
<p>There is no strict technical limit,
but more than 150 is not recommended.</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">Dunbars number</a> for more insights.</p>
<h2 id="webxdc">
@@ -892,7 +887,7 @@ Lun na pas besoin de lautre pour pouvoir fonctionner.</p>
<p>Vérifier à nouveau que les deux appareils sont sur <strong>le même réseau ou le même Wi-Fi</strong>.</p>
</li>
<li>
<p>On <strong>Windows</strong>, go to Control Panel / Network and Internet
<p>On <strong>Windows</strong>, go to <strong>Control Panel / Network and Internet</strong>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
@@ -983,10 +978,10 @@ end-to-end encrypted messages with your communication partners.</p>
</h2>
<h3 id="experiments">
<h3 id="experimental-features">
Experimental Features <a href="#experiments" class="anchor"></a>
Experimental Features <a href="#experimental-features" class="anchor"></a>
</h3>
@@ -1022,7 +1017,7 @@ you can configure relays at At <strong>Settings → Advanced → Relays</strong>
<li>
<p>You can <strong>add</strong> a relay by scanning its QR code;
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
If you have multiple relays, you will receive messages on all of them.</p>
If you have multiple relays, your will receive messages on all of them.</p>
</li>
<li>
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
@@ -1145,7 +1140,9 @@ weekly statistics will be automatically sent to a bot.</p>
</h3>
<p>Consultez les <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">standards utilisés dans Delta Chat</a>.</p>
<ul>
<li>Consultez les <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">standards utilisés dans Delta Chat</a>.</li>
</ul>
<h2 id="e2ee">
@@ -1384,32 +1381,6 @@ 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>
<h3 id="who-sees-my-ip-address">
Who sees my IP Address? <a href="#who-sees-my-ip-address" class="anchor"></a>
</h3>
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
as well as sometimes your contacts devices if you have a <a href="#experiments">call</a>
or use <a href="#webxdc">apps</a> together.</p>
<p>IP Addresses are needed for connectivity and efficiency.
They are neither persisted nor exposed.
Note that the IP Address
is not like a detailed address you give to a delivery service,
but much more coarse, often defining region or country only.</p>
<p>As this is just how the internet and other messengers work by default,
we do not offer options here or ask upfront questions.</p>
<p>If you see your IP Address as a security or privacy risk,
we recommend to use a VPN, in combination with system lockdown mode.
Hunting down options in all apps on your system will leave gaps.
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
<h3 id="sealedsender">
@@ -1628,38 +1599,47 @@ ordered chronologically:</p>
<ul>
<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>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>
<p>Lassociation <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 à lapplication Android et publier une première version Beta de lapplication de bureau. Elle nous a aussi permis dancrer 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>
</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>
<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>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>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>
</li>
<li>
<p>Nous recevons parfois des dons ponctuels de la part de personnes privées.
-4
View File
@@ -6,10 +6,6 @@ a {
text-decoration: none;
}
a[href^='http']::after {
content: " ↗";
}
h2, h3, h4 {
margin-top: 2rem;
}
+103 -117
View File
@@ -15,7 +15,7 @@
<li><a href="#what-do-the-ticks-shown-beside-outgoing-messages-mean">What do the ticks shown beside outgoing messages mean?</a></li>
<li><a href="#edit">Correct typos and delete messages after sending</a></li>
<li><a href="#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="#delold">What happens if I turn on “Delete old messages from device”?</a></li>
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
</ul>
</li>
@@ -26,7 +26,6 @@
<li><a href="#i-have-deleted-myself-by-accident">I have deleted myself by accident.</a></li>
<li><a href="#i-do-not-want-to-receive-the-messages-of-a-group-any-longer">I do not want to receive the messages of a group any longer.</a></li>
<li><a href="#cloning-a-group">Cloning a group</a></li>
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
</ul>
</li>
<li><a href="#webxdc">In-chat apps</a>
@@ -55,7 +54,7 @@
</li>
<li><a href="#advanced">Advanced</a>
<ul>
<li><a href="#experiments">Experimental Features</a></li>
<li><a href="#experimental-features">Experimental Features</a></li>
<li><a href="#relays">What are Relays?</a></li>
<li><a href="#can-i-use-a-classic-email-address-with-delta-chat">Can I use a classic email address with Delta Chat?</a></li>
<li><a href="#classic-email">How can I configure a chat profile with a classic email address as relay?</a></li>
@@ -77,7 +76,6 @@
<li><a href="#tls">Are messages marked with the mail icon exposed on the Internet?</a></li>
<li><a href="#message-metadata">How does Delta Chat protect metadata in messages?</a></li>
<li><a href="#device-seizure">How to protect metadata and contacts when a device is seized?</a></li>
<li><a href="#who-sees-my-ip-address">Who sees my IP Address?</a></li>
<li><a href="#sealedsender">Does Delta Chat support “Sealed Sender”?</a></li>
<li><a href="#pfs">Does Delta Chat support Perfect Forward Secrecy?</a></li>
<li><a href="#pqc">Does Delta Chat support Post-Quantum-Cryptography?</a></li>
@@ -187,8 +185,7 @@ If you add each other to <a href="#groups">groups</a>, end-to-end encryption wil
<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>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>Your friends may share your contact with other friends, this appears as a <strong>request</strong>.</p>
<ul>
<li>
@@ -226,10 +223,15 @@ and can tap it to start chatting with the first contact.</p>
</h3>
<p>Yes. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
<ul>
<li>
<p>Yes. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
<p>For performance, images are optimized and sent at a smaller size by default, but you can send it as a “file” to preserve the original.</p>
</li>
<li>
<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>
</li>
</ul>
<h3 id="multiple-accounts">
@@ -260,11 +262,16 @@ or to <strong>Switch Profiles</strong>.</p>
</h3>
<p>Anda dapat menambahkan gambar profil di pengaturan Anda. Jika Anda menulis ke kontak Anda
atau menambahkannya melalui kode QR, mereka secara otomatis melihatnya sebagai gambar profil Anda.</p>
<p>Untuk alasan kerahasiaan, tidak ada satupun yang dapat melihat Foto Profil anda hingga anda menulis
<ul>
<li>
<p>Anda dapat menambahkan gambar profil di pengaturan Anda. Jika Anda menulis ke kontak Anda
atau menambahkannya melalui kode QR, mereka secara otomatis melihatnya sebagai gambar profil Anda.</p>
</li>
<li>
<p>Untuk alasan kerahasiaan, tidak ada satupun yang dapat melihat Foto Profil anda hingga anda menulis
sebuah pesan kepada mereka.</p>
</li>
</ul>
<h3 id="signature">
@@ -298,8 +305,7 @@ they will see it when they view your contact details.</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>
Archived chats remain accessible above the chat list or via search.</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.
@@ -334,7 +340,7 @@ By tapping <img style="vertical-align:middle; width:1.2em; margin:1px" src="../g
you can go back to the original message in the original chat</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>Finally, you can also use “Save Messages” to take <strong>personal notes</strong> - open the chat, type something, add a photo or a voice message etc.</p>
</li>
<li>
<p>As “Saved Message” are synced, they can become very handy for transferring data between devices</p>
@@ -371,18 +377,22 @@ 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>
means that the message was sent successfully to your provider.</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>
mean that at least one recipients device
reported back to having received the message.</p>
</li>
<li>
<p>Recipients may have disabled read-receipts,
so even if you see only one tick, the message may have been read.</p>
</li>
<li>
<p>The other way round, two ticks do not automatically mean
that a human has read or understood the message ;)</p>
</li>
</ul>
<p>In <a href="#groups">groups</a> the second tick means that at least one member has reported back having read the message.</p>
<p>You will only get the second tick if both you and one of the recipients who read the message
has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
<h3 id="edit">
@@ -447,18 +457,19 @@ the (anyway encrypted) messages may take longer to get deleted from their server
<h3 id="delold">
What happens if I turn on “Delete Messages from Device”? <a href="#delold" class="anchor"></a>
What happens if I turn on “Delete old messages from device”? <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>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”;
<ul>
<li>If you want to save storage on your device, you can choose to delete old
messages automatically.</li>
<li>To turn it on, go to “delete old messages from device” in the “Chats &amp; Media”
settings. 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>
older than that.</li>
</ul>
<h3 id="remove-account">
@@ -506,15 +517,9 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<ul>
<li>
<p>Select <strong>New chat</strong> and then <strong>New group</strong> from the menu in the upper right corner or hit the corresponding button on Android/iOS.</p>
</li>
<li>
<p>On the following screen, select the <strong>group members</strong> and define a <strong>group name</strong>. You can also select a <strong>group avatar</strong>.</p>
</li>
<li>
<p>As soon as you write the <strong>first message</strong> in the group, all members are informed about the new group and can answer in the group (as long as you do not write a message in the group the group is invisible to the members).</p>
</li>
<li>Select <strong>New chat</strong> and then <strong>New group</strong> from the menu in the upper right corner or hit the corresponding button on Android/iOS.</li>
<li>On the following screen, select the <strong>group members</strong> and define a <strong>group name</strong>. You can also select a <strong>group avatar</strong>.</li>
<li>As soon as you write the <strong>first message</strong> in the group, all members are informed about the new group and can answer in the group (as long as you do not write a message in the group the group is invisible to the members).</li>
</ul>
<h3 id="addmembers">
@@ -525,10 +530,11 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
<ul>
<li>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
</li>
<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>
</li>
@@ -556,8 +562,10 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<p>As youre no longer a group member, you cannot add yourself again.
However, no problem, just ask any other group member in a normal chat to re-add you.</p>
<ul>
<li>As youre no longer a group member, you cannot add yourself again.
However, no problem, just ask any other group member in a normal chat to re-add you.</li>
</ul>
<h3 id="i-do-not-want-to-receive-the-messages-of-a-group-any-longer">
@@ -568,12 +576,15 @@ However, no problem, just ask any other group member in a normal chat to re-add
</h3>
<ul>
<li>Either delete yourself from the member list or delete the whole chat.
If you want to join the group again later on, ask another group member to add you again.</li>
</ul>
<p>As an alternative, you can also “Mute” a group - doing so means you get all messages and
<li>
<p>Either delete yourself from the member list or delete the whole chat.
If you want to join the group again later on, ask another group member to add you again.</p>
</li>
<li>
<p>As an alternative, you can also “Mute” a group - doing so means you get all messages and
can still write, but are no longer notified of any new messages.</p>
</li>
</ul>
<h3 id="cloning-a-group">
@@ -599,21 +610,6 @@ or right-click the group in the chat list (Desktop).</p>
<p>The new group is <strong>fully independent</strong> from the original,
which continues to work as before.</p>
<h3 id="how-many-members-can-participate-in-a-single-group">
How many members can participate in a single group? <a href="#how-many-members-can-participate-in-a-single-group" class="anchor"></a>
</h3>
<p>There is no strict technical limit,
but more than 150 is not recommended.</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">Dunbars number</a> for more insights.</p>
<h2 id="webxdc">
@@ -902,7 +898,7 @@ One device is not needed for the other to work.</p>
<p>Double-check both devices are in the <strong>same Wi-Fi or network</strong></p>
</li>
<li>
<p>On <strong>Windows</strong>, go to Control Panel / Network and Internet
<p>On <strong>Windows</strong>, go to <strong>Control Panel / Network and Internet</strong>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
@@ -997,10 +993,10 @@ or the AppImage for Linux. You can find them on
</h2>
<h3 id="experiments">
<h3 id="experimental-features">
Experimental Features <a href="#experiments" class="anchor"></a>
Experimental Features <a href="#experimental-features" class="anchor"></a>
</h3>
@@ -1036,7 +1032,7 @@ you can configure relays at At <strong>Settings → Advanced → Relays</strong>
<li>
<p>You can <strong>add</strong> a relay by scanning its QR code;
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
If you have multiple relays, you will receive messages on all of them.</p>
If you have multiple relays, your will receive messages on all of them.</p>
</li>
<li>
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
@@ -1159,7 +1155,9 @@ weekly statistics will be automatically sent to a bot.</p>
</h3>
<p>See <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Standards used in Delta Chat</a>.</p>
<ul>
<li>See <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Standards used in Delta Chat</a>.</li>
</ul>
<h2 id="e2ee">
@@ -1398,32 +1396,6 @@ 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>
<h3 id="who-sees-my-ip-address">
Who sees my IP Address? <a href="#who-sees-my-ip-address" class="anchor"></a>
</h3>
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
as well as sometimes your contacts devices if you have a <a href="#experiments">call</a>
or use <a href="#webxdc">apps</a> together.</p>
<p>IP Addresses are needed for connectivity and efficiency.
They are neither persisted nor exposed.
Note that the IP Address
is not like a detailed address you give to a delivery service,
but much more coarse, often defining region or country only.</p>
<p>As this is just how the internet and other messengers work by default,
we do not offer options here or ask upfront questions.</p>
<p>If you see your IP Address as a security or privacy risk,
we recommend to use a VPN, in combination with system lockdown mode.
Hunting down options in all apps on your system will leave gaps.
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
<h3 id="sealedsender">
@@ -1663,38 +1635,52 @@ ordered chronologically:</p>
<ul>
<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>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>
<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>
</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>
<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>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>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>
</li>
<li>
<p>Terkadang kami menerima sumbangan satu kali dari perorangan.
+100 -109
View File
@@ -26,7 +26,6 @@
<li><a href="#mi-sono-cancellato-per-sbaglio">Mi sono cancellato per sbaglio.</a></li>
<li><a href="#non-desidero-più-ricevere-i-messaggi-di-un-gruppo">Non desidero più ricevere i messaggi di un gruppo.</a></li>
<li><a href="#clonazione-di-un-gruppo">Clonazione di un gruppo</a></li>
<li><a href="#quanti-membri-possono-partecipare-a-un-singolo-gruppo">Quanti membri possono partecipare a un singolo gruppo?</a></li>
</ul>
</li>
<li><a href="#webxdc">Apps in chat</a>
@@ -55,7 +54,7 @@
</li>
<li><a href="#avanzato">Avanzato</a>
<ul>
<li><a href="#experiments">Funzionalità Sperimentali</a></li>
<li><a href="#funzionalità-sperimentali">Funzionalità Sperimentali</a></li>
<li><a href="#relays">Cosa sono i ripetitori?</a></li>
<li><a href="#posso-usare-un-indirizzo-email-classico-con-delta-chat">Posso usare un indirizzo email classico con Delta Chat?</a></li>
<li><a href="#classic-email">Come posso configurare un profilo chat con un indirizzo email classico come inoltro?</a></li>
@@ -77,7 +76,6 @@
<li><a href="#tls">I messaggi contrassegnati dallicona della posta sono esposti su Internet?</a></li>
<li><a href="#message-metadata">In che modo Delta Chat protegge i metadati nei messaggi?</a></li>
<li><a href="#device-seizure">Come proteggere i metadati e contatti quando un dispositivo viene sequestrato?</a></li>
<li><a href="#chi-vede-il-mio-indirizzo-ip">Chi vede il mio Indirizzo IP?</a></li>
<li><a href="#sealedsender">Delta Chat supporta “Mittente Sigillato”?</a></li>
<li><a href="#pfs">Delta Chat supporta Perfect Forward Secrecy?</a></li>
<li><a href="#pqc">Delta Chat supporta la Crittografia Post-Quantistica?</a></li>
@@ -187,7 +185,7 @@ Se vi aggiungete a vicenda a <a href="#groups">gruppi</a>, la crittografia end-t
<p>Essendo un messenger privato,
solo gli amici e i familiari con cui <a href="#howtoe2ee">condividi il tuo codice QR o il link di invito</a> possono scriverti.</p>
<p>I tuoi amici potrebbero condividere i tuoi contatti con altri amici; ciò apparirà come una <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Richiesta</b></p>
<p>I tuoi amici potrebbero condividere i tuoi contatti con altri amici; ciò apparirà come una <strong>richiesta</strong>.</p>
<ul>
<li>
@@ -225,10 +223,15 @@ e potrà toccarla per iniziare a chattare con il primo contatto.</p>
</h3>
<p>Sì. Immagini, video, files, messaggi vocali ecc. possono essere inviati utilizzando <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Allegato-</strong>
<ul>
<li>
<p>Sì. Immagini, video, files, messaggi vocali ecc. possono essere inviati utilizzando <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Allegato-</strong>
o <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> pulsanti <strong>Messaggio Vocale</strong></p>
<p>Per le prestazioni, le immagini sono ottimizzate e inviate in dimensioni inferiori per impostazione predefinita, ma è possibile inviarle come “file” per preservare loriginale.</p>
</li>
<li>
<p>Per le prestazioni, le immagini sono ottimizzate e inviate in dimensioni inferiori per impostazione predefinita, ma è possibile inviarle come “file” per preservare loriginale.</p>
</li>
</ul>
<h3 id="multiple-accounts">
@@ -259,11 +262,16 @@ o <strong>Cambiare Profili</strong>.</p>
</h3>
<p>Puoi aggiungere unimmagine del profilo nelle tue impostazioni. Se scrivi ai tuoi contatti
<ul>
<li>
<p>Puoi aggiungere unimmagine del profilo nelle tue impostazioni. Se scrivi ai tuoi contatti
o li aggiungi tramite codice QR, la vedranno automaticamente come immagine del tuo profilo.</p>
<p>Per motivi di privacy, nessuno vede la tua immagine del profilo finché non scrivi un
</li>
<li>
<p>Per motivi di privacy, nessuno vede la tua immagine del profilo finché non scrivi un
messaggio a loro.</p>
</li>
</ul>
<h3 id="signature">
@@ -296,7 +304,8 @@ lo vedrà quando visualizzerà i tuoi dati di contatto.</p>
<p><strong>Silenzia chat</strong> se non vuoi ricevere notifiche da queste. Le chat silenziate restano al loro posto e puoi anche fissare una chat silenziata.</p>
</li>
<li>
<p><strong>Archivia chats</strong> se non vuoi più vederle nel tuo elenco chat. Le chat archiviate rimangono accessibili sopra lelenco delle chat o tramite la ricerca.</p>
<p><strong>Archivia chats</strong> se non vuoi più vederle nel tuo elenco chat.
Le chat archiviate rimangono accessibili sopra lelenco delle chat o tramite la ricerca.</p>
</li>
<li>
<p>Quando una chat archiviata riceve un nuovo messaggio, a meno che non sia silenziata, <strong>salterà fuori dallarchivio</strong> e tornerà nellelenco delle chat.
@@ -368,19 +377,23 @@ e anche gli altri non sempre vedranno che sei “online”.</p>
<ul>
<li>
<p><strong>Una spunta</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick1.png" alt="" />
significa che il messaggio è stato inviato correttamente al <a href="#relays">ripetitore</a>.</p>
<p><strong>Un segno di spunta</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick1.png" alt="" />
significa che il messaggio è stato inviato correttamente al tuo fornitore.</p>
</li>
<li>
<p><strong>Due spunte</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick2.png" alt="" />
indica che il tuo contatto ha letto il messaggio.</p>
<p><strong>Due spunte</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick2.png" alt="" />
significa che almeno il dispositivo di un destinatario
ha segnalato di aver ricevuto il messaggio.</p>
</li>
<li>
<p>I destinatari potrebbero aver disattivato le conferme di lettura,
quindi anche se vedi solo un segno di spunta, il messaggio potrebbe essere stato letto.</p>
</li>
<li>
<p>Al contrario, due spunte non significano automaticamente
che un essere umano abbia letto o compreso il messaggio ;)</p>
</li>
</ul>
<p>In <a href="#groups">gruppi</a> il secondo segno di spunta significa che almeno un membro ha segnalato di aver letto il messaggio.</p>
<p>Riceverai la seconda spunta solo se sia tu che uno dei destinatari che hanno letto il messaggio
avete abilitato <strong>Impostazioni → Chat → Conferme di Lettura</strong>.</p>
<h3 id="edit">
@@ -449,9 +462,14 @@ i messaggi (comunque crittografati) potrebbero richiedere più tempo per essere
</h3>
<p>Se si desidera risparmiare spazio sul dispositivo, è possibile scegliere di eliminare i vecchi messaggi automaticamente.</p>
<p>Per attivarla, andare su “Elimina Messaggi dal Dispositivo” nelle impostazioni di “Chat e Media”. È possibile impostare un intervallo di tempo compreso tra “Dopo 1 ora” e “Dopo 1 anno”; in questo modo, <em>tutti</em> i messaggi saranno eliminati dal dispositivo non appena saranno più vecchi di quel periodo.</p>
<ul>
<li>Se si desidera risparmiare spazio sul dispositivo, è possibile scegliere di eliminare i vecchi
messaggi automaticamente.</li>
<li>Per attivarla, andare su “Elimina Messaggi dal Dispositivo” nelle impostazioni di “Chat e Media”.
È possibile impostare un intervallo di tempo compreso tra “Dopo 1 ora” e “Dopo 1 anno”;
in questo modo, <em>tutti</em> i messaggi saranno eliminati dal dispositivo non appena saranno
più vecchi di quel periodo.</li>
</ul>
<h3 id="remove-account">
@@ -499,15 +517,9 @@ ed <a href="#edit">eliminare i propri messaggi</a> dai dispositivi di tutti i me
</h3>
<ul>
<li>
<p>Seleziona <strong>Nuova chat</strong> e poi <strong>Nuovo gruppo</strong> dal menu nellangolo in alto a destra o premi il pulsante corrispondente su Android/iOS.</p>
</li>
<li>
<p>Nella schermata successiva, seleziona i <strong>membri del gruppo</strong> e definisci un <strong>nome del gruppo</strong>. Puoi anche selezionare un <strong>avatar di gruppo</strong>.</p>
</li>
<li>
<p>Non appena scrivi il <strong>primo messaggio</strong> nel gruppo, tutti i membri vengono informati del nuovo gruppo e possono rispondere nel gruppo (finché non scrivi un messaggio nel gruppo il gruppo è invisibile ai membri).</p>
</li>
<li>Seleziona <strong>Nuova chat</strong> e poi <strong>Nuovo gruppo</strong> dal menu nellangolo in alto a destra o premi il pulsante corrispondente su Android/iOS.</li>
<li>Nella schermata successiva, seleziona i <strong>membri del gruppo</strong> e definisci un <strong>nome del gruppo</strong>. Puoi anche selezionare un <strong>avatar di gruppo</strong>.</li>
<li>Non appena scrivi il <strong>primo messaggio</strong> nel gruppo, tutti i membri vengono informati del nuovo gruppo e possono rispondere nel gruppo (finché non scrivi un messaggio nel gruppo il gruppo è invisibile ai membri).</li>
</ul>
<h3 id="addmembers">
@@ -518,10 +530,11 @@ ed <a href="#edit">eliminare i propri messaggi</a> dai dispositivi di tutti i me
</h3>
<p>Tutti i membri del gruppo hanno gli <strong>stessi diritti</strong>.
Per questo motivo, tutti possono eliminare qualsiasi membro o aggiungerne di nuovi.</p>
<ul>
<li>
<p>Tutti i membri del gruppo hanno gli <strong>stessi diritti</strong>.
Per questo motivo, tutti possono eliminare qualsiasi membro o aggiungerne di nuovi.</p>
</li>
<li>
<p>Per <strong>aggiungere o eliminare membri</strong>, tocca il nome del gruppo nella chat e seleziona il membro da aggiungere o rimuovere.</p>
</li>
@@ -549,8 +562,10 @@ Tuttavia, poiché i gruppi sono <a href="#groups">destinati a persone fidate</a>
</h3>
<p>Poiché non sei più un membro del gruppo, non puoi aggiungerti di nuovo.
Tuttavia, nessun problema, chiedi a qualsiasi altro membro del gruppo in una normale chat di aggiungerti nuovamente.</p>
<ul>
<li>Poiché non sei più un membro del gruppo, non puoi aggiungerti di nuovo.
Tuttavia, nessun problema, chiedi a qualsiasi altro membro del gruppo in una normale chat di aggiungerti nuovamente.</li>
</ul>
<h3 id="non-desidero-più-ricevere-i-messaggi-di-un-gruppo">
@@ -561,12 +576,15 @@ Tuttavia, nessun problema, chiedi a qualsiasi altro membro del gruppo in una nor
</h3>
<ul>
<li>Elimina te stesso dallelenco dei membri o elimina lintera chat.
Se vuoi unirti di nuovo al gruppo in un secondo momento, chiedi a un altro membro del gruppo di aggiungerti di nuovo.</li>
</ul>
<p>In alternativa, puoi anche “Silenziare” un gruppo - così facendo riceverai tutti i messaggi e
<li>
<p>Elimina te stesso dallelenco dei membri o elimina lintera chat.
Se vuoi unirti di nuovo al gruppo in un secondo momento, chiedi a un altro membro del gruppo di aggiungerti di nuovo.</p>
</li>
<li>
<p>In alternativa, puoi anche “Silenziare” un gruppo - così facendo riceverai tutti i messaggi e
puoi ancora scrivere, ma non viene più notificato alcun nuovo messaggio.</p>
</li>
</ul>
<h3 id="clonazione-di-un-gruppo">
@@ -592,21 +610,6 @@ oppure fai clic con il pulsante destro del mouse sul gruppo nellelenco delle
<p>Il nuovo gruppo è <strong>completamente indipendente</strong> dalloriginale,
che continua a funzionare come prima.</p>
<h3 id="quanti-membri-possono-partecipare-a-un-singolo-gruppo">
Quanti membri possono partecipare a un singolo gruppo? <a href="#quanti-membri-possono-partecipare-a-un-singolo-gruppo" class="anchor"></a>
</h3>
<p>Non esiste un limite tecnico preciso,
ma non è consigliabile superare i 150.</p>
<p>Man mano che i gruppi diventano più grandi, possono diventare socialmente instabili e potrebbero aver bisogno di una gerarchia,
dove Delta Chat è un servizio di messaggistica privato per chattare con <a href="#groups">uguali diritti</a>.
Vedi <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">numero di Dunbar</a> per ulteriori approfondimenti.</p>
<h2 id="webxdc">
@@ -894,7 +897,7 @@ Un dispositivo non è necessario perché laltro funzioni.</p>
<p>Verificare che entrambi i dispositivi siano nella <strong>stessa rete o Wi-Fi</strong>.</p>
</li>
<li>
<p>Su <strong>Windows</strong>, vai su Pannello di controllo / Rete e Internet
<p>Su <strong>Windows</strong>, vai su <strong>Pannello di controllo / Rete e Internet</strong>
e assicurati che <strong>Rete Privata</strong> sia selezionata come “Tipo di profilo di rete”
(dopo il trasferimento è possibile ripristinare il valore originale)</p>
</li>
@@ -984,10 +987,10 @@ o lAppImage per Linux. Le trovi su
</h2>
<h3 id="experiments">
<h3 id="funzionalità-sperimentali">
Funzionalità Sperimentali <a href="#experiments" class="anchor"></a>
Funzionalità Sperimentali <a href="#funzionalità-sperimentali" class="anchor"></a>
</h3>
@@ -1144,7 +1147,9 @@ statistiche settimanali verranno inviate automaticamente a un bot.</p>
</h3>
<p>Vedi <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Standard usati in Delta Chat</a>.</p>
<ul>
<li>Vedi <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Standard usati in Delta Chat</a>.</li>
</ul>
<h2 id="e2ee">
@@ -1381,32 +1386,6 @@ con la consapevolezza che tutti i loro dati, insieme a tutti i metadati, verrann
Inoltre, se un dispositivo viene sequestrato, i contatti di chat che utilizzano profili di breve durata
non possono essere identificati facilmente.</p>
<h3 id="chi-vede-il-mio-indirizzo-ip">
Chi vede il mio Indirizzo IP? <a href="#chi-vede-il-mio-indirizzo-ip" class="anchor"></a>
</h3>
<p>Il <a href="#relays">ripetitore</a> utilizzato deve conoscere il tuo indirizzo IP,
e talvolta anche i dispositivi dei tuoi contatti se avete una <a href="#experiments">chiamata</a>
o utilizzate <a href="#webxdc">apps</a> insieme.</p>
<p>Gli indirizzi IP sono necessari per la connettività e lefficienza.
Non sono né persistenti né esposti.
Si noti che lindirizzo IP
non è come un indirizzo dettagliato che si fornisce a un servizio di consegna,
ma molto più generico, che spesso definisce solo la regione o il paese.</p>
<p>Poiché questo è il modo in cui Internet e altri servizi di messaggistica funzionano di default,
non offriamo opzioni né poniamo domande in anticipo.</p>
<p>Se ritieni che il tuo indirizzo IP rappresenti un rischio per la sicurezza o la privacy,
ti consigliamo di utilizzare una VPN, in combinazione con la modalità di blocco del sistema.
Esplorare le opzioni in tutte le app del tuo sistema lascerà delle lacune.
Ad esempio, cliccare su un link espone gli indirizzi IP a sconosciuti e rappresenta il rischio di gran lunga maggiore.</p>
<h3 id="sealedsender">
@@ -1647,37 +1626,49 @@ ordinate cronologicamente:</p>
<ul>
<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>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>L<a href="https://opentechfund.org">Open Technology Fund</a> ci ha dato una
prima sovvenzione 2018/2019 (~$200K) durante la quale abbiamo notevolmente migliorato lapp Android
e ha rilasciato una prima versione beta dellapp desktop, e che inoltre
ancorato i nostri sviluppi delle funzionalità nella ricerca sulla UX nei contesti dei diritti umani,
vedete il nostro <a href="https://delta.chat/en/2019-07-19-uxreport">Rapporto Needfinding e UX</a> conclusivo.
La seconda sovvenzione 2019/2020 (~$300K) ci ha aiutato a farlo
rilasciare nelle versioni Delta/iOS, per convertire la nostra libreria principale in Rust, e
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 dallUE 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>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>Da fine 2021 a marzo 2023 abbiamo ricevuto un finanziamento <em>Internet Freedom</em> (500.000 USD) dallUfficio 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 unampia 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>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>
<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>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 allinizio del 2025.</p>
</li>
<li>
<p>A volte riceviamo donazioni una tantum da privati.
+102 -112
View File
@@ -26,7 +26,6 @@
<li><a href="#ik-heb-mezelf-per-ongeluk-verwijderd">Ik heb mezelf per ongeluk verwijderd</a></li>
<li><a href="#ik-wil-geen-groepsberichten-meer-ontvangen">Ik wil geen groepsberichten meer ontvangen</a></li>
<li><a href="#cloning-a-group">Cloning a group</a></li>
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
</ul>
</li>
<li><a href="#webxdc">In-chat apps</a>
@@ -55,7 +54,7 @@
</li>
<li><a href="#advanced">Advanced</a>
<ul>
<li><a href="#experiments">Experimental Features</a></li>
<li><a href="#experimental-features">Experimental Features</a></li>
<li><a href="#relays">What are Relays?</a></li>
<li><a href="#can-i-use-a-classic-email-address-with-delta-chat">Can I use a classic email address with Delta Chat?</a></li>
<li><a href="#classic-email">How can I configure a chat profile with a classic email address as relay?</a></li>
@@ -77,7 +76,6 @@
<li><a href="#tls">Are messages marked with the mail icon exposed on the Internet?</a></li>
<li><a href="#message-metadata">How does Delta Chat protect metadata in messages?</a></li>
<li><a href="#device-seizure">How to protect metadata and contacts when a device is seized?</a></li>
<li><a href="#who-sees-my-ip-address">Who sees my IP Address?</a></li>
<li><a href="#sealedsender">Does Delta Chat support “Sealed Sender”?</a></li>
<li><a href="#pfs">Does Delta Chat support Perfect Forward Secrecy?</a></li>
<li><a href="#pqc">Does Delta Chat support Post-Quantum-Cryptography?</a></li>
@@ -187,8 +185,7 @@ If you add each other to <a href="#groups">groups</a>, end-to-end encryption wil
<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>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>Your friends may share your contact with other friends, this appears as a <strong>request</strong>.</p>
<ul>
<li>
@@ -226,10 +223,15 @@ and can tap it to start chatting with the first contact.</p>
</h3>
<p>Yes. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
<ul>
<li>
<p>Yes. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
<p>Om de prestaties te verhogen, worden afbeeldingen standaard geoptimaliseerd en verkleind verstuurd, maar je kunt ze als een bestand verzenden om het origineel te sturen.</p>
</li>
<li>
<p>Om de prestaties te verhogen, worden afbeeldingen standaard geoptimaliseerd en verkleind verstuurd, maar je kunt ze als een bestand verzenden om het origineel te sturen.</p>
</li>
</ul>
<h3 id="multiple-accounts">
@@ -260,11 +262,16 @@ or to <strong>Switch Profiles</strong>.</p>
</h3>
<p>In de instellingen kun je een profielfoto toevoegen. Als je een bericht stuurt aan
je contactpersonen of ze toevoegt middels hun QR-code, dan krijgen ze je profielfoto te zien.</p>
<p>Omwille van je privacy, krijgen anderen je profielfoto pas te zien
als je ze een bericht stuurt.</p>
<ul>
<li>
<p>In de instellingen kun je een profielfoto toevoegen. Als je een bericht stuurt aan
je contactpersonen of ze toevoegt middels hun QR-code, dan krijgen ze je profielfoto te zien.</p>
</li>
<li>
<p>Omwille van je privacy, krijgen anderen je profielfoto pas te zien
als je ze een bericht stuurt.</p>
</li>
</ul>
<h3 id="signature">
@@ -297,7 +304,8 @@ they will see it when they view your contact details.</p>
<p>Stel gesprekken in op <strong>Negeren</strong> als je geen meldingen meer wilt ontvangen. Wel blijven genegeerde gesprekken op de lijst staan en kun je ze te allen tijde vastmaken.</p>
</li>
<li>
<p><strong>Archiveer gesprekken</strong> als je ze niet meer op de gesprekslijst wilt zien. Gearchiveerde gesprekken zijn te allen tijde te bekijken boven de lijst of via een zoekopdracht.</p>
<p><strong>Archiveer gesprekken</strong> als je ze niet meer op de gesprekslijst wilt zien.
Gearchiveerde gesprekken zijn te allen tijde te bekijken boven de lijst of via een zoekopdracht.</p>
</li>
<li>
<p>Als er een nieuw bericht in een gearchiveerd gesprek wordt ontvangen, dan wordt het gesprek in kwestie <strong>ge-dearchiveerd</strong> en dus weer op de gesprekslijst geplaatst.
@@ -333,7 +341,7 @@ By tapping <img style="vertical-align:middle; width:1.2em; margin:1px" src="../g
you can go back to the original message in the original chat</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>Finally, you can also use “Save Messages” to take <strong>personal notes</strong> - open the chat, type something, add a photo or a voice message etc.</p>
</li>
<li>
<p>As “Saved Message” are synced, they can become very handy for transferring data between devices</p>
@@ -370,18 +378,22 @@ 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>
means that the message was sent successfully to your provider.</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>
mean that at least one recipients device
reported back to having received the message.</p>
</li>
<li>
<p>Recipients may have disabled read-receipts,
so even if you see only one tick, the message may have been read.</p>
</li>
<li>
<p>The other way round, two ticks do not automatically mean
that a human has read or understood the message ;)</p>
</li>
</ul>
<p>In <a href="#groups">groups</a> the second tick means that at least one member has reported back having read the message.</p>
<p>You will only get the second tick if both you and one of the recipients who read the message
has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
<h3 id="edit">
@@ -451,9 +463,12 @@ the (anyway encrypted) messages may take longer to get deleted from their server
</h3>
<p>Als je ruimte wilt besparen op je apparaat, dan kun je er voor kiezen om oude berichten automatisch te verwijderen.</p>
<p>Inschakelen kan via de sectie Gesprekken en media in de instellingen. Je kunt een periode tussen na één uur en na één jaar kiezen. <em>Alle</em> berichten die ouder zijn, worden verwijderd.</p>
<ul>
<li>Als je ruimte wilt besparen op je apparaat, dan kun je er voor kiezen om oude
berichten automatisch te verwijderen.</li>
<li>Inschakelen kan via de sectie Gesprekken en media in de instellingen. Je kunt een periode tussen
na één uur en na één jaar kiezen. *Alle berichten die ouder zijn, worden verwijderd.</li>
</ul>
<h3 id="remove-account">
@@ -501,15 +516,9 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<ul>
<li>
<p>Open het menu met de drie puntjes rechtsboven in het gespreksoverzicht, kies <strong>Nieuw gesprek</strong> en daarna <strong>Nieuwe groep</strong>.</p>
</li>
<li>
<p>Kies dan de <strong>groepsleden</strong> en druk op het vinkje rechtsboven. Daarna kun je een <strong>groepsnaam</strong> opgeven.</p>
</li>
<li>
<p>Zodra je het <strong>eerste groepsbericht</strong> hebt verstuurd, worden alle deelnemers op de hoogte gebracht en kunnen zij antwoorden versturen (de groep blijft onzichtbaar voor anderen zolang jij geen bericht verstuurt).</p>
</li>
<li>Open het menu met de drie puntjes rechtsboven in het gespreksoverzicht, kies <strong>Nieuw gesprek</strong> en daarna <strong>Nieuwe groep</strong>.</li>
<li>Kies dan de <strong>groepsleden</strong> en druk op het vinkje rechtsboven. Daarna kun je een <strong>groepsnaam</strong> opgeven.</li>
<li>Zodra je het <strong>eerste groepsbericht</strong> hebt verstuurd, worden alle deelnemers op de hoogte gebracht en kunnen zij antwoorden versturen (de groep blijft onzichtbaar voor anderen zolang jij geen bericht verstuurt).</li>
</ul>
<h3 id="addmembers">
@@ -520,10 +529,11 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
<ul>
<li>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
</li>
<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>
</li>
@@ -551,8 +561,10 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<p>Je neemt geen deel meer aan de groep en kunt jezelf dus niet meer toevoegen.
Vraag iemand via een één-op-ééngesprek of hij/zij je weer wilt toevoegen.</p>
<ul>
<li>Je neemt geen deel meer aan de groep en kunt jezelf dus niet meer toevoegen.
Vraag iemand via een één-op-ééngesprek of hij/zij je weer wilt toevoegen.</li>
</ul>
<h3 id="ik-wil-geen-groepsberichten-meer-ontvangen">
@@ -563,12 +575,15 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<ul>
<li>Verwijder jezelf van de groepslijst of verwijder het hele groepsgesprek.
Als je later weer wilt deelnemen, vraag dan iemand anders of hij/zij je weer wilt toevoegen.</li>
<li>
<p>Verwijder jezelf van de groepslijst of verwijder het hele groepsgesprek.
Als je later weer wilt deelnemen, vraag dan iemand anders of hij/zij je weer wilt toevoegen.</p>
</li>
<li>
<p>Wat ook kan doen is groepsmeldingen uitschakelen. Zo blijf je in de groep, maar ontvang je
geen meldingen meer als er nieuwe berichten zijn.</p>
</li>
</ul>
<p>Wat ook kan doen is groepsmeldingen uitschakelen. Zo blijf je in de groep, maar ontvang je
geen meldingen meer als er nieuwe berichten zijn.</p>
<h3 id="cloning-a-group">
@@ -594,21 +609,6 @@ or right-click the group in the chat list (Desktop).</p>
<p>The new group is <strong>fully independent</strong> from the original,
which continues to work as before.</p>
<h3 id="how-many-members-can-participate-in-a-single-group">
How many members can participate in a single group? <a href="#how-many-members-can-participate-in-a-single-group" class="anchor"></a>
</h3>
<p>There is no strict technical limit,
but more than 150 is not recommended.</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">Dunbars number</a> for more insights.</p>
<h2 id="webxdc">
@@ -896,7 +896,7 @@ op beide apparaten</strong>. Hierdoor hoef je niet het ene apparaat bij de hand
<p>Controleer of beide apparaten verbonden zijn met <strong>hetzelfde (wifi)netwerk</strong></p>
</li>
<li>
<p>On <strong>Windows</strong>, go to Control Panel / Network and Internet
<p>On <strong>Windows</strong>, go to <strong>Control Panel / Network and Internet</strong>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
@@ -991,10 +991,10 @@ of de AppImage van de Linux-client. Deze kun je downloaden op
</h2>
<h3 id="experiments">
<h3 id="experimental-features">
Experimental Features <a href="#experiments" class="anchor"></a>
Experimental Features <a href="#experimental-features" class="anchor"></a>
</h3>
@@ -1030,7 +1030,7 @@ you can configure relays at At <strong>Settings → Advanced → Relays</strong>
<li>
<p>You can <strong>add</strong> a relay by scanning its QR code;
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
If you have multiple relays, you will receive messages on all of them.</p>
If you have multiple relays, your will receive messages on all of them.</p>
</li>
<li>
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
@@ -1153,7 +1153,9 @@ weekly statistics will be automatically sent to a bot.</p>
</h3>
<p>Bekijk de pagina <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Door Delta Chat gebruikte standaarden</a>.</p>
<ul>
<li>Bekijk de pagina <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Door Delta Chat gebruikte standaarden</a>.</li>
</ul>
<h2 id="e2ee">
@@ -1392,32 +1394,6 @@ 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>
<h3 id="who-sees-my-ip-address">
Who sees my IP Address? <a href="#who-sees-my-ip-address" class="anchor"></a>
</h3>
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
as well as sometimes your contacts devices if you have a <a href="#experiments">call</a>
or use <a href="#webxdc">apps</a> together.</p>
<p>IP Addresses are needed for connectivity and efficiency.
They are neither persisted nor exposed.
Note that the IP Address
is not like a detailed address you give to a delivery service,
but much more coarse, often defining region or country only.</p>
<p>As this is just how the internet and other messengers work by default,
we do not offer options here or ask upfront questions.</p>
<p>If you see your IP Address as a security or privacy risk,
we recommend to use a VPN, in combination with system lockdown mode.
Hunting down options in all apps on your system will leave gaps.
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
<h3 id="sealedsender">
@@ -1657,38 +1633,52 @@ ordered chronologically:</p>
<ul>
<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>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>
<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>
</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>
<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>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>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>
</li>
<li>
<p>Soms ontvangen we eenmalige donaties van privépersonen, waar we
+76 -106
View File
@@ -26,7 +26,6 @@
<li><a href="#usunąłem-się-przez-przypadek">Usunąłem się przez przypadek.</a></li>
<li><a href="#nie-chcę-już-otrzymywać-wiadomości-od-grupy">Nie chcę już otrzymywać wiadomości od grupy.</a></li>
<li><a href="#cloning-a-group">Cloning a group</a></li>
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
</ul>
</li>
<li><a href="#webxdc">In-chat apps</a>
@@ -55,7 +54,7 @@
</li>
<li><a href="#zaawansowane">Zaawansowane</a>
<ul>
<li><a href="#experiments">Experimental Features</a></li>
<li><a href="#experimental-features">Experimental Features</a></li>
<li><a href="#relays">What are Relays?</a></li>
<li><a href="#can-i-use-a-classic-email-address-with-delta-chat">Can I use a classic email address with Delta Chat?</a></li>
<li><a href="#classic-email">How can I configure a chat profile with a classic email address as relay?</a></li>
@@ -77,7 +76,6 @@
<li><a href="#tls">Czy wiadomości oznaczone ikoną poczty są widoczne w internecie?</a></li>
<li><a href="#message-metadata">W jaki sposób Delta Chat chroni metadane w wiadomościach?</a></li>
<li><a href="#device-seizure">Jak chronić metadane i kontakty w przypadku przejęcia urządzenia?</a></li>
<li><a href="#who-sees-my-ip-address">Who sees my IP Address?</a></li>
<li><a href="#sealedsender">Czy Delta Chat obsługuje funkcję „Sealed Sender”?</a></li>
<li><a href="#pfs">Czy Delta Chat obsługuje funkcję Perfect Forward Secrecy?</a></li>
<li><a href="#pqc">Czy Delta Chat obsługuje kryptografię postkwantową?</a></li>
@@ -105,7 +103,8 @@
</h2>
<p>Delta Chat to niezawodna, zdecentralizowana i bezpieczna aplikacja do błyskawicznego przesyłania wiadomości, dostępna na platformy mobilne i stacjonarne.</p>
<p>Delta Chat is a reliable, decentralized and secure instant messaging app,
available for mobile and desktop platforms.</p>
<ul>
<li>
@@ -186,8 +185,7 @@ If you add each other to <a href="#groups">groups</a>, end-to-end encryption wil
<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>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>Your friends may share your contact with other friends, this appears as a <strong>request</strong>.</p>
<ul>
<li>
@@ -223,10 +221,15 @@ and can tap it to start chatting with the first contact.</p>
</h3>
<p>Tak. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
<ul>
<li>
<p>Tak. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
<p>Ze względu na wydajność obrazy są domyślnie optymalizowane i wysyłane w mniejszym rozmiarze, ale można je wysłać jako „plik”, aby zachować oryginał.</p>
</li>
<li>
<p>Ze względu na wydajność obrazy są domyślnie optymalizowane i wysyłane w mniejszym rozmiarze, ale można je wysłać jako „plik”, aby zachować oryginał.</p>
</li>
</ul>
<h3 id="multiple-accounts">
@@ -256,9 +259,14 @@ and uses the server only to relay messages.</p>
</h3>
<p>Możesz dodać zdjęcie profilowe w swoich ustawieniach. Jeśli napiszesz do swoich kontaktów lub dodasz je za pomocą kodu QR, automatycznie zobaczą je jako Twoje zdjęcie profilowe.</p>
<p>Ze względów prywatności nikt nie widzi Twojego zdjęcia profilowego, dopóki nie napiszesz do niego wiadomości.</p>
<ul>
<li>
<p>Możesz dodać zdjęcie profilowe w swoich ustawieniach. Jeśli napiszesz do swoich kontaktów lub dodasz je za pomocą kodu QR, automatycznie zobaczą je jako Twoje zdjęcie profilowe.</p>
</li>
<li>
<p>Ze względów prywatności nikt nie widzi Twojego zdjęcia profilowego, dopóki nie napiszesz do niego wiadomości.</p>
</li>
</ul>
<h3 id="signature">
@@ -358,18 +366,22 @@ 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>
means that the message was sent successfully to your provider.</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>
mean that at least one recipients device
reported back to having received the message.</p>
</li>
<li>
<p>Recipients may have disabled read-receipts,
so even if you see only one tick, the message may have been read.</p>
</li>
<li>
<p>The other way round, two ticks do not automatically mean
that a human has read or understood the message ;)</p>
</li>
</ul>
<p>In <a href="#groups">groups</a> the second tick means that at least one member has reported back having read the message.</p>
<p>You will only get the second tick if both you and one of the recipients who read the message
has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
<h3 id="edit">
@@ -426,9 +438,10 @@ the (anyway encrypted) messages may take longer to get deleted from their server
</h3>
<p>Jeśli chcesz zaoszczędzić miejsce na urządzeniu, możesz wybrać opcję automatycznego usuwania starych wiadomości.</p>
<p>Aby ją włączyć, przejdź do „Usuń wiadomości z urządzenia” w ustawieniach w sekcji „Czaty i media”. Możesz ustawić przedział czasowy pomiędzy „po 1 godzinie” a „po 1 roku”; w ten sposób <em>wszystkie</em> wiadomości zostaną usunięte z urządzenia, gdy tylko staną się starsze.</p>
<ul>
<li>Jeśli chcesz zaoszczędzić miejsce na urządzeniu, możesz wybrać opcję automatycznego usuwania starych wiadomości.</li>
<li>Aby ją włączyć, przejdź do „Usuń wiadomości z urządzenia” w ustawieniach w sekcji „Czaty i media”. Możesz ustawić przedział czasowy pomiędzy „po 1 godzinie” a „po 1 roku”; w ten sposób <em>wszystkie</em> wiadomości zostaną usunięte z urządzenia, gdy tylko staną się starsze.</li>
</ul>
<h3 id="remove-account">
@@ -476,15 +489,9 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<ul>
<li>
<p>Wybierz <strong>Nowy czat</strong>, a następnie <strong>Nowa grupa</strong> z menu w prawym górnym rogu lub naciśnij odpowiedni przycisk na Androidzie / iOS.</p>
</li>
<li>
<p>Na następnym ekranie wybierz <strong>członków grupy</strong> i zdefiniuj <strong>nazwę grupy</strong>. Możesz też wybrać awatar <strong>grupy</strong>.</p>
</li>
<li>
<p>Zaraz po napisaniu pierwszej wiadomości w grupie wszyscy członkowie zostaną poinformowani o nowej grupie i mogą odpowiedzieć w grupie (jeżeli nie napiszesz wiadomości w grupie, grupa jest niewidoczna dla członków).</p>
</li>
<li>Wybierz <strong>Nowy czat</strong>, a następnie <strong>Nowa grupa</strong> z menu w prawym górnym rogu lub naciśnij odpowiedni przycisk na Androidzie / iOS.</li>
<li>Na następnym ekranie wybierz <strong>członków grupy</strong> i zdefiniuj <strong>nazwę grupy</strong>. Możesz też wybrać awatar <strong>grupy</strong>.</li>
<li>Zaraz po napisaniu pierwszej wiadomości w grupie wszyscy członkowie zostaną poinformowani o nowej grupie i mogą odpowiedzieć w grupie (jeżeli nie napiszesz wiadomości w grupie, grupa jest niewidoczna dla członków).</li>
</ul>
<h3 id="addmembers">
@@ -495,10 +502,11 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
<ul>
<li>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
</li>
<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>
</li>
@@ -526,8 +534,10 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<p>Ponieważ nie jesteś członkiem grupy, nie możesz dodać siebie ponownie.
Jednak nie ma problemu, po prostu poproś dowolnego członka grupy na normalnym czacie, aby dodał cię ponownie.</p>
<ul>
<li>Ponieważ nie jesteś członkiem grupy, nie możesz dodać siebie ponownie.
Jednak nie ma problemu, po prostu poproś dowolnego członka grupy na normalnym czacie, aby dodał cię ponownie.</li>
</ul>
<h3 id="nie-chcę-już-otrzymywać-wiadomości-od-grupy">
@@ -538,12 +548,15 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<ul>
<li>Usuń siebie z listy członków lub usuń cały czat.
Jeśli później będziesz chciał ponownie dołączyć do grupy, poproś innego członka grupy, aby dodał cię do grupy.</li>
<li>
<p>Usuń siebie z listy członków lub usuń cy czat.
Jeśli później będziesz chciał ponownie dołączyć do grupy, poproś innego członka grupy, aby dodał cię do grupy.</p>
</li>
<li>
<p>Alternatywnie możesz też „Wyłączyć powiadomienia” dla grupy dzięki temu otrzymasz wszystkie wiadomości i
nadal będziesz mógł pisać, ale nie będziesz już powiadamiany o żadnych nowych wiadomościach.</p>
</li>
</ul>
<p>Alternatywnie możesz też „Wyłączyć powiadomienia” dla grupy dzięki temu otrzymasz wszystkie wiadomości i
nadal będziesz mógł pisać, ale nie będziesz już powiadamiany o żadnych nowych wiadomościach.</p>
<h3 id="cloning-a-group">
@@ -569,21 +582,6 @@ or right-click the group in the chat list (Desktop).</p>
<p>The new group is <strong>fully independent</strong> from the original,
which continues to work as before.</p>
<h3 id="how-many-members-can-participate-in-a-single-group">
How many members can participate in a single group? <a href="#how-many-members-can-participate-in-a-single-group" class="anchor"></a>
</h3>
<p>There is no strict technical limit,
but more than 150 is not recommended.</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">Dunbars number</a> for more insights.</p>
<h2 id="webxdc">
@@ -836,7 +834,7 @@ Welcome to the power of the interoperable chatmail relay network :)</p>
<p>Sprawdź dokładnie, czy oba urządzenia są w tym <strong>samym Wi-Fi lub tej samej sieci</strong></p>
</li>
<li>
<p>Na <strong>Windowsie</strong>, przejdź do Panel sterowania / Sieć i internet i upewnij się, że <strong>Sieć prywatna</strong> jest wybrana jako “Typ profilu sieci”
<p>Na <strong>Windowsie</strong>, przejdź do <strong>Panel sterowania / Sieć i internet</strong> i upewnij się, że <strong>Sieć prywatna</strong> jest wybrana jako “Typ profilu sieci”
(po przeniesieniu możesz wrócić do pierwotnej wartości)</p>
</li>
<li>
@@ -913,10 +911,10 @@ Jeśli korzystasz z iOS i napotykasz trudności, może <a href="https://support.
</h2>
<h3 id="experiments">
<h3 id="experimental-features">
Experimental Features <a href="#experiments" class="anchor"></a>
Experimental Features <a href="#experimental-features" class="anchor"></a>
</h3>
@@ -952,7 +950,7 @@ you can configure relays at At <strong>Settings → Advanced → Relays</strong>
<li>
<p>You can <strong>add</strong> a relay by scanning its QR code;
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
If you have multiple relays, you will receive messages on all of them.</p>
If you have multiple relays, your will receive messages on all of them.</p>
</li>
<li>
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
@@ -1075,7 +1073,9 @@ weekly statistics will be automatically sent to a bot.</p>
</h3>
<p>Zobacz <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Standardy używane w Delta Chat</a>.</p>
<ul>
<li>Zobacz <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Standardy używane w Delta Chat</a>.</li>
</ul>
<h2 id="e2ee">
@@ -1262,32 +1262,6 @@ 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>
<h3 id="who-sees-my-ip-address">
Who sees my IP Address? <a href="#who-sees-my-ip-address" class="anchor"></a>
</h3>
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
as well as sometimes your contacts devices if you have a <a href="#experiments">call</a>
or use <a href="#webxdc">apps</a> together.</p>
<p>IP Addresses are needed for connectivity and efficiency.
They are neither persisted nor exposed.
Note that the IP Address
is not like a detailed address you give to a delivery service,
but much more coarse, often defining region or country only.</p>
<p>As this is just how the internet and other messengers work by default,
we do not offer options here or ask upfront questions.</p>
<p>If you see your IP Address as a security or privacy risk,
we recommend to use a VPN, in combination with system lockdown mode.
Hunting down options in all apps on your system will leave gaps.
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
<h3 id="sealedsender">
@@ -1463,35 +1437,31 @@ Raczej korzystamy z publicznych źródeł finansowania, jak dotąd pochodzących
<ul>
<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>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><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>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>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>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>
<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>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>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>
+287 -300
View File
@@ -2,36 +2,35 @@
<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">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="#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="#dá-para-mandar-imagens-vídeos-e-outros-anexos-pelo-delta-chat">Dá para mandar imagens, vídeos e outros anexos pelo Delta Chat?</a></li>
<li><a href="#multiple-accounts">O que são perfis? Como posso alternar entre eles?</a></li>
<li><a href="#multiple-accounts">What are profiles? How can I switch between them?</a></li>
<li><a href="#quem-consegue-ver-a-imagem-do-meu-perfil">Quem consegue ver a imagem do meu perfil?</a></li>
<li><a href="#signature">Posso definir uma Bio/Assinatura com o Delta Chat?</a></li>
<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="#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="#o-que-significam-os-carrapatos-mostrados-ao-lado-das-mensagens-de-saída">O que significam os carrapatos mostrados ao lado das mensagens de saída?</a></li>
<li><a href="#edit">Corrigir erros de digitação e excluir mensagens após o envio</a></li>
<li><a href="#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>
<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 old messages from device”?</a></li>
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
</ul>
</li>
<li><a href="#groups">Grupos</a>
<li><a href="#groups">Groups</a>
<ul>
<li><a href="#criação-de-um-grupo">Criação de um grupo</a></li>
<li><a href="#addmembers">Adicionar e remover membros</a></li>
<li><a href="#addmembers">Add and remove members</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="#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>
<li><a href="#cloning-a-group">Cloning a group</a></li>
</ul>
</li>
<li><a href="#webxdc">Aplicativos embutidos</a>
<li><a href="#webxdc">In-chat apps</a>
<ul>
<li><a href="#onde-posso-obter-aplicativos-embutidos">Onde posso obter aplicativos embutidos?</a></li>
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</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>
@@ -55,7 +54,7 @@
</li>
<li><a href="#advanced">Advanced</a>
<ul>
<li><a href="#experiments">Experimental Features</a></li>
<li><a href="#experimental-features">Experimental Features</a></li>
<li><a href="#relays">What are Relays?</a></li>
<li><a href="#can-i-use-a-classic-email-address-with-delta-chat">Can I use a classic email address with Delta Chat?</a></li>
<li><a href="#classic-email">How can I configure a chat profile with a classic email address as relay?</a></li>
@@ -77,7 +76,6 @@
<li><a href="#tls">Are messages marked with the mail icon exposed on the Internet?</a></li>
<li><a href="#message-metadata">How does Delta Chat protect metadata in messages?</a></li>
<li><a href="#device-seizure">How to protect metadata and contacts when a device is seized?</a></li>
<li><a href="#who-sees-my-ip-address">Who sees my IP Address?</a></li>
<li><a href="#sealedsender">Does Delta Chat support “Sealed Sender”?</a></li>
<li><a href="#pfs">Does Delta Chat support Perfect Forward Secrecy?</a></li>
<li><a href="#pqc">Does Delta Chat support Post-Quantum-Cryptography?</a></li>
@@ -105,118 +103,117 @@
</h2>
<p>Delta Chat é um aplicativo de mensagens instantâneas confiável, descentralizado e seguro,
disponível para desktop e plataformas móveis.</p>
<p>Delta Chat is a reliable, decentralized and secure instant messaging app,
available for mobile and desktop platforms.</p>
<ul>
<li>
<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>
<p>Instant creation of <strong>private chat profiles</strong>
with secure and interoperable <a href="https://chatmail.at/relays">chatmail relays</a>
that offer instant message delivery, and Push Notifications for iOS and Android devices.</p>
</li>
<li>
<p>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>
<p>Pervasive <a href="#multiple-accounts">multi-profile</a> and
<a href="#multiclient">multi-device</a> support on all platforms
and between different <a href="https://chatmail.at/clients">chatmail apps</a>.</p>
</li>
<li>
<p><a href="#webxdc">Aplicativos interativos embutidos</a> para jogos e colaboração</p>
<p>Interactive <a href="#webxdc">in-chat apps</a> for gaming and collaboration</p>
</li>
<li>
<p><a href="#security-audits">Criptografia de ponta a ponta auditada</a>
segura contra ataques à rede e ao servidor.</p>
<p><a href="#security-audits">Audited end-to-end encryption</a>
safe against network and server attacks.</p>
</li>
<li>
<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>
<p>Free and Open Source software, both app and server side,
built on <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Internet Standards</a>.</p>
</li>
</ul>
<h3 id="howtoe2ee">
Como posso encontrar pessoas para conversar? <a href="#howtoe2ee" class="anchor"></a>
How can I find people to chat with? <a href="#howtoe2ee" class="anchor"></a>
</h3>
<p>Primeiro, observe que o Delta Chat é um mensageiro privado.
Não há descoberta pública, <em>você</em> decide sobre seus contatos.</p>
<p>First, note that Delta Chat is a private messenger.
There is no public discovery, <em>you</em> decide about your contacts.</p>
<ul>
<li>
<p>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>
<p>If you are <strong>face to face</strong> with your friend or family,
tap the <strong>QR Code</strong> icon <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" />
on the main screen.<br />
Ask your chat partner to <strong>scan</strong> the QR image
with their Delta Chat app.</p>
</li>
<li>
<p>Para criar um contato <strong>remotamente</strong>,
na mesma tela,
clique em “Copiar” ouCompartilhar” e envie o <strong>link do convite</strong>
por meio de outro mensageiro privado.</p>
<p>For a <strong>remote</strong> contact setup,
from the same screen,
click “Copy” orShareand send the <strong>invite link</strong>
through another private chat.</p>
</li>
</ul>
<p>Agora, aguarde enquanto a conexão se estabelece.</p>
<p>Now wait while connection gets established.</p>
<ul>
<li>
<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>
<p>If both sides are online, they will soon see a chat
and can start messaging securely.</p>
</li>
<li>
<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>
<p>If one side is offline or in bad network,
the ability to chat is delayed until connectivity is restored.</p>
</li>
</ul>
<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>
<p>Congratulations!
You now will automatically use <a href="#e2ee">end-to-end encryption</a> with this contact.
If you add each other to <a href="#groups">groups</a>, end-to-end encryption will be established among all members.</p>
<h3 id="por-que-um-chat-está-marcado-como-solicitação">
<h3 id="why-is-a-chat-marked-as-request">
Por que um chat está marcado como “Solicitação”? <a href="#por-que-um-chat-está-marcado-como-solicitação" class="anchor"></a>
Why is a chat marked as “Request”? <a href="#why-is-a-chat-marked-as-request" class="anchor"></a>
</h3>
<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>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>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>
<p>Your friends may share your contact with other friends, this appears as a <strong>request</strong>.</p>
<ul>
<li>
<p>Você precisa <strong>aceitar</strong> a solicitação antes de poder responder.</p>
<p>You need to <strong>accept</strong> the request before you can reply.</p>
</li>
<li>
<p>Você também pode <strong>excluir</strong> a solicitação se não quiser conversar com a pessoa por enquanto.</p>
<p>You can also <strong>delete</strong> it if you dont want to chat with them for now.</p>
</li>
<li>
<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>
<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 dont want to
receive messages from this person, consider <strong>blocking</strong> them.</p>
</li>
</ul>
<h3 id="como-posso-colocar-dois-dos-meus-amigos-em-contato-um-com-o-outro">
<h3 id="how-can-i-put-two-of-my-friends-in-contact-with-each-other">
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>
How can I put two of my friends in contact with each other? <a href="#how-can-i-put-two-of-my-friends-in-contact-with-each-other" class="anchor"></a>
</h3>
<p>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>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>O segundo contato receberá um <strong>cartão</strong>
e poderá tocar nele para começar a conversar com o primeiro contato.</p>
<p>The second contact will receive a <strong>card</strong> then
and can tap it to start chatting with the first contact.</p>
<h3 id="dá-para-mandar-imagens-vídeos-e-outros-anexos-pelo-delta-chat">
@@ -226,31 +223,36 @@ e poderá tocar nele para começar a conversar com o primeiro contato.</p>
</h3>
<p>Sim. Imagens, vídeos, arquivos, mensagens de voz etc. podem ser enviados usando os botões de<img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Anexo-</strong>
ou <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Mensagem de Voz</strong></p>
<p>Para fins de desempenho, as imagens são otimizadas e enviadas em um tamanho menor por padrão, mas você pode enviá-las como um “arquivo” para preservar o original.</p>
<ul>
<li>
<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>
</li>
<li>
<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>
</li>
</ul>
<h3 id="multiple-accounts">
O que são perfis? Como posso alternar entre eles? <a href="#multiple-accounts" class="anchor"></a>
What are profiles? How can I switch between them? <a href="#multiple-accounts" class="anchor"></a>
</h3>
<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>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>Na primeira instalação do Delta Chat, é criado um primeiro perfil.</p>
<p>On first installation of Delta Chat a first profile is created.</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>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>Talvez você queira usar perfis separados para atividades políticas, familiares ou atividades do trabalho.</p>
<p>You may want to use separate profiles for political, family or work related activities.</p>
<p>Talvez você também queira aprender <a href="#multiclient">como usar a mesma conta em vários dispositivos</a>.</p>
<p>You may also wish to learn <a href="#multiclient">how to use the same profile on multiple devices</a>.</p>
<h3 id="quem-consegue-ver-a-imagem-do-meu-perfil">
@@ -260,103 +262,107 @@ ou para <strong>Mudar de Conta</strong>.</p>
</h3>
<p>Você pode adicionar uma imagem de perfil nas suas configurações. Se você escrever aos seus contatos ou adicioná-los via código QR, eles automaticamente verão a imagem do seu perfil.</p>
<p>Por motivos de privacidade, ninguém pode ver a imagem do seu ṕerfil até que você escreva para as pessoas.</p>
<ul>
<li>
<p>Você pode adicionar uma imagem de perfil nas suas configurações. Se você escrever aos seus contatos ou adicioná-los via código QR, eles automaticamente verão a imagem do seu perfil.</p>
</li>
<li>
<p>Por motivos de privacidade, ninguém pode ver a imagem do seu ṕerfil até que você escreva para as pessoas.</p>
</li>
</ul>
<h3 id="signature">
Posso definir uma Bio/Assinatura com o Delta Chat? <a href="#signature" class="anchor"></a>
Can I set a Bio/Status with Delta Chat? <a href="#signature" class="anchor"></a>
</h3>
<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>
<p>Yes,
you can do so under <strong>Settings → Profile → Bio</strong>.
Once you sent a message to a contact,
they will see it when they view your contact details.</p>
<h3 id="o-que-significa-fixar-silenciar-e-arquivar">
<h3 id="what-do-pinning-muting-and-archiving-mean">
O que significa Fixar, Silenciar e Arquivar? <a href="#o-que-significa-fixar-silenciar-e-arquivar" class="anchor"></a>
What do Pinning, Muting and Archiving mean? <a href="#what-do-pinning-muting-and-archiving-mean" class="anchor"></a>
</h3>
<p>Use essas ferramentas para organizar seus chats e manter tudo em seu devido lugar:</p>
<p>Use these tools to organize your chats and keep everything in its place:</p>
<ul>
<li>
<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>
<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>
</li>
<li>
<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>
<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>
</li>
<li>
<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>
<p><strong>Archive chats</strong> if you do not want to see them in your chat list any longer.
Archived chats remain accessible above the chat list or via search.</p>
</li>
<li>
<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>
<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>
</li>
</ul>
<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>
<p>To use the functions, long tap or right click a chat in the chat list.</p>
<h3 id="save">
Como funcionam as “Mensagens salvas”? <a href="#save" class="anchor"></a>
How do “Saved Messages” work? <a href="#save" class="anchor"></a>
</h3>
<p><strong>Saved Messages</strong> é um chat que você pode usar para lembrar e localizar facilmente as mensagens.</p>
<p><strong>Saved Messages</strong> is a chat that you can use to easily remember and find messages.</p>
<ul>
<li>
<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>
<p>In any chat, long tap or right click a message and select <strong>Save</strong></p>
</li>
<li>
<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>
<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>
</li>
<li>
<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>
<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>
</li>
<li>
<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>
<p>Finally, you can also use “Save Messages” to take <strong>personal notes</strong> - open the chat, type something, add a photo or a voice message etc.</p>
</li>
<li>
<p>Como as “Mensagens salvas” são sincronizadas, elas podem se tornar muito úteis para a transferência de dados entre dispositivos</p>
<p>As “Saved Message” are synced, they can become very handy for transferring data between devices</p>
</li>
</ul>
<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>
<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>
<h3 id="o-que-significa-o-ponto-verde">
<h3 id="what-does-the-green-dot-mean">
O que significa o ponto verde? <a href="#o-que-significa-o-ponto-verde" class="anchor"></a>
What does the green dot mean? <a href="#what-does-the-green-dot-mean" class="anchor"></a>
</h3>
<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>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>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>
<p>So this is not a real time online status
and others will as well not always see that you are “online”.</p>
<h3 id="o-que-significam-os-carrapatos-mostrados-ao-lado-das-mensagens-de-saída">
@@ -368,131 +374,137 @@ e outras pessoas também nem sempre verão que você está “on-line”.</p>
<ul>
<li>
<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>
<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 your provider.</p>
</li>
<li>
<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>
<p><strong>Two ticks</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick2.png" alt="" />
mean that at least one recipients device
reported back to having received the message.</p>
</li>
<li>
<p>Recipients may have disabled read-receipts,
so even if you see only one tick, the message may have been read.</p>
</li>
<li>
<p>The other way round, two ticks do not automatically mean
that a human has read or understood the message ;)</p>
</li>
</ul>
<p>Em <a href="#groups">grupos</a>, o segundo tique significa que pelo menos um membro leu a mensagem.</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">
Corrigir erros de digitação e excluir mensagens após o envio <a href="#edit" class="anchor"></a>
Correct typos and delete messages after sending <a href="#edit" class="anchor"></a>
</h3>
<ul>
<li>
<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>
<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>
</li>
<li>
<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>
<p>If you have sent a message accidentally,
from the same menu, select <strong>Delete</strong> and then <strong>Delete for Everyone</strong>.</p>
</li>
</ul>
<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>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>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>
<p>Note, that the original message may still be received by chat members
who could have already replied, forwarded, saved, screenshotted or otherwise copied the message.</p>
<h3 id="ephemeralmsgs">
Como funcionam as mensagens efêmeras? <a href="#ephemeralmsgs" class="anchor"></a>
How do disappearing messages work? <a href="#ephemeralmsgs" class="anchor"></a>
</h3>
<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>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>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>Until the setting is turned off again,
each chat members 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>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>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>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>
<p>Apart from that,
if one chat partner uninstalls Delta Chat,
the (anyway encrypted) messages may take longer to get deleted from their server.</p>
<h3 id="delold">
O que acontece se eu ativar a opção “Apagar mensagens do dispositivo”? <a href="#delold" class="anchor"></a>
What happens if I turn on “Delete old messages from device”? <a href="#delold" class="anchor"></a>
</h3>
<p>Se quiser economizar espaço de armazenamento no dispositivo, você pode optar por excluir mensagens antigas automaticamente.</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>
<ul>
<li>If you want to save storage on your device, you can choose to delete old
messages automatically.</li>
<li>To turn it on, go to delete old messages from device” in the “Chats &amp; Media”
settings. 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.</li>
</ul>
<h3 id="remove-account">
Como posso excluir minha conta? <a href="#remove-account" class="anchor"></a>
How can I delete my chat profile? <a href="#remove-account" class="anchor"></a>
</h3>
<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 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 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>
<p>If you use a single default chat profile you can simply uninstall the app.
This will still automatically trigger deletion of all associated address data on the chatmail server.
For more info, please refer to <a href="https://nine.testrun.org/info.html#account-deletion">nine.testrun.org address-deletion</a>
or the respective page from your chosen <a href="https://chatmail.at/relays">3rd party chatmail server</a>.</p>
<h2 id="groups">
Grupos <a href="#groups" class="anchor"></a>
Groups <a href="#groups" class="anchor"></a>
</h2>
<p>Os grupos permitem que várias pessoas conversem em particular com <strong>direitos iguais</strong>.</p>
<p>Groups let several people chat together privately with <strong>equal rights</strong>.</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>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 members devices.</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>
<p>Because all members have the same rights, groups work best among <strong>trusted friends and family</strong>.</p>
<h3 id="criação-de-um-grupo">
@@ -503,47 +515,42 @@ e <a href="#edit">excluir suas próprias mensagens</a> dos dispositivos de todos
</h3>
<ul>
<li>
<p>Selecione <strong>Nova Conversa</strong> e em seguida <strong>Novo Grupo</strong> no menu que fica na parte de cima da tela, no canto direito, ou clique no botão correspondente no ANdroid/iOS.</p>
</li>
<li>
<p>Na tela seguinte, selecione <strong>os membros do grupo</strong> e defina o <strong>nome do grupo</strong>. Você também pode selecionar o <strong>avatar do grupo</strong> (uma imagem).</p>
</li>
<li>
<p>Logo após você escrever a <strong>primeira mensagem</strong>, todas as pessoas do grupo serão informadas sobre o novo grupo e poderão responder no grupo (a não que você escreva uma mensagem ali, o grupo estará invisível para os membros).</p>
</li>
<li>Selecione <strong>Nova Conversa</strong> e em seguida <strong>Novo Grupo</strong> no menu que fica na parte de cima da tela, no canto direito, ou clique no botão correspondente no ANdroid/iOS.</li>
<li>Na tela seguinte, selecione <strong>os membros do grupo</strong> e defina o <strong>nome do grupo</strong>. Você também pode selecionar o <strong>avatar do grupo</strong> (uma imagem).</li>
<li>Logo após você escrever a <strong>primeira mensagem</strong>, todas as pessoas do grupo serão informadas sobre o novo grupo e poderão responder no grupo (a não que você escreva uma mensagem ali, o grupo estará invisível para os membros).</li>
</ul>
<h3 id="addmembers">
Adicionar e remover membros <a href="#addmembers" class="anchor"></a>
Add and remove members <a href="#addmembers" class="anchor"></a>
</h3>
<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>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>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
</li>
<li>
<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>
<p>To <strong>add or delete members</strong>, tap the group name in the chat and select the member to add or remove.</p>
</li>
<li>
<p>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>
<p>If the member is not yet in your contact list, but <strong>face to face</strong> with you,
from the same screen, show a <strong>QR code</strong>.<br />
Ask your chat partner to <strong>scan</strong> the QR image with their Delta Chat app by tapping
<img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> on the main screen.</p>
</li>
<li>
<p>For a <strong>remote</strong> member addition,
click “Copy” or “Share” and send the <strong>invite link</strong>
through another private chat to the new member.</p>
</li>
</ul>
<p>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>
<p>QR code and invite link can be used to add several members.
However, since groups are <a href="#groups">meant for trusted people</a>, avoid sharing them publicly.</p>
<h3 id="deletei-minha-própria-conta-por-acidente">
@@ -553,7 +560,9 @@ No entanto, como os grupos são <a href="#groups">destinados a pessoas de confia
</h3>
<p>Já que você não é mais um membro do grupo, não tem como se adicionar novamente. Entretanto, não tem problema, é só pedir para outra pessoa do grupo, através de um chat normal, adicionar você.</p>
<ul>
<li>Já que você não é mais um membro do grupo, não tem como se adicionar novamente. Entretanto, não tem problema, é só pedir para outra pessoa do grupo, através de um chat normal, adicionar você.</li>
</ul>
<h3 id="não-quero-mais-receber-as-mensagens-de-um-grupo">
@@ -564,66 +573,54 @@ No entanto, como os grupos são <a href="#groups">destinados a pessoas de confia
</h3>
<ul>
<li>Ou você se exclui do grupo ou apaga a conversa inteira do grupo.
Se você quiser entrar mais tarde no grupo novamente, peça a outra pessoa do grupo para adicioná-la novamente.</li>
<li>
<p>Ou você se exclui do grupo ou apaga a conversa inteira do grupo.
Se você quiser entrar mais tarde no grupo novamente, peça a outra pessoa do grupo para adicioná-la novamente.</p>
</li>
<li>
<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>
</li>
</ul>
<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="clonagem-de-grupos">
<h3 id="cloning-a-group">
Clonagem de grupos <a href="#clonagem-de-grupos" class="anchor"></a>
Cloning a group <a href="#cloning-a-group" class="anchor"></a>
</h3>
<p>Você pode duplicar um grupo para iniciar uma discussão separada
ou para excluir membros sem que eles percebam.</p>
<p>You can duplicate a group to start a separate discussion
or to exclude members without them noticing.</p>
<ul>
<li>
<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>
<p>Open the group profile and tap <strong>Clone Chat</strong> (Android/iOS),
or right-click the group in the chat list (Desktop).</p>
</li>
<li>
<p>Defina um novo nome, escolha um avatar e ajuste a lista de membros, se necessário.</p>
<p>Set a new name, choose an avatar, and adjust the member list if needed.</p>
</li>
</ul>
<p>O novo grupo é <strong>totalmente independente</strong> do original,
que continua funcionando como antes.</p>
<h3 id="quantos-membros-podem-participar-em-um-único-grupo">
Quantos membros podem participar em um único grupo? <a href="#quantos-membros-podem-participar-em-um-único-grupo" class="anchor"></a>
</h3>
<p>Não há um limite técnico rigoroso,
mas não é recomendável mais de 150.</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>
<p>The new group is <strong>fully independent</strong> from the original,
which continues to work as before.</p>
<h2 id="webxdc">
Aplicativos embutidos <a href="#webxdc" class="anchor"></a>
In-chat apps <a href="#webxdc" class="anchor"></a>
</h2>
<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>
<p>You can send apps to a chat - games, editors, polls and other tools.
This makes Delta Chat a truly extensible messenger.</p>
<h3 id="onde-posso-obter-aplicativos-embutidos">
<h3 id="where-can-i-get-in-chat-apps">
Onde posso obter aplicativos embutidos? <a href="#onde-posso-obter-aplicativos-embutidos" class="anchor"></a>
Where can I get in-chat apps? <a href="#where-can-i-get-in-chat-apps" class="anchor"></a>
</h3>
@@ -897,7 +894,7 @@ One device is not needed for the other to work.</p>
<p>Double-check both devices are in the <strong>same Wi-Fi or network</strong></p>
</li>
<li>
<p>On <strong>Windows</strong>, go to Control Panel / Network and Internet
<p>On <strong>Windows</strong>, go to <strong>Control Panel / Network and Internet</strong>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
@@ -992,10 +989,10 @@ ou o AppImage para Linux. Você pode encontrá-los em
</h2>
<h3 id="experiments">
<h3 id="experimental-features">
Experimental Features <a href="#experiments" class="anchor"></a>
Experimental Features <a href="#experimental-features" class="anchor"></a>
</h3>
@@ -1031,7 +1028,7 @@ you can configure relays at At <strong>Settings → Advanced → Relays</strong>
<li>
<p>You can <strong>add</strong> a relay by scanning its QR code;
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
If you have multiple relays, you will receive messages on all of them.</p>
If you have multiple relays, your will receive messages on all of them.</p>
</li>
<li>
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
@@ -1154,7 +1151,9 @@ weekly statistics will be automatically sent to a bot.</p>
</h3>
<p>Veja <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">As normas usadas no Delta Chat</a>.</p>
<ul>
<li>Veja <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">As normas usadas no Delta Chat</a>.</li>
</ul>
<h2 id="e2ee">
@@ -1393,32 +1392,6 @@ 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>
<h3 id="who-sees-my-ip-address">
Who sees my IP Address? <a href="#who-sees-my-ip-address" class="anchor"></a>
</h3>
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
as well as sometimes your contacts devices if you have a <a href="#experiments">call</a>
or use <a href="#webxdc">apps</a> together.</p>
<p>IP Addresses are needed for connectivity and efficiency.
They are neither persisted nor exposed.
Note that the IP Address
is not like a detailed address you give to a delivery service,
but much more coarse, often defining region or country only.</p>
<p>As this is just how the internet and other messengers work by default,
we do not offer options here or ask upfront questions.</p>
<p>If you see your IP Address as a security or privacy risk,
we recommend to use a VPN, in combination with system lockdown mode.
Hunting down options in all apps on your system will leave gaps.
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
<h3 id="sealedsender">
@@ -1658,38 +1631,52 @@ ordered chronologically:</p>
<ul>
<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>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>
<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>
</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>
<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>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>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>
</li>
<li>
<p>Sometimes we receive one-time donations from private individuals.
+105 -123
View File
@@ -15,7 +15,7 @@
<li><a href="#что-означают-галочки-рядом-с-исходящими-сообщениями">Что означают галочки рядом с исходящими сообщениями?</a></li>
<li><a href="#edit">Исправление опечаток и удаление сообщений после отправки</a></li>
<li><a href="#ephemeralmsgs">Как работают исчезающие сообщения?</a></li>
<li><a href="#delold">Что произойдет, если я включу функцию “Удалять сообщения с устройства”?</a></li>
<li><a href="#delold">Что произойдет, если я включу функцию “Удалять старые сообщения с устройства”?</a></li>
<li><a href="#remove-account">Как удалить свой профиль в чате?</a></li>
</ul>
</li>
@@ -26,7 +26,6 @@
<li><a href="#я-случайно-удалил-самого-себя">Я случайно удалил самого себя.</a></li>
<li><a href="#я-больше-не-хочу-получать-сообщения-группы">Я больше не хочу получать сообщения группы.</a></li>
<li><a href="#клонирование-группы">Клонирование группы</a></li>
<li><a href="#сколько-участников-может-быть-в-одной-группе">Сколько участников может быть в одной группе?</a></li>
</ul>
</li>
<li><a href="#webxdc">Встроенные приложения чата</a>
@@ -55,7 +54,7 @@
</li>
<li><a href="#расширенные">Расширенные</a>
<ul>
<li><a href="#experiments">Экспериментальные функции</a></li>
<li><a href="#экспериментальные-функции">Экспериментальные функции</a></li>
<li><a href="#relays">Что такое релеи chatmail?</a></li>
<li><a href="#могу-ли-я-использовать-обычный-адрес-электронной-почты-с-delta-chat">Могу ли я использовать обычный адрес электронной почты с Delta Chat?</a></li>
<li><a href="#classic-email">Как настроить профиль чата с использованием классического адреса электронной почты в качестве релея?</a></li>
@@ -77,7 +76,6 @@
<li><a href="#tls">Видны ли в Интернете сообщения, отмеченные значком почты?</a></li>
<li><a href="#message-metadata">Как Delta Chat защищает метаданные в сообщениях?</a></li>
<li><a href="#device-seizure">Как защитить метаданные и контакты при изъятии устройства?</a></li>
<li><a href="#кто-видит-мой-ip-адрес">Кто видит мой IP-адрес?</a></li>
<li><a href="#sealedsender">Поддерживает ли Delta Chat функцию “Sealed Sender” (Засекреченный отправитель)?</a></li>
<li><a href="#pfs">Поддерживает ли Delta Chat свойство Perfect forward secrecy, PFS (Совершенную прямую секретность)?</a></li>
<li><a href="#pqc">Поддерживает ли Delta Chat Post-Quantum-Cryptography (Постквантовую криптографию)?</a></li>
@@ -187,8 +185,7 @@
<p>Поскольку это приватный мессенджер,
писать вам могут только друзья и члены семьи, с которыми вы <a href="#howtoe2ee">поделились QR-кодом или ссылкой-приглашением.</a></p>
<p>Ваши друзья могут поделиться вашим контактом с другими друзьями,
это отображается как <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Запрос</b></p>
<p>Ваши друзья могут поделиться вашим контактом с другими друзьями, это отображается как <strong>запрос</strong>.</p>
<p>— Нужно <strong>принять</strong> запрос, прежде чем ответить.</p>
@@ -222,10 +219,15 @@
</h3>
<p>Да. Изображения, видео, файлы, голосовые сообщения и т.д. можно отправлять с помощью кнопок <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Вложение</strong>
<ul>
<li>
<p>Да. Изображения, видео, файлы, голосовые сообщения и т.д. можно отправлять с помощью кнопок <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Вложение</strong>
или <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Голосовое сообщение</strong>.</p>
<p>Для повышения производительности, изображения оптимизируются и отправляются по умолчанию в уменьшенном размере, но вы можете отправить их как “файл”, чтобы сохранить оригинал.</p>
</li>
<li>
<p>Для лучшей производительности изображения по умолчанию оптимизируются и отправляются в меньшем размере, но вы можете отправить их как “файл”, чтобы сохранить оригинал.</p>
</li>
</ul>
<h3 id="multiple-accounts">
@@ -256,11 +258,16 @@
</h3>
<p>Вы можете добавить изображение профиля в настройках. Если вы пишете своим контактам
<ul>
<li>
<p>Вы можете добавить изображение профиля в настройках. Если вы пишете своим контактам
или добавляете их с помощью QR-кода, они автоматически видят его как изображение вашего профиля.</p>
<p>По соображениям конфиденциальности, никто не увидит изображение вашего профиля, пока вы не напишете
им сообщение.</p>
</li>
<li>
<p>По соображениям конфиденциальности, никто не увидит изображение вашего профиля,
пока вы не напишете им сообщение.</p>
</li>
</ul>
<h3 id="signature">
@@ -294,8 +301,7 @@
</li>
<li>
<p><strong>Отправить в архив</strong> необходимо, если вы не хотите больше видеть их в списке чатов.
Архивные чаты остаются доступными над списком чатов или через поиск
и будут отмечены как <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Архивировано</b></p>
Архивные чаты остаются доступными над списком чатов или через поиск.</p>
</li>
<li>
<p>Когда в чат, находящийся в архиве, приходит новое сообщение, если не включена опция <strong>Отключить уведомления</strong>, он <strong>Возвращается из архива</strong> в ваш список чатов.
@@ -330,7 +336,7 @@
вы можете вернуться к этому сообщению в исходном чате</p>
</li>
<li>
<p>Наконец, вы можете использовать “Сохраненные сообщения”, для создания <strong>личных заметок</strong> - откройте чат, напечатайте что-нибудь, добавьте фото или голосовое сообщение и т.д.</p>
<p>Наконец, вы также можете использовать “Сохраненные сообщения” для создания <strong>личных заметок</strong> - откройте чат, введите что-то, добавьте фото или голосовое сообщение и т.д.</p>
</li>
<li>
<p>Поскольку “Сохраненные сообщения” синхронизируются, они могут стать удобным способом передачи данных между устройствами</p>
@@ -366,18 +372,22 @@
<ul>
<li>
<p><strong>Одна галочка</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick1.png" alt="" />
означает, что сообщение успешно отправлено на <a href="#relays">релей</a>.</p>
означает, что сообщение было успешно отправлено вашему провайдеру.</p>
</li>
<li>
<p><strong>Две галочки</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick2.png" alt="" />
указывают на то, что ваш контакт прочитал сообщение.</p>
означают, что по крайней мере одно устройство получателя
сообщило об успешном получении сообщения.</p>
</li>
<li>
<p>Получатели могли отключить подтверждения прочтения,
поэтому даже если вы видите только одну галочку, сообщение могло быть прочитано.</p>
</li>
<li>
<p>И наоборот, две галочки не обязательно означают
что человек прочитал или понял сообщение ;)</p>
</li>
</ul>
<p>В <a href="#groups">группах</a> вторая галочка означает, что хотя бы один из участников подтвердил прочтение сообщения.</p>
<p>Вторая галочка появится только в том случае, если у вас и хотя бы одного из получателей, прочитавшего сообщение,
включена опция <strong>Настройки → Чаты → Уведомление о прочтении</strong>.</p>
<h3 id="edit">
@@ -442,18 +452,18 @@
<h3 id="delold">
Что произойдет, если я включу функцию “Удалять сообщения с устройства”? <a href="#delold" class="anchor"></a>
Что произойдет, если я включу функцию “Удалять старые сообщения с устройства”? <a href="#delold" class="anchor"></a>
</h3>
<p>Если вы хотите сэкономить место на устройстве, можно выбрать функцию автоматического удаления старых
сообщений.</p>
<p>Чтобы включить эту функцию, перейдите в <strong>Настройки → Чаты → Удалять сообщения с устройства</strong>.
Вы можете установить временные рамки от “через 1 час” до “через 1 год”;
Таким образом, <em>все</em> сообщения будут удаляться с вашего устройства, как только они станут
старше выбранного срока.</p>
<ul>
<li>Если вы хотите сэкономить место на устройстве, вы можете выбрать
автоматическое удаление старых сообщений.</li>
<li>Чтобы включить эту функцию, перейдите в Удалять сообщения с устройства” в настройках “Чаты и медиафайлы”
Вы можете установить период от “Через 1 час” до “Через 1 год”;
Таким образом, <em>все</em> сообщения будут удалены с устройства, как только они станут старше выбранного срока.</li>
</ul>
<h3 id="remove-account">
@@ -501,15 +511,9 @@
</h3>
<ul>
<li>
<p>Выберите <strong>Новый чат</strong>, а затем <strong>Новая группа</strong> из меню в правом верхнем углу или нажмите соответствующую кнопку на Android/iOS.</p>
</li>
<li>
<p>На следующем экране выберите <strong>участников группы</strong> и придумайте <strong>название группы</strong>. Вы также можете выбрать <strong>аватар группы</strong>.</p>
</li>
<li>
<p>Как только вы напишете <strong>первое сообщение</strong> в группе, все участники будут проинформированы о новой группе и смогут ответить. (Пока вы не напишете сообщение в группе, группа будет невидима для участников).</p>
</li>
<li>Выберите <strong>Новый чат</strong>, а затем <strong>Новая группа</strong> из меню в правом верхнем углу или нажмите соответствующую кнопку на Android/iOS.</li>
<li>На следующем экране выберите <strong>участников</strong> и придумайте <strong>название группы</strong>. Вы также можете выбрать <strong>изображение группы</strong>.</li>
<li>Как только вы напишете <strong>первое сообщение</strong> в группе, все участники будут проинформированы о новой группе и смогут ответить. (Пока вы не напишете сообщение в группе, группа будет невидима для участников).</li>
</ul>
<h3 id="addmembers">
@@ -520,10 +524,11 @@
</h3>
<p>Все участники группы имеют <strong>одинаковые права</strong>.
Поэтому каждый может удалить любого участника или добавить нового.</p>
<ul>
<li>
<p>У всех участников группы <strong>одинаковые права</strong>.
Поэтому каждый может удалить любого участника или добавить новых.</p>
</li>
<li>
<p>Чтобы <strong>добавлять или удалять участников</strong>, коснитесь названия группы в чате и выберите участника, которого нужно добавить или удалить.</p>
</li>
@@ -551,8 +556,10 @@
</h3>
<p>Поскольку вы больше не являетесь участником группы, вы не можете добавить себя снова.
Однако, это не проблема, просто попросите любого другого участника группы в обычном чате добавить вас снова.</p>
<ul>
<li>Поскольку вы больше не являетесь участником группы, вы не можете добавлять себя снова.
Однако, это не проблема, просто попросите любого другого участника группы в обычном чате добавить вас снова.</li>
</ul>
<h3 id="я-больше-не-хочу-получать-сообщения-группы">
@@ -563,12 +570,14 @@
</h3>
<ul>
<li>Либо удалите себя из списка участников, либо удалите весь чат.
Если позже вы снова захотите присоединиться к группе, попросите другого участника группы добавить вас.</li>
<li>
<p>Либо удалите себя из списка участников, либо удалите весь чат.
Если позже вы снова захотите присоединиться к группе, попросите другого участника группы добавить вас.</p>
</li>
<li>
<p>Или, вместо этого, вы можете “отключить уведомления” для группы — это означает, что вы будете получать все сообщения и сможете их писать, но больше не будете получать уведомления о новых сообщениях.</p>
</li>
</ul>
<p>Или, вместо этого, вы можете “отключить уведомления” для группы - в этом случае вы будете получать все сообщения и
можете их писать, но больше не будете получать уведомления о новых сообщениях.</p>
<h3 id="клонирование-группы">
@@ -594,21 +603,6 @@
<p>Новая группа <strong>полностью независима</strong> от исходной,
которая продолжает работать как прежде.</p>
<h3 id="сколько-участников-может-быть-в-одной-группе">
Сколько участников может быть в одной группе? <a href="#сколько-участников-может-быть-в-одной-группе" class="anchor"></a>
</h3>
<p>Строгого технического ограничения нет,
но не рекомендуется создавать группы больше 150 участников.</p>
<p>По мере увеличения размера групп они могут стать социально нестабильными и потребовать иерархии,
в то время как Delta Chat - это приватный мессенджер для общения на <a href="#groups">равных правах</a>.
Смотрите <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">число Данбара</a> для более глубокого понимания.</p>
<h2 id="webxdc">
@@ -896,9 +890,9 @@ Push-уведомления автоматически активируются
<p>Перепроверьте, что оба устройства находятся <strong>в одной Wi-Fi или локальной сети</strong>.</p>
</li>
<li>
<p>В <strong>Windows</strong> перейдите в Панель управления / Сеть и Интернет
<p>В <strong>Windows</strong> перейдите в <strong>Панель управления / Сеть и Интернет</strong>
и убедитесь, что в качестве “Типа сетевого профиля” выбрана <strong>Частная сеть</strong>.
(после передачи можно вернуть исходное значение)</p>
(после передачи, вы можете изменить обратно на исходное значение)</p>
</li>
<li>
<p>На <strong>iOS</strong>, убедитесь, что предоставлен доступ “Настройки системы / Приложения / Delta Chat / <strong>Локальная сеть</strong></p>
@@ -991,10 +985,10 @@ PIN-код разблокировки экрана, графический кл
</h2>
<h3 id="experiments">
<h3 id="экспериментальные-функции">
Экспериментальные функции <a href="#experiments" class="anchor"></a>
Экспериментальные функции <a href="#экспериментальные-функции" class="anchor"></a>
</h3>
@@ -1152,7 +1146,9 @@ Chatmail использует INBOX по умолчанию для ретран
</h3>
<p>Смотрите <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Стандарты, используемые в Delta Chat</a>.</p>
<ul>
<li>Смотрите <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Стандарты, используемые в Delta Chat</a>.</li>
</ul>
<h2 id="e2ee">
@@ -1391,34 +1387,6 @@ Delta Chat вместо этого использует реализацию Ope
Кроме того, если устройство изъято, контакты, использующие временные профили,
не могут быть легко идентифицированы.</p>
<h3 id="кто-видит-мой-ip-адрес">
Кто видит мой IP-адрес? <a href="#кто-видит-мой-ip-адрес" class="anchor"></a>
</h3>
<p>Используемый <a href="#relays">релей</a> должен знать ваш IP-адрес,
а также иногда устройства ваших контактов, если вы проводите совместные <a href="#experiments">звонки</a>
или используете <a href="#webxdc">приложения</a>.</p>
<p>IP-адреса необходимы для обеспечения соединения и эффективности.
Они не сохраняются и не передаются третьим лицам.
Обратите внимание, что IP-адрес</p>
<ul>
<li>это не подробный адрес, который вы указываете службе доставки,
а скорее приблизительный, обычно определяющий регион или страну.</li>
</ul>
<p>Поскольку именно так по умолчанию работает интернет и другие мессенджеры,
мы не предлагаем здесь никаких настроек и не задаём предварительных вопросов</p>
<p>Если вы считаете свой IP-адрес угрозой безопасности или конфиденциальности,
мы рекомендуем использовать VPN в сочетании с режимом блокировки системы.
Поиск настроек во всех приложениях на вашем устройстве оставит уязвимости.
Например, нажатие на ссылку раскрывает IP-адрес неизвестным лицам и представляет собой гораздо больший риск в данном случае.</p>
<h3 id="sealedsender">
@@ -1427,7 +1395,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 +1416,7 @@ Delta Chat вместо этого использует реализацию Ope
</h3>
<p>Нет, еще нет.</p>
<p>Нет, пока нет.</p>
<p>На данный момент, Delta Chat не поддерживает Perfect Forward Secrecy (PFS) (Совершенную прямую секретность).
Это означает, что если ваш приватный ключ дешифрования будет скомпрометирован,
@@ -1474,7 +1442,7 @@ Delta Chat вместо этого использует реализацию Ope
</h3>
<p>Нет, еще нет.</p>
<p>Нет, пока нет.</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>.
@@ -1658,38 +1626,52 @@ Google Play Store, F-Droid, Huawei App Gallery, iOS и macOS App Store, Microsof
<ul>
<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>Проект ЕС <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">Open Technology Fund</a> предоставил нам
первый грант в 2018/2019 году (~$200 тыс.), благодаря которому мы существенно улучшили приложение для Android
и выпустили первую бета-версию приложения для настольных систем, а также провели
исследования в области UX в контексте прав человека,
см. наш заключительный отчет <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
Второй грант, полученный в 2019/2020 году (~$300 тыс.), помог нам
выпустить версии Delta/iOS, перевести наш основной код на Rust и
предоставить новые функции для всех платформ.</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>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>С конца 2021 года по март 2023 года мы получили финансирование в размере ($500 тыс.) от
U.S. Bureau of Democracy, Human Rights and Labor (DRL) для поддержки <em>свободы интернета</em>.
Это финансирование поддержало наши долгосрочные цели, сделать Delta Chat более удобным для использования
и совместимым с широким спектром электронных почтовых серверов по всему миру, а также более устойчивым
и безопасным в местах, часто подвергающихся интернет-цензуре и отключениям.</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>
<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>Проект ЕС <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>Иногда мы получаем разовые пожертвования от физических лиц.
+102 -116
View File
@@ -15,7 +15,7 @@
<li><a href="#čo-znamenajú-zaškrtnutia-zobrazené-vedľa-odchádzajúcich-správ">Čo znamenajú zaškrtnutia zobrazené vedľa odchádzajúcich správ?</a></li>
<li><a href="#edit">Correct typos and delete messages after sending</a></li>
<li><a href="#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="#delold">What happens if I turn on “Delete old messages from device”?</a></li>
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
</ul>
</li>
@@ -26,7 +26,6 @@
<li><a href="#omylom-som-sa-vymazal">Omylom som sa vymazal.</a></li>
<li><a href="#už-viac-nechcem-dostávať-správy-od-skupiny">Už viac nechcem dostávať správy od skupiny.</a></li>
<li><a href="#cloning-a-group">Cloning a group</a></li>
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
</ul>
</li>
<li><a href="#webxdc">In-chat apps</a>
@@ -55,7 +54,7 @@
</li>
<li><a href="#advanced">Advanced</a>
<ul>
<li><a href="#experiments">Experimental Features</a></li>
<li><a href="#experimental-features">Experimental Features</a></li>
<li><a href="#relays">What are Relays?</a></li>
<li><a href="#can-i-use-a-classic-email-address-with-delta-chat">Can I use a classic email address with Delta Chat?</a></li>
<li><a href="#classic-email">How can I configure a chat profile with a classic email address as relay?</a></li>
@@ -77,7 +76,6 @@
<li><a href="#tls">Are messages marked with the mail icon exposed on the Internet?</a></li>
<li><a href="#message-metadata">How does Delta Chat protect metadata in messages?</a></li>
<li><a href="#device-seizure">How to protect metadata and contacts when a device is seized?</a></li>
<li><a href="#who-sees-my-ip-address">Who sees my IP Address?</a></li>
<li><a href="#sealedsender">Does Delta Chat support “Sealed Sender”?</a></li>
<li><a href="#pfs">Does Delta Chat support Perfect Forward Secrecy?</a></li>
<li><a href="#pqc">Does Delta Chat support Post-Quantum-Cryptography?</a></li>
@@ -187,8 +185,7 @@ If you add each other to <a href="#groups">groups</a>, end-to-end encryption wil
<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>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>Your friends may share your contact with other friends, this appears as a <strong>request</strong>.</p>
<ul>
<li>
@@ -226,10 +223,15 @@ and can tap it to start chatting with the first contact.</p>
</h3>
<p>Yes. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
<ul>
<li>
<p>Yes. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
<p>For performance, images are optimized and sent at a smaller size by default, but you can send it as a “file” to preserve the original.</p>
</li>
<li>
<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>
</li>
</ul>
<h3 id="multiple-accounts">
@@ -260,11 +262,16 @@ or to <strong>Switch Profiles</strong>.</p>
</h3>
<p>V nastaveniach si môžete pridať profilový obrázok. Ak napíšete svojim kontaktom
<ul>
<li>
<p>V nastaveniach si môžete pridať profilový obrázok. Ak napíšete svojim kontaktom
alebo si ich pridáte pomocou QR kódu, automaticky to vidia ako váš profilový obrázok.</p>
<p>Z dôvodu ochrany osobných údajov nikto nevidí váš profilový obrázok, kým im nenapíšete
</li>
<li>
<p>Z dôvodu ochrany osobných údajov nikto nevidí váš profilový obrázok, kým im nenapíšete
správu.</p>
</li>
</ul>
<h3 id="signature">
@@ -298,8 +305,7 @@ they will see it when they view your contact details.</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>
Archived chats remain accessible above the chat list or via search.</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.
@@ -334,7 +340,7 @@ By tapping <img style="vertical-align:middle; width:1.2em; margin:1px" src="../g
you can go back to the original message in the original chat</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>Finally, you can also use “Save Messages” to take <strong>personal notes</strong> - open the chat, type something, add a photo or a voice message etc.</p>
</li>
<li>
<p>As “Saved Message” are synced, they can become very handy for transferring data between devices</p>
@@ -371,18 +377,22 @@ 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>
means that the message was sent successfully to your provider.</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>
mean that at least one recipients device
reported back to having received the message.</p>
</li>
<li>
<p>Recipients may have disabled read-receipts,
so even if you see only one tick, the message may have been read.</p>
</li>
<li>
<p>The other way round, two ticks do not automatically mean
that a human has read or understood the message ;)</p>
</li>
</ul>
<p>In <a href="#groups">groups</a> the second tick means that at least one member has reported back having read the message.</p>
<p>You will only get the second tick if both you and one of the recipients who read the message
has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
<h3 id="edit">
@@ -447,18 +457,19 @@ the (anyway encrypted) messages may take longer to get deleted from their server
<h3 id="delold">
What happens if I turn on “Delete Messages from Device”? <a href="#delold" class="anchor"></a>
What happens if I turn on “Delete old messages from device”? <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>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”;
<ul>
<li>If you want to save storage on your device, you can choose to delete old
messages automatically.</li>
<li>To turn it on, go to “delete old messages from device” in the “Chats &amp; Media”
settings. 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>
older than that.</li>
</ul>
<h3 id="remove-account">
@@ -506,15 +517,9 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<ul>
<li>
<p>Vyberte <strong>Nový chat</strong> a potom <strong>Nová skupina</strong> z ponuky v pravom hornom rohu alebo stlačte príslušné tlačidlo v systéme Android/iOS.</p>
</li>
<li>
<p>Na nasledujúcej obrazovke vyberte <strong>členov skupiny</strong> a definujte <strong>názov skupiny</strong>. Môžete si tiež vybrať <strong>avatara skupiny</strong>.</p>
</li>
<li>
<p>Hneď ako napíšete <strong>prvú správu</strong> v skupine, všetci členovia sú informovaní o novej skupine a môžu odpovedať v skupine (pokiaľ nenapíšete správu v skupine, skupina je pre skupinu neviditeľná členovia).</p>
</li>
<li>Vyberte <strong>Nový chat</strong> a potom <strong>Nová skupina</strong> z ponuky v pravom hornom rohu alebo stlačte príslušné tlačidlo v systéme Android/iOS.</li>
<li>Na nasledujúcej obrazovke vyberte <strong>členov skupiny</strong> a definujte <strong>názov skupiny</strong>. Môžete si tiež vybrať <strong>avatara skupiny</strong>.</li>
<li>Hneď ako napíšete <strong>prvú správu</strong> v skupine, všetci členovia sú informovaní o novej skupine a môžu odpovedať v skupine (pokiaľ nenapíšete správu v skupine, skupina je pre skupinu neviditeľná členovia).</li>
</ul>
<h3 id="addmembers">
@@ -525,10 +530,11 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
<ul>
<li>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
</li>
<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>
</li>
@@ -556,8 +562,10 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<p>Keďže už nie ste členom skupiny, nemôžete sa znova pridať.
Žiadny problém, jednoducho požiadajte ktoréhokoľvek iného člena skupiny v bežnom chate, aby vás znova pridal.</p>
<ul>
<li>Keďže už nie ste členom skupiny, nemôžete sa znova pridať.
Žiadny problém, jednoducho požiadajte ktoréhokoľvek iného člena skupiny v bežnom chate, aby vás znova pridal.</li>
</ul>
<h3 id="už-viac-nechcem-dostávať-správy-od-skupiny">
@@ -568,12 +576,15 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<ul>
<li>Vymažte sa zo zoznamu členov alebo odstráňte celý chat.
Ak sa chcete neskôr znova pripojiť k skupine, požiadajte iného člena skupiny, aby vás znova pridal.</li>
</ul>
<p>Ako alternatívu môžete tiež “Stlmiť” skupinu - znamená to, že budete dostávať všetky správy a
<li>
<p>Vymažte sa zo zoznamu členov alebo odstráňte celý chat.
Ak sa chcete neskôr znova pripojiť k skupine, požiadajte iného člena skupiny, aby vás znova pridal.</p>
</li>
<li>
<p>Ako alternatívu môžete tiež “Stlmiť” skupinu - znamená to, že budete dostávať všetky správy a
môžete stále písať, ale už nebudete upozorňovaní na žiadne nové správy.</p>
</li>
</ul>
<h3 id="cloning-a-group">
@@ -599,21 +610,6 @@ or right-click the group in the chat list (Desktop).</p>
<p>The new group is <strong>fully independent</strong> from the original,
which continues to work as before.</p>
<h3 id="how-many-members-can-participate-in-a-single-group">
How many members can participate in a single group? <a href="#how-many-members-can-participate-in-a-single-group" class="anchor"></a>
</h3>
<p>There is no strict technical limit,
but more than 150 is not recommended.</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">Dunbars number</a> for more insights.</p>
<h2 id="webxdc">
@@ -902,7 +898,7 @@ One device is not needed for the other to work.</p>
<p>Double-check both devices are in the <strong>same Wi-Fi or network</strong></p>
</li>
<li>
<p>On <strong>Windows</strong>, go to Control Panel / Network and Internet
<p>On <strong>Windows</strong>, go to <strong>Control Panel / Network and Internet</strong>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
@@ -997,10 +993,10 @@ alebo AppImage pre Linux. Nájdete ich na
</h2>
<h3 id="experiments">
<h3 id="experimental-features">
Experimental Features <a href="#experiments" class="anchor"></a>
Experimental Features <a href="#experimental-features" class="anchor"></a>
</h3>
@@ -1036,7 +1032,7 @@ you can configure relays at At <strong>Settings → Advanced → Relays</strong>
<li>
<p>You can <strong>add</strong> a relay by scanning its QR code;
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
If you have multiple relays, you will receive messages on all of them.</p>
If you have multiple relays, your will receive messages on all of them.</p>
</li>
<li>
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
@@ -1159,7 +1155,9 @@ weekly statistics will be automatically sent to a bot.</p>
</h3>
<p>Pozrite si <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Štandardy používané v Delta Chate</a>.</p>
<ul>
<li>Pozrite si <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Štandardy používané v Delta Chate</a>.</li>
</ul>
<h2 id="e2ee">
@@ -1398,32 +1396,6 @@ 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>
<h3 id="who-sees-my-ip-address">
Who sees my IP Address? <a href="#who-sees-my-ip-address" class="anchor"></a>
</h3>
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
as well as sometimes your contacts devices if you have a <a href="#experiments">call</a>
or use <a href="#webxdc">apps</a> together.</p>
<p>IP Addresses are needed for connectivity and efficiency.
They are neither persisted nor exposed.
Note that the IP Address
is not like a detailed address you give to a delivery service,
but much more coarse, often defining region or country only.</p>
<p>As this is just how the internet and other messengers work by default,
we do not offer options here or ask upfront questions.</p>
<p>If you see your IP Address as a security or privacy risk,
we recommend to use a VPN, in combination with system lockdown mode.
Hunting down options in all apps on your system will leave gaps.
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
<h3 id="sealedsender">
@@ -1663,38 +1635,52 @@ ordered chronologically:</p>
<ul>
<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>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>
<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>
</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>
<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>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>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>
</li>
<li>
<p>Sometimes we receive one-time donations from private individuals.
+102 -114
View File
@@ -26,7 +26,6 @@
<li><a href="#fshiva-veten-padashje">Fshiva veten padashje.</a></li>
<li><a href="#sdua-ti-marr-më-mesazhet-e-një-grupi">Sdua ti marr më mesazhet e një grupi.</a></li>
<li><a href="#cloning-a-group">Cloning a group</a></li>
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
</ul>
</li>
<li><a href="#webxdc">In-chat apps</a>
@@ -55,7 +54,7 @@
</li>
<li><a href="#advanced">Advanced</a>
<ul>
<li><a href="#experiments">Experimental Features</a></li>
<li><a href="#experimental-features">Experimental Features</a></li>
<li><a href="#relays">What are Relays?</a></li>
<li><a href="#can-i-use-a-classic-email-address-with-delta-chat">Can I use a classic email address with Delta Chat?</a></li>
<li><a href="#classic-email">How can I configure a chat profile with a classic email address as relay?</a></li>
@@ -77,7 +76,6 @@
<li><a href="#tls">Are messages marked with the mail icon exposed on the Internet?</a></li>
<li><a href="#message-metadata">Si i mbron Delta Chat-i tejtëdhënat në mesazhe?</a></li>
<li><a href="#device-seizure">Si të mbrohen tejtëdhënat dhe kontaktet, kur shtien në dorë një pajisje?</a></li>
<li><a href="#who-sees-my-ip-address">Who sees my IP Address?</a></li>
<li><a href="#sealedsender">Does Delta Chat support “Sealed Sender”?</a></li>
<li><a href="#pfs">Does Delta Chat support Perfect Forward Secrecy?</a></li>
<li><a href="#pqc">Does Delta Chat support Post-Quantum-Cryptography?</a></li>
@@ -187,8 +185,7 @@ If you add each other to <a href="#groups">groups</a>, end-to-end encryption wil
<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>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>Your friends may share your contact with other friends, this appears as a <strong>request</strong>.</p>
<ul>
<li>
@@ -226,10 +223,15 @@ and can tap it to start chatting with the first contact.</p>
</h3>
<p>Po Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
<ul>
<li>
<p>Po Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
<p>Si parazgjedhje, për funksionim më të mirë, figurat optimizohen dhe dërgohen në madhësi më të vogël, por mund ta dërgoni si një “kartelë”, që të ruhet origjinali.</p>
</li>
<li>
<p>Si parazgjedhje, për funksionim më të mirë, figurat optimizohen dhe dërgohen në madhësi më të vogël, por mund ta dërgoni si një “kartelë”, që të ruhet origjinali.</p>
</li>
</ul>
<h3 id="multiple-accounts">
@@ -260,11 +262,16 @@ or to <strong>Switch Profiles</strong>.</p>
</h3>
<p>Mund të shtoni një foto profili te rregullimet tuaja. Nëse u shkruani kontakteve
tuaja ose i shtoni përmes kodi QR, e shohin automatikisht si foton e profilit tuaj.</p>
<p>Për arsye privatësie, askush se sheh foton tuaj të profilit, deri sa
tu shkruani një mesazh.</p>
<ul>
<li>
<p>Mund të shtoni një foto profili te rregullimet tuaja. Nëse u shkruani kontakteve
tuaja ose i shtoni përmes kodi QR, e shohin automatikisht si foton e profilit tuaj.</p>
</li>
<li>
<p>Për arsye privatësie, askush se sheh foton tuaj të profilit, deri sa
tu shkruani një mesazh.</p>
</li>
</ul>
<h3 id="signature">
@@ -297,7 +304,8 @@ they will see it when they view your contact details.</p>
<p><strong>Heshtoni fjalosje</strong>, nëse sdoni të merrni njoftime mbi to. Fjalosjet e heshtuara qëndrojnë në vend dhe mundeni edhe të fiksoni një fjalosje të heshtuar.</p>
</li>
<li>
<p><strong>Arkivoni fjalosje</strong>, nëse sdoni ti shihni më në listën tuaj të fjalosjeve. Fjalosjet e arkivuara mbesin të përdorshme mbi listën e fjalosjeve, ose përmes kërkimit.</p>
<p><strong>Arkivoni fjalosje</strong>, nëse sdoni ti shihni më në listën tuaj të fjalosjeve.
Fjalosjet e arkivuara mbesin të përdorshme mbi listën e fjalosjeve, ose përmes kërkimit.</p>
</li>
<li>
<p>Kur te një fjalosje e arkivuar vjen një mesazh i ri, do të <strong>hapet jashtë arkivit</strong> dhe kalojë te lista juaj e fjalosjeve, veç në mos qoftë e heshtuar.
@@ -333,7 +341,7 @@ By tapping <img style="vertical-align:middle; width:1.2em; margin:1px" src="../g
you can go back to the original message in the original chat</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>Finally, you can also use “Save Messages” to take <strong>personal notes</strong> - open the chat, type something, add a photo or a voice message etc.</p>
</li>
<li>
<p>As “Saved Message” are synced, they can become very handy for transferring data between devices</p>
@@ -370,18 +378,22 @@ 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>
means that the message was sent successfully to your provider.</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>
mean that at least one recipients device
reported back to having received the message.</p>
</li>
<li>
<p>Recipients may have disabled read-receipts,
so even if you see only one tick, the message may have been read.</p>
</li>
<li>
<p>The other way round, two ticks do not automatically mean
that a human has read or understood the message ;)</p>
</li>
</ul>
<p>In <a href="#groups">groups</a> the second tick means that at least one member has reported back having read the message.</p>
<p>You will only get the second tick if both you and one of the recipients who read the message
has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
<h3 id="edit">
@@ -451,13 +463,14 @@ the (anyway encrypted) messages may take longer to get deleted from their server
</h3>
<p>Nëse doni të kurseni hapësirë në pajisjen tuaj, mund të zgjidhni të fshihen
automatikisht mesazhe të vjetër.</p>
<p>Për ta aktivizuar, kaloni te “fshi prej pajisjeje mesazhe të vjetër”, te rregullimet
<ul>
<li>Nëse doni të kurseni hapësirë në pajisjen tuaj, mund të zgjidhni të fshihen
automatikisht mesazhe të vjetër.</li>
<li>Për ta aktivizuar, kaloni te “fshi prej pajisjeje mesazhe të vjetër”, te rregullimet
“Fjalosje &amp; Media”. Mund të caktoni një periudhë nga “pas një ore” e deri
“pas një viti”; në këtë mënyrë, <em>krejt</em> mesazhet do të fshihen nga pajisja juaj
sapo të jenë më të vjetër se aq.</p>
sapo të jenë më të vjetër se aq.</li>
</ul>
<h3 id="remove-account">
@@ -505,15 +518,9 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<ul>
<li>
<p>Prej menusë në cepin e sipërm djathtas, përzgjidhni <strong>Fjalosje e re</strong> dhe mandej <strong>Grup i ri</strong>, ose shtypni butonin përgjegjës në Android/iOS.</p>
</li>
<li>
<p>Te skena vijuese, përzgjidhni <strong>anëtarë grupi</strong> dhe përcaktoni një <strong>emër grupi</strong>. Mund të përzgjidhni edhe një <strong>avatar grupi</strong>.</p>
</li>
<li>
<p>Sapo të shkruani <strong>mesazhin e parë</strong> te grupi, krejt anëtarët marrin vesh për grupin e ri dhe mund të përgjigjen në të (për sa kohë që te grupi sshkruani një mesazh i cili është i padukshëm për anëtarët).</p>
</li>
<li>Prej menusë në cepin e sipërm djathtas, përzgjidhni <strong>Fjalosje e re</strong> dhe mandej <strong>Grup i ri</strong>, ose shtypni butonin përgjegjës në Android/iOS.</li>
<li>Te skena vijuese, përzgjidhni <strong>anëtarë grupi</strong> dhe përcaktoni një <strong>emër grupi</strong>. Mund të përzgjidhni edhe një <strong>avatar grupi</strong>.</li>
<li>Sapo të shkruani <strong>mesazhin e parë</strong> te grupi, krejt anëtarët marrin vesh për grupin e ri dhe mund të përgjigjen në të (për sa kohë që te grupi sshkruani një mesazh i cili është i padukshëm për anëtarët).</li>
</ul>
<h3 id="addmembers">
@@ -524,10 +531,11 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
<ul>
<li>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
</li>
<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>
</li>
@@ -555,8 +563,10 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<p>Ngaqë sjeni më anëtar i grupit, smund të shtoni veten sërish.
Megjithatë, ska problem, thjesht kërkojini një anëtari tjetër të grupit në një fjalosje të zakonshme tju shtojë sërish.</p>
<ul>
<li>Ngaqë sjeni më anëtar i grupit, smund të shtoni veten sërish.
Megjithatë, ska problem, thjesht kërkojini një anëtari tjetër të grupit në një fjalosje të zakonshme tju shtojë sërish.</li>
</ul>
<h3 id="sdua-ti-marr-më-mesazhet-e-një-grupi">
@@ -567,13 +577,16 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<ul>
<li>Ose fshini veten si anëtar i listës, ose fshini krejt bisedën.
Nëse më vonë doni të ribëheni pjesë e grupit, kërkojini një anëtari tjetër të grupit tju shtojë sërish.</li>
</ul>
<p>Ndryshe, mundeni edhe ta “Heshtoni” një grup - duke bërë këtë, do të merrni
<li>
<p>Ose fshini veten si anëtar i listës, ose fshini krejt bisedën.
Nëse më vonë doni të ribëheni pjesë e grupit, kërkojini një anëtari tjetër të grupit tju shtojë sërish.</p>
</li>
<li>
<p>Ndryshe, mundeni edhe ta “Heshtoni” një grup - duke bërë këtë, do të merrni
krejt mesazhet dhe prapë mund të shkruani, por nuk njoftoheni më,
për çfarëdo mesazhesh të rinj.</p>
</li>
</ul>
<h3 id="cloning-a-group">
@@ -599,21 +612,6 @@ or right-click the group in the chat list (Desktop).</p>
<p>The new group is <strong>fully independent</strong> from the original,
which continues to work as before.</p>
<h3 id="how-many-members-can-participate-in-a-single-group">
How many members can participate in a single group? <a href="#how-many-members-can-participate-in-a-single-group" class="anchor"></a>
</h3>
<p>There is no strict technical limit,
but more than 150 is not recommended.</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">Dunbars number</a> for more insights.</p>
<h2 id="webxdc">
@@ -902,7 +900,7 @@ Njëra pajisja ska nevojë për tjetrën që të funksionojë.</p>
<p>Kontrolloni sërish që të dyja pajisjet të gjenden në <strong>të njëjtin rrjet Wi-Fi ose klasik</strong></p>
</li>
<li>
<p>On <strong>Windows</strong>, go to Control Panel / Network and Internet
<p>On <strong>Windows</strong>, go to <strong>Control Panel / Network and Internet</strong>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
@@ -999,10 +997,10 @@ Windows Desktop, ose AppImage për Linux. Mund ti gjeni te
</h2>
<h3 id="experiments">
<h3 id="experimental-features">
Experimental Features <a href="#experiments" class="anchor"></a>
Experimental Features <a href="#experimental-features" class="anchor"></a>
</h3>
@@ -1038,7 +1036,7 @@ you can configure relays at At <strong>Settings → Advanced → Relays</strong>
<li>
<p>You can <strong>add</strong> a relay by scanning its QR code;
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
If you have multiple relays, you will receive messages on all of them.</p>
If you have multiple relays, your will receive messages on all of them.</p>
</li>
<li>
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
@@ -1161,7 +1159,9 @@ weekly statistics will be automatically sent to a bot.</p>
</h3>
<p>Shihni <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Standarde të përdorur në Delta Chat</a>.</p>
<ul>
<li>Shihni <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Standarde të përdorur në Delta Chat</a>.</li>
</ul>
<h2 id="e2ee">
@@ -1400,32 +1400,6 @@ 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>
<h3 id="who-sees-my-ip-address">
Who sees my IP Address? <a href="#who-sees-my-ip-address" class="anchor"></a>
</h3>
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
as well as sometimes your contacts devices if you have a <a href="#experiments">call</a>
or use <a href="#webxdc">apps</a> together.</p>
<p>IP Addresses are needed for connectivity and efficiency.
They are neither persisted nor exposed.
Note that the IP Address
is not like a detailed address you give to a delivery service,
but much more coarse, often defining region or country only.</p>
<p>As this is just how the internet and other messengers work by default,
we do not offer options here or ask upfront questions.</p>
<p>If you see your IP Address as a security or privacy risk,
we recommend to use a VPN, in combination with system lockdown mode.
Hunting down options in all apps on your system will leave gaps.
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
<h3 id="sealedsender">
@@ -1665,38 +1639,52 @@ ordered chronologically:</p>
<ul>
<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>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>
<p><a href="https://opentechfund.org">Open Technology Fund</a> na dha grantin e parë
për 2018/2019 (~200 mijë dollarë) me të cilin përmirësuam ndjeshëm aplikacionin
për Android dhe hodhëm në qarkullim një version të parë beta aplikacioni për Desktop,
si dhe i afroi më tepër zhvillimet tona për veçori me kërkime UX në kontekste të drejtash të njeriut,
shihni <a href="https://delta.chat/en/2019-07-19-uxreport">raportin tonë përfundimtar “Needfinding and UX”</a>.
Granti i dytë për 2019/2020 (~$300K) na ndihmoi të hedhim në qarkullim
versione Delta/iOS, për të shndërruar bibliotekën tonë bazë në Rust, si dhe
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>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>
<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>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>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>
</li>
<li>
<p>Ndonjëherë marrim dhurime unike nga individë privatë.
+81 -104
View File
@@ -25,7 +25,6 @@
<li><a href="#я-випадково-себе-видалив">Я випадково себе видалив</a></li>
<li><a href="#я-більше-не-хочу-отримувати-повідомлення-групи">Я більше не хочу отримувати повідомлення групи.</a></li>
<li><a href="#клонування-групи">Клонування групи</a></li>
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
</ul>
</li>
<li><a href="#webxdc">In-chat apps</a>
@@ -54,7 +53,7 @@
</li>
<li><a href="#advanced">Advanced</a>
<ul>
<li><a href="#experiments">Експериментальні функції</a></li>
<li><a href="#експериментальні-функції">Експериментальні функції</a></li>
<li><a href="#relays">What are Relays?</a></li>
<li><a href="#can-i-use-a-classic-email-address-with-delta-chat">Can I use a classic email address with Delta Chat?</a></li>
<li><a href="#classic-email">How can I configure a chat profile with a classic email address as relay?</a></li>
@@ -76,7 +75,6 @@
<li><a href="#чи-повідомлення-позначені-значком-пошти-доступні-в-інтернетіtls">Чи повідомлення, позначені значком пошти, доступні в Інтернеті?{#tls}</a></li>
<li><a href="#message-metadata">Як Delta Chat захищає метадані у повідомленнях?</a></li>
<li><a href="#device-seizure">Як захистити метадані та контакти якщо пристрій вилучено?</a></li>
<li><a href="#who-sees-my-ip-address">Who sees my IP Address?</a></li>
<li><a href="#sealedsender">Чи підтримує Delta Chat функцію “Запечатаний відправник”?</a></li>
<li><a href="#pfs">Чи підтримує Delta Chat цілковиту пряму секретність (Perfect Forward Secrecy)?</a></li>
<li><a href="#pqc">Чи підтримує Delta Chat пост-квантову криптографію?</a></li>
@@ -182,8 +180,7 @@ the ability to chat is delayed until connectivity is restored.</p>
<p>As being a private messenger,
only friends and family you <a href="#howtoe2ee">share your QR code or invite link with</a> can write to you.</p>
<p>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>Your friends may share your contact with other friends, this appears as a <strong>request</strong>.</p>
<ul>
<li>
@@ -219,10 +216,15 @@ and can tap it to start chatting with the first contact.</p>
</h3>
<p>Так. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
<ul>
<li>
<p>Так. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
<p>З міркувань продуктивності, зображення оптимізовані та надсилаються в меншому розмірі за замовчуванням, але ви можете надіслати їх як «файл», щоб зберегти оригінал.</p>
</li>
<li>
<p>З міркувань продуктивності, зображення оптимізовані та надсилаються в меншому розмірі за замовчуванням, але ви можете надіслати їх як «файл», щоб зберегти оригінал.</p>
</li>
</ul>
<h3 id="multiple-accounts">
@@ -252,9 +254,14 @@ and uses the server only to relay messages.</p>
</h3>
<p>Ви можете додати зображення профілю в ваших налаштуваннях. Якщо ви пишете комусь із ваших контактів чи додаєте їх через QR код, вони автоматично побачать ваше зображення профілю.</p>
<p>Із міркувань приватності, ніхто не бачить ваше зображення профілю доки ви їм не напишете.</p>
<ul>
<li>
<p>Ви можете додати зображення профілю в ваших налаштуваннях. Якщо ви пишете комусь із ваших контактів чи додаєте їх через QR код, вони автоматично побачать ваше зображення профілю.</p>
</li>
<li>
<p>Із міркувань приватності, ніхто не бачить ваше зображення профілю доки ви їм не напишете.</p>
</li>
</ul>
<h3 id="signature">
@@ -287,7 +294,8 @@ they will see it when they view your contact details.</p>
<p><strong>Приглушіть чати</strong> якщо ви не хочете отримувати сповіщення для них. Приглушені чати залишаються на місці і ви також можете закріпити приглушений чат.</p>
</li>
<li>
<p><strong>Архівуйте чати</strong>, якщо ви більше не хочете бачити їх у своєму списку чатів. Заархівовані чати залишаються доступними над списком чатів або через пошук.</p>
<p><strong>Архівуйте чати</strong>, якщо ви більше не хочете бачити їх у своєму списку чатів.
Заархівовані чати залишаються доступними над списком чатів або через пошук.</p>
</li>
<li>
<p>Коли архівний чат отримує нове повідомлення, якщо не приглушений, він <strong>вискочить з архіву</strong> і повернеться у ваш список чатів.
@@ -354,18 +362,22 @@ 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>
means that the message was sent successfully to your provider.</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>
mean that at least one recipients device
reported back to having received the message.</p>
</li>
<li>
<p>Recipients may have disabled read-receipts,
so even if you see only one tick, the message may have been read.</p>
</li>
<li>
<p>The other way round, two ticks do not automatically mean
that a human has read or understood the message ;)</p>
</li>
</ul>
<p>In <a href="#groups">groups</a> the second tick means that at least one member has reported back having read the message.</p>
<p>You will only get the second tick if both you and one of the recipients who read the message
has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
<h3 id="edit">
@@ -426,10 +438,11 @@ the (anyway encrypted) messages may take longer to get deleted from their server
</h3>
<p>Якщо ви хочете заощадити пам’ять на своєму пристрої, ви можете видалити старе повідомлення автоматично.</p>
<p>Щоб увімкнути його, перейдіть до «видалити старі повідомлення з пристрою» в налаштуваннях «Чатів та медіа» . Ви можете встановити часові рамки від «через годину» до «через рік»;
Таким чином, <em>усі</em> повідомлення будуть видалені з вашого пристрою, як тільки вони будуть старішими за це.</p>
<ul>
<li>Якщо ви хочете заощадити пам’ять на своєму пристрої, ви можете видалити старе повідомлення автоматично.</li>
<li>Щоб увімкнути його, перейдіть до «видалити старі повідомлення з пристрою» в налаштуваннях «Чатів та медіа» . Ви можете встановити часові рамки від «через годину» до «через рік»;
Таким чином, <em>усі</em> повідомлення будуть видалені з вашого пристрою, як тільки вони будуть старішими за це.</li>
</ul>
<p>Як я можу видалити свій профіль у Delta Chat? {#remove-account}</p>
@@ -471,15 +484,9 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<ul>
<li>
<p>Оберіть <strong>Новий чат</strong>, потім <strong>Нова групи</strong> у меню в верхньому правому кутку або натисніть відповідну кнопку у Android/iOS.</p>
</li>
<li>
<p>На наступному екрані виберіть <strong>учасники групи</strong> та встановіть <strong>назву групи</strong>. Ви також можете обрати <strong>аватар групи</strong>.</p>
</li>
<li>
<p>Як тільки ви напишете <strong>перше повідомлення</strong> у групу, усі учасники будуть проінформовані про нову групу і зможуть відповісти у нову групу (доки ви не напишете повідомлення у групі, група залишатиметься невидимою для учасників).</p>
</li>
<li>Оберіть <strong>Новий чат</strong>, потім <strong>Нова групи</strong> у меню в верхньому правому кутку або натисніть відповідну кнопку у Android/iOS.</li>
<li>На наступному екрані виберіть <strong>учасники групи</strong> та встановіть <strong>назву групи</strong>. Ви також можете обрати <strong>аватар групи</strong>.</li>
<li>Як тільки ви напишете <strong>перше повідомлення</strong> у групу, усі учасники будуть проінформовані про нову групу і зможуть відповісти у нову групу (доки ви не напишете повідомлення у групі, група залишатиметься невидимою для учасників).</li>
</ul>
<h3 id="addmembers">
@@ -490,10 +497,11 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
<ul>
<li>
<p>Усі учасники групи мають <strong>однакові права</strong>.
Тому кожен може видалити будь-якого учасника або додати нових.</p>
</li>
<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>
</li>
@@ -521,7 +529,9 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<p>Оскільки ви більше не учасник групи, ви не зможете додати себе знову. Однак, це не проблема, просто попросіть будь-якого іншого учасника групи в звичайному чаті додати вас знову.</p>
<ul>
<li>Оскільки ви більше не учасник групи, ви не зможете додати себе знову. Однак, це не проблема, просто попросіть будь-якого іншого учасника групи в звичайному чаті додати вас знову.</li>
</ul>
<h3 id="я-більше-не-хочу-отримувати-повідомлення-групи">
@@ -532,10 +542,13 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<ul>
<li>Або видаліть себе із списку учасників групи, або видаліть весь чат. Якщо ви хочете повернутись до чату пізніше, попросіть іншого учасника групи додати вас знову.</li>
<li>
<p>Або видаліть себе із списку учасників групи, або видаліть весь чат. Якщо ви хочете повернутись до чату пізніше, попросіть іншого учасника групи додати вас знову.</p>
</li>
<li>
<p>Ви також можете “Заглушити” групу - це означає, що ви будете отримувати усі повідомлення та можете писати у групу, але ви більше не будете отримувати сповіщення про нові повідомлення.</p>
</li>
</ul>
<p>Ви також можете “Заглушити” групу - це означає, що ви будете отримувати усі повідомлення та можете писати у групу, але ви більше не будете отримувати сповіщення про нові повідомлення.</p>
<h3 id="клонування-групи">
@@ -561,21 +574,6 @@ or right-click the group in the chat list (Desktop).</p>
<p>Нова група є <strong>цілком незалежною</strong> від оригінальної,
котра продовжує працювати як раніше.</p>
<h3 id="how-many-members-can-participate-in-a-single-group">
How many members can participate in a single group? <a href="#how-many-members-can-participate-in-a-single-group" class="anchor"></a>
</h3>
<p>There is no strict technical limit,
but more than 150 is not recommended.</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">Dunbars number</a> for more insights.</p>
<h2 id="webxdc">
@@ -833,7 +831,7 @@ Welcome to the power of the interoperable chatmail relay network :)</p>
<p>Ще раз упевніться, що обидва пристрої підключені до <strong>одного Wi-Fi або мережі</strong></p>
</li>
<li>
<p>У <strong>Windows</strong> перейдіть до Панель керування / Мережа та Інтернет і переконайтеся, що <strong>Приватна мережа</strong> вибрано як “Тип мережевого профілю” (після перенесення ви можете повернути початкове значення)</p>
<p>У <strong>Windows</strong> перейдіть до <strong>Панель керування / Мережа та Інтернет</strong> і переконайтеся, що <strong>Приватна мережа</strong> вибрано як “Тип мережевого профілю” (після перенесення ви можете повернути початкове значення)</p>
</li>
<li>
<p>На <strong>iOS</strong> переконайтеся, що доступ до “Системні налаштування / Програми / Delta Chat / <strong>Локальна мережа</strong>” дозволено</p>
@@ -906,10 +904,10 @@ Welcome to the power of the interoperable chatmail relay network :)</p>
</h2>
<h3 id="experiments">
<h3 id="експериментальні-функції">
Експериментальні функції <a href="#experiments" class="anchor"></a>
Експериментальні функції <a href="#експериментальні-функції" class="anchor"></a>
</h3>
@@ -945,7 +943,7 @@ you can configure relays at At <strong>Settings → Advanced → Relays</strong>
<li>
<p>You can <strong>add</strong> a relay by scanning its QR code;
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
If you have multiple relays, you will receive messages on all of them.</p>
If you have multiple relays, your will receive messages on all of them.</p>
</li>
<li>
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
@@ -1068,7 +1066,9 @@ to send anonymous usage statistics.</p>
</h3>
<p>Дивіться <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Стандарти, що використовуються у Delta Chat</a>.</p>
<ul>
<li>Дивіться <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Стандарти, що використовуються у Delta Chat</a>.</li>
</ul>
<h2 id="e2ee">
@@ -1246,32 +1246,6 @@ 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>
<h3 id="who-sees-my-ip-address">
Who sees my IP Address? <a href="#who-sees-my-ip-address" class="anchor"></a>
</h3>
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
as well as sometimes your contacts devices if you have a <a href="#experiments">call</a>
or use <a href="#webxdc">apps</a> together.</p>
<p>IP Addresses are needed for connectivity and efficiency.
They are neither persisted nor exposed.
Note that the IP Address
is not like a detailed address you give to a delivery service,
but much more coarse, often defining region or country only.</p>
<p>As this is just how the internet and other messengers work by default,
we do not offer options here or ask upfront questions.</p>
<p>If you see your IP Address as a security or privacy risk,
we recommend to use a VPN, in combination with system lockdown mode.
Hunting down options in all apps on your system will leave gaps.
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
<h3 id="sealedsender">
@@ -1445,35 +1419,38 @@ Google Play Store, F-Droid, Huawei App Gallery, iOS and macOS App Store, Microso
<ul>
<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>Проект ЄС <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">Open Technology Fund</a> надав нам два гранти.
Перший грант 2018/2019 року (~$200K), допоміг значно покращили додаток для Android
і випустили першу бета-версію додатка для ПК, і який до того ж
закріпив наші розробки функцій у дослідженнях UX у контексті прав людини,
дивіться наш підсумковий звіт <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
Другий грант 2019/2020 року (~$300K) допоміг нам
випустити Delta/iOS версію, конвертувати нашу основному бібліотеку на Rust,
і додати нові функції для всіх платформ.</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>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>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>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>
<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>Проект ЄС <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 році щедра приватна особа перерахував нам 4 тис. євро з повідомленням «так тримати!». 💜 Ми використовуємо такі пожертви для фінансування зборів на розвиток або для тимчасових витрат, які важко передбачити або відшкодувати за рахунок грантів державного фінансування. Отримання більшої кількості пожертв також допомагає нам стати більш незалежними та довгостроково життєздатними як спільнота контриб’юторів.</p>
+80 -102
View File
@@ -26,7 +26,6 @@
<li><a href="#我不小心删除了我自己">我不小心删除了我自己。</a></li>
<li><a href="#我不想再收到某个群组中的消息了">我不想再收到某个群组中的消息了。</a></li>
<li><a href="#cloning-a-group">Cloning a group</a></li>
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
</ul>
</li>
<li><a href="#webxdc">In-chat apps</a>
@@ -55,7 +54,7 @@
</li>
<li><a href="#advanced">Advanced</a>
<ul>
<li><a href="#experiments">Experimental Features</a></li>
<li><a href="#experimental-features">Experimental Features</a></li>
<li><a href="#relays">What are Relays?</a></li>
<li><a href="#can-i-use-a-classic-email-address-with-delta-chat">Can I use a classic email address with Delta Chat?</a></li>
<li><a href="#classic-email">How can I configure a chat profile with a classic email address as relay?</a></li>
@@ -77,7 +76,6 @@
<li><a href="#tls">Are messages marked with the mail icon exposed on the Internet?</a></li>
<li><a href="#message-metadata">Delta Chat 如何保护消息中的元数据?</a></li>
<li><a href="#device-seizure">当设备被查封时,如何保护元数据和联系人?</a></li>
<li><a href="#who-sees-my-ip-address">Who sees my IP Address?</a></li>
<li><a href="#sealedsender">Does Delta Chat support “Sealed Sender”?</a></li>
<li><a href="#pfs">Delta Chat 是否支持完美前向保密?</a></li>
<li><a href="#pqc">Does Delta Chat support Post-Quantum-Cryptography?</a></li>
@@ -187,8 +185,7 @@ If you add each other to <a href="#groups">groups</a>, end-to-end encryption wil
<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>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>Your friends may share your contact with other friends, this appears as a <strong>request</strong>.</p>
<ul>
<li>
@@ -226,10 +223,15 @@ and can tap it to start chatting with the first contact.</p>
</h3>
<p>是的。 Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
<ul>
<li>
<p>是的。 Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
<p>为了提高性能,默认情况下会对图像进行优化并以较小的尺寸发送,但您也可以将其作为 “文件 “发送,以保留原始图像。</p>
</li>
<li>
<p>为了提高性能,默认情况下会对图像进行优化并以较小的尺寸发送,但您也可以将其作为 “文件 “发送,以保留原始图像。</p>
</li>
</ul>
<h3 id="multiple-accounts">
@@ -260,9 +262,14 @@ and uses the server only to relay messages.</p>
</h3>
<p>您可以在设置中添加个人资料图片。如果您给您的联系人发消息或者通过二维码添加他们,他们会自动看到您的个人资料图片。</p>
<p>出于隐私原因,在您向他们发送消息之前,没有人会看到您的个人资料片。</p>
<ul>
<li>
<p>您可以在设置中添加个人资料图片。如果您给您的联系人发消息或者通过二维码添加他们,他们会自动看到您的个人资料片。</p>
</li>
<li>
<p>出于隐私原因,在您向他们发送消息之前,没有人会看到您的个人资料照片。</p>
</li>
</ul>
<h3 id="signature">
@@ -368,18 +375,22 @@ 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>
means that the message was sent successfully to your provider.</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>
mean that at least one recipients device
reported back to having received the message.</p>
</li>
<li>
<p>Recipients may have disabled read-receipts,
so even if you see only one tick, the message may have been read.</p>
</li>
<li>
<p>The other way round, two ticks do not automatically mean
that a human has read or understood the message ;)</p>
</li>
</ul>
<p>In <a href="#groups">groups</a> the second tick means that at least one member has reported back having read the message.</p>
<p>You will only get the second tick if both you and one of the recipients who read the message
has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
<h3 id="edit">
@@ -445,9 +456,10 @@ the (anyway encrypted) messages may take longer to get deleted from their server
</h3>
<p>若要节省设备上的存储空间,可以开启自动删除旧消息</p>
<p>找到“聊天与媒体”设置中的“从设备删除旧消息”,在从“一小时后”到“一年后”的一系列选项中选择一个。这样,设备上 <em>所有</em> 比所选择时间长度老的消息将被删除</p>
<ul>
<li>若要节省设备上的存储空间,可以开启自动删除旧消息。</li>
<li>找到“聊天与媒体”设置中的“从设备删除旧消息”,在从“一小时后”到“一年后”的一系列选项中选择一个。这样,设备上 <em>所有</em> 比所选择时间长度老的消息将被删除</li>
</ul>
<h3 id="remove-account">
@@ -495,15 +507,9 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<ul>
<li>
<p>从右上角的菜单中选择<strong>新建聊天</strong>,然后选择<strong>新建群组</strong>或在 Android/iOS 上点击相应的按钮</p>
</li>
<li>
<p>在随后的屏幕上,选择<strong>群组成员</strong>并起一个<strong>群组名称</strong>。您也可以选择一个<strong>群组头像</strong></p>
</li>
<li>
<p>当您在群组中发送<strong>第一条消息</strong>时,所有成员都会被告知新群组的信息并可以在该群组中应答(只要您不在群组中发送第一条消息,那么群组对成员就是不可见的)。</p>
</li>
<li>从右上角的菜单中选择<strong>新建聊天</strong>,然后选择<strong>新建群组</strong>或在 Android/iOS 上点击相应的按钮。</li>
<li>在随后的屏幕上,选择<strong>群组成员</strong>并起一个<strong>群组名称</strong>。您也可以选择一个<strong>群组头像</strong></li>
<li>当您在群组中发送<strong>第一条消息</strong>时,所有成员都会被告知新群组的信息并可以在该群组中应答(只要您不在群组中发送第一条消息,那么群组对成员就是不可见的)。</li>
</ul>
<h3 id="addmembers">
@@ -514,10 +520,11 @@ and <a href="#edit">delete their own messages</a> from all members devices.</
</h3>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
<ul>
<li>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
</li>
<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>
</li>
@@ -545,7 +552,9 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<p>由于您不再是群组成员,您无法将自己加入到群组中。但是,问题不大,只需在普通聊天中请求其他群组成员将您重新加入即可。</p>
<ul>
<li>由于您不再是群组成员,您无法将自己加入到群组中。但是,问题不大,只需在普通聊天中请求其他群组成员将您重新加入即可。</li>
</ul>
<h3 id="我不想再收到某个群组中的消息了">
@@ -556,10 +565,13 @@ However, since groups are <a href="#groups">meant for trusted people</a>, avoid
</h3>
<ul>
<li>从成员列表中删除自己,或者删除整个聊天。如果您之后想再加入该群组,请让其他群组成员添加您。</li>
<li>
<p>从成员列表中删除自己,或者删除整个聊天。如果您之后想再加入该群组,请让其他群组成员添加您。</p>
</li>
<li>
<p>另外,您也可以“静音”群组——这样做意味着您会收到所有消息并且仍可以编写消息,但不会再收到任何新消息的通知。</p>
</li>
</ul>
<p>另外,您也可以“静音”群组——这样做意味着您会收到所有消息并且仍可以编写消息,但不会再收到任何新消息的通知。</p>
<h3 id="cloning-a-group">
@@ -585,21 +597,6 @@ or right-click the group in the chat list (Desktop).</p>
<p>The new group is <strong>fully independent</strong> from the original,
which continues to work as before.</p>
<h3 id="how-many-members-can-participate-in-a-single-group">
How many members can participate in a single group? <a href="#how-many-members-can-participate-in-a-single-group" class="anchor"></a>
</h3>
<p>There is no strict technical limit,
but more than 150 is not recommended.</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">Dunbars number</a> for more insights.</p>
<h2 id="webxdc">
@@ -885,7 +882,7 @@ Welcome to the power of the interoperable chatmail relay network :)</p>
<p>仔细检查两台设备是否在<strong>同一个 Wi-Fi 或网络中</strong></p>
</li>
<li>
<p><strong>Windows</strong> 上,转到控制面板 / 网络和 Internet
<p><strong>Windows</strong> 上,转到<strong>控制面板 / 网络和 Internet</strong>
并确保<strong>专用网络</strong>被选为“网络配置文件类型”
(传输后,你可以更改回原始值)</p>
</li>
@@ -970,10 +967,10 @@ Welcome to the power of the interoperable chatmail relay network :)</p>
</h2>
<h3 id="experiments">
<h3 id="experimental-features">
Experimental Features <a href="#experiments" class="anchor"></a>
Experimental Features <a href="#experimental-features" class="anchor"></a>
</h3>
@@ -1009,7 +1006,7 @@ you can configure relays at At <strong>Settings → Advanced → Relays</strong>
<li>
<p>You can <strong>add</strong> a relay by scanning its QR code;
<a href="https://chatmail.at/relays">https://chatmail.at/relays</a> shows some known ones.
If you have multiple relays, you will receive messages on all of them.</p>
If you have multiple relays, your will receive messages on all of them.</p>
</li>
<li>
<p>The <strong>default</strong> defines the one where your chat partners send future messages to.</p>
@@ -1132,7 +1129,9 @@ weekly statistics will be automatically sent to a bot.</p>
</h3>
<p>请参阅 <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Delta Chat 中使用的标准</a></p>
<ul>
<li>请参阅 <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Delta Chat 中使用的标准</a></li>
</ul>
<h2 id="e2ee">
@@ -1368,32 +1367,6 @@ 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>
<h3 id="who-sees-my-ip-address">
Who sees my IP Address? <a href="#who-sees-my-ip-address" class="anchor"></a>
</h3>
<p>The used <a href="#relays">relay</a> needs to know your IP Address,
as well as sometimes your contacts devices if you have a <a href="#experiments">call</a>
or use <a href="#webxdc">apps</a> together.</p>
<p>IP Addresses are needed for connectivity and efficiency.
They are neither persisted nor exposed.
Note that the IP Address
is not like a detailed address you give to a delivery service,
but much more coarse, often defining region or country only.</p>
<p>As this is just how the internet and other messengers work by default,
we do not offer options here or ask upfront questions.</p>
<p>If you see your IP Address as a security or privacy risk,
we recommend to use a VPN, in combination with system lockdown mode.
Hunting down options in all apps on your system will leave gaps.
For example, tapping a link exposes IP Addresses to unknown parties and is the by far larger risk here.</p>
<h3 id="sealedsender">
@@ -1624,35 +1597,40 @@ Google Play Store, F-Droid, Huawei App Gallery, iOS and macOS App Store, Microso
<ul>
<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><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>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>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>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>
<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><a href="https://nextleap.eu">NEXTLEAP</a>欧盟项目资助了以下研究和实施工作:在 2017 年和 2018 年实施的验证组和设置联系协议和通过 <a href="https://autocrypt.org">Autocrypt</a>整合了端到端加密。</p>
<p>在 2023 年和 2024 年,我们的 <a href="https://nlnet.nl/project/WebXDC-Push/">WebXDC PUSH</a> 工作已在下一代互联网 (NGI) 中获得认可,
并与致力于
<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 年,一位慷慨的人士以 “继续保持良好的发展态势!”为主题通过银行向我们捐赠了4千欧元💜。 我们用这些钱来资助发展活动或支付不容易预测或从公共基金中报销的临时费用。收到更多的捐款也有助于我们作为一个贡献者社区变得更加独立和长期可持续。</p>
+9 -38
View File
@@ -211,11 +211,11 @@ public class Rpc {
}
/**
* 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.
* 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`.
* <p>
* "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.
* Internally, the function will call dc_set_config() with the appropriate keys,
*/
public void setConfigFromQr(Integer accountId, String qrContent) throws RpcException {
transport.call("set_config_from_qr", mapper.valueToTree(accountId), mapper.valueToTree(qrContent));
@@ -234,11 +234,6 @@ 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));
}
@@ -454,11 +449,11 @@ public class Rpc {
* Delete a chat.
* <p>
* Messages are deleted from the device and the chat database entry is deleted.
* After that, a `MsgsChanged` event is emitted.
* Messages are deleted from the server in background.
* After that, the event #DC_EVENT_MSGS_CHANGED is posted.
* <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
@@ -738,26 +733,11 @@ 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()
* (read receipts aren't sent for noticed messages).
* (IMAP/MDNs is not done for noticed messages).
* <p>
* Calling this function usually results in the event #DC_EVENT_MSGS_NOTICED.
* See also markseen_msgs().
@@ -913,15 +893,6 @@ 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));
@@ -1315,8 +1286,8 @@ public class Rpc {
}
/** Starts an outgoing call. */
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));
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));
}
/** Accepts an incoming call. */
@@ -2,7 +2,7 @@
package chat.delta.rpc.types;
/**
* cheaper version of fullchat, omits: - contact_ids - fresh_message_counter - ephemeral_timer - self_in_group - was_seen_recently - can_send
* cheaper version of fullchat, omits: - contacts - 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 the call is started as a video call. */
/** True if SDP offer has a video. */
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 of the contact. Prefix the text by a green checkmark.
* - 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.
* <p>
* - If `verifierId` == 0 and `isVerified` != 0, display "Introduced" prefixed by a green checkmark.
* <p>
@@ -412,9 +412,7 @@ public abstract class EventType {
/**
* One or more transports has changed.
* <p>
* 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.
* 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.
*/
public static class TransportsModified extends EventType {
}
@@ -7,6 +7,7 @@ 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,7 +12,6 @@ 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,7 +22,6 @@ 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;
@@ -2,9 +2,8 @@ package com.b44t.messenger;
public class DcAccounts {
public DcAccounts(String dir, DcEventChannel channel) {
accountsCPtr = createAccountsCPtr(dir, channel);
if (accountsCPtr == 0) throw new RuntimeException("createAccountsCPtr() returned null pointer");
public DcAccounts(String dir) {
accountsCPtr = createAccountsCPtr(dir);
}
@Override
@@ -46,7 +45,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, DcEventChannel channel);
private native long createAccountsCPtr (String dir);
private native void unrefAccountsCPtr ();
private native long getEventEmitterCPtr ();
private native long getJsonrpcInstanceCPtr ();
@@ -35,7 +35,6 @@ public class DcContext {
public final static int DC_EVENT_INCOMING_CALL_ACCEPTED = 2560;
public final static int DC_EVENT_OUTGOING_CALL_ACCEPTED = 2570;
public final static int DC_EVENT_CALL_ENDED = 2580;
public final static int DC_EVENT_TRANSPORTS_MODIFIED = 2600;
public final static int DC_IMEX_EXPORT_SELF_KEYS = 1;
public final static int DC_IMEX_IMPORT_SELF_KEYS = 2;
@@ -1,27 +0,0 @@
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,24 +1,16 @@
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;
@@ -26,13 +18,11 @@ 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;
@@ -40,7 +30,6 @@ 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";
@@ -68,10 +57,6 @@ 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();
@@ -106,19 +91,11 @@ 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();
}
@@ -147,40 +124,6 @@ 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,10 +11,9 @@ 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;
@@ -32,8 +31,7 @@ class AllMediaDocumentsAdapter extends StickyHeaderGridAdapter {
private final ItemClickListener itemClickListener;
private final Set<DcMsg> selected;
private BucketedThreadMedia media;
private AudioPlaybackViewModel playbackViewModel;
private BucketedThreadMedia media;
private static class ViewHolder extends StickyHeaderGridAdapter.ItemViewHolder {
private final DocumentView documentView;
@@ -73,10 +71,6 @@ 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));
@@ -103,7 +97,6 @@ class AllMediaDocumentsAdapter extends StickyHeaderGridAdapter {
viewHolder.webxdcView.setVisibility(View.GONE);
viewHolder.audioView.setVisibility(View.VISIBLE);
viewHolder.audioView.setPlaybackViewModel(playbackViewModel);
viewHolder.audioView.setAudio((AudioSlide)slide, dcMsg.getDuration());
viewHolder.audioView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg));
viewHolder.audioView.setOnLongClickListener(view -> { itemClickListener.onMediaLongClicked(dcMsg); return true; });
@@ -16,7 +16,6 @@ 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;
@@ -26,7 +25,6 @@ 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;
@@ -56,6 +54,7 @@ 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);
@@ -74,11 +73,9 @@ public class AllMediaDocumentsFragment
// add padding to avoid content hidden behind system bars
ViewUtil.applyWindowInsets(recyclerView, true, false, true, true);
AllMediaDocumentsAdapter adapter = new AllMediaDocumentsAdapter(getContext(),
new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()),
this);
this.recyclerView.setAdapter(adapter);
adapter.setPlaybackViewModel(new ViewModelProvider(requireActivity()).get(AudioPlaybackViewModel.class));
this.recyclerView.setAdapter(new AllMediaDocumentsAdapter(getContext(),
new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()),
this));
this.recyclerView.setLayoutManager(gridManager);
this.recyclerView.setHasFixedSize(true);
@@ -243,13 +240,12 @@ 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(), playbackViewModel::stopByIds, playbackViewModel::stopByIds);
handleDeleteMessages(chatId, getListAdapter().getSelectedMedia());
mode.finish();
return true;
} else if (itemId == R.id.share) {
@@ -52,6 +52,7 @@ 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,7 +23,6 @@ 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;
@@ -65,9 +64,9 @@ public class ApplicationContext extends MultiDexApplication {
private Rpc rpc;
private DcContext dcContext;
private DcLocationManager dcLocationManager;
private DcEventCenter eventCenter;
private NotificationCenter notificationCenter;
public DcLocationManager dcLocationManager;
public DcEventCenter eventCenter;
public NotificationCenter notificationCenter;
private JobManager jobManager;
private int debugOnAvailableCount;
@@ -130,33 +129,6 @@ public class ApplicationContext extends MultiDexApplication {
}
}
/**
* Get DcLocationManager instance, waiting for initialization if necessary.
* This method is thread-safe and will block until initialization is complete.
*/
public DcLocationManager getLocationManager() {
ensureInitialized();
return dcLocationManager;
}
/**
* Get DcEventCenter instance, waiting for initialization if necessary.
* This method is thread-safe and will block until initialization is complete.
*/
public DcEventCenter getEventCenter() {
ensureInitialized();
return eventCenter;
}
/**
* Get NotificationCenter instance, waiting for initialization if necessary.
* This method is thread-safe and will block until initialization is complete.
*/
public NotificationCenter getNotificationCenter() {
ensureInitialized();
return notificationCenter;
}
@Override
public void onCreate() {
super.onCreate();
@@ -165,8 +137,7 @@ 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();
errorMsg += "\n" + LogViewFragment.grabLogcat();
String subject = "ArcaneChat " + BuildConfig.VERSION_NAME + "-" + BuildConfig.FLAVOR + " Crash Report";
String subject = "ArcaneChat " + BuildConfig.VERSION_NAME + " Crash Report";
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
@@ -197,29 +168,7 @@ public class ApplicationContext extends MultiDexApplication {
Util.runOnBackground(() -> {
synchronized (initLock) {
try {
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);
dcAccounts = new DcAccounts(new File(getFilesDir(), "accounts").getAbsolutePath());
Log.i(TAG, "DcAccounts created");
rpc = new Rpc(new FFITransport(dcAccounts.getJsonrpcInstance()));
Log.i(TAG, "Rpc created");
@@ -241,11 +190,7 @@ public class ApplicationContext extends MultiDexApplication {
}
}
// 2025-12-16: The setting was removed.
// Revert it to the default if it was changed in the past.
ac.setConfigInt("webxdc_realtime_enabled", 1);
// 2025-11-12: this is needed until core starts ignoring "delete_server_after" for chatmail
// 2025.11.12: this is needed until core starts ignoring "delete_server_after" for chatmail
if (ac.isChatmail()) {
ac.setConfig("delete_server_after", null); // reset
}
@@ -259,12 +204,29 @@ public class ApplicationContext extends MultiDexApplication {
}
dcContext = dcAccounts.getSelectedAccount();
notificationCenter = new NotificationCenter(this);
dcLocationManager = new DcLocationManager(this, dcContext);
eventCenter = new DcEventCenter(this);
// Mark as initialized before starting threads that depend on it
isInitialized = true;
initLock.notifyAll();
Log.i(TAG, "DcAccounts initialization complete");
dcLocationManager = new DcLocationManager(this); // depends on dcContext
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 bindPartial(@NonNull DcMsg messageRecord,
@NonNull DcChat dcChat,
@NonNull Set<DcMsg> batchSelected,
boolean pulseHighlight,
@NonNull Recipient conversationRecipient)
protected void bind(@NonNull DcMsg messageRecord,
@NonNull DcChat dcChat,
@NonNull Set<DcMsg> batchSelected,
boolean pulseHighlight,
@NonNull Recipient conversationRecipient)
{
this.messageRecord = messageRecord;
this.dcChat = dcChat;
@@ -126,8 +126,6 @@ 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,8 +6,6 @@ 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;
@@ -19,9 +17,7 @@ public interface BindableConversationItem extends Unbindable {
@NonNull GlideRequests glideRequests,
@NonNull Set<DcMsg> batchSelected,
@NonNull Recipient recipients,
boolean pulseHighlight,
@Nullable AudioPlaybackViewModel playbackViewModel,
AudioView.OnActionListener audioPlayPauseListener);
boolean pulseHighlight);
DcMsg getMessageRecord();
@@ -34,7 +34,6 @@ 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) {
@@ -72,9 +71,7 @@ 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,9 +81,8 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
if (item.getItemId() == android.R.id.home) {
getOnBackPressedDispatcher().onBackPressed();
return true;
switch (item.getItemId()) {
case android.R.id.home: super.onBackPressed(); return true;
}
return false;
@@ -22,7 +22,6 @@ 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;
@@ -30,7 +29,6 @@ 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;
@@ -58,7 +56,6 @@ 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;
@@ -84,11 +81,9 @@ 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;
@@ -118,7 +113,6 @@ 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);
@@ -129,8 +123,8 @@ public class ContactSelectionListFragment extends Fragment
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.contact_list, menu);
menu.findItem(R.id.menu_delete_selected).setVisible(!isMulti());
updateActionModeState(actionMode);
setCorrectMenuVisibility(menu);
actionMode.setTitle("1");
return true;
}
@@ -167,16 +161,19 @@ public class ContactSelectionListFragment extends Fragment
private void handleSelectAll() {
getContactSelectionListAdapter().selectAll();
updateActionModeState(actionMode);
updateActionModeTitle();
}
private void updateActionModeState(ActionMode actionMode) {
int size = getContactSelectionListAdapter().getActionModeSelection().size();
if (size == 0) {
actionMode.finish();
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);
} else {
actionMode.getMenu().findItem(R.id.menu_view_profile).setVisible(size == 1);
actionMode.setTitle(String.valueOf(size));
menu.findItem(R.id.menu_view_profile).setVisible(true);
}
}
@@ -229,15 +226,6 @@ 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);
}
@@ -253,7 +241,6 @@ 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);
@@ -279,16 +266,7 @@ public class ContactSelectionListFragment extends Fragment
@Override
public void onLoadFinished(Loader<DcContactsLoader.Ret> loader, DcContactsLoader.Ret 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);
}
}
((ContactSelectionListAdapter) recyclerView.getAdapter()).changeData(data);
}
@Override
@@ -302,7 +280,10 @@ public class ContactSelectionListFragment extends Fragment
{
if (handleActionMode) {
if (actionMode != null) {
updateActionModeState(actionMode);
Menu menu = actionMode.getMenu();
setCorrectMenuVisibility(menu);
updateActionModeTitle();
finishActionModeIfSelectionIsEmpty();
}
return;
}
@@ -322,14 +303,12 @@ 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);
@@ -342,11 +321,17 @@ public class ContactSelectionListFragment extends Fragment
if (actionMode == null) {
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback);
} else {
updateActionModeState(actionMode);
finishActionModeIfSelectionIsEmpty();
}
}
}
private void finishActionModeIfSelectionIsEmpty() {
if (getContactSelectionListAdapter().getActionModeSelection().size() == 0) {
actionMode.finish();
}
}
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
this.onContactSelectedListener = onContactSelectedListener;
}
@@ -370,7 +355,6 @@ 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,7 +25,6 @@ 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;
@@ -58,7 +57,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.annotation.StringRes;
@@ -66,12 +64,7 @@ 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;
@@ -82,7 +75,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.calls.CallUtil;
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
import org.thoughtcrime.securesms.components.AnimatingToggle;
import org.thoughtcrime.securesms.components.AttachmentTypeSelector;
import org.thoughtcrime.securesms.components.ComposeText;
@@ -92,8 +85,6 @@ 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;
@@ -112,19 +103,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;
@@ -146,13 +137,14 @@ 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, AudioView.OnActionListener {
implements ConversationFragment.ConversationFragmentListener,
AttachmentManager.AttachmentListener,
SearchView.OnQueryTextListener,
DcEventCenter.DcEventDelegate,
OnKeyboardShownListener,
InputPanel.Listener,
InputPanel.MediaListener
{
private static final String TAG = ConversationActivity.class.getSimpleName();
public static final String ACCOUNT_ID_EXTRA = "account_id";
@@ -192,12 +184,10 @@ 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;
@@ -206,11 +196,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);
@@ -227,10 +217,6 @@ 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) {
@@ -251,64 +237,17 @@ 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);
// 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);
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);
if (!isMultiUser()) {
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);
eventCenter.addObserver(DcContext.DC_EVENT_INCOMING_MSG, this);
eventCenter.addObserver(DcContext.DC_EVENT_MSG_READ, this);
}
SessionCommand updateContextCommand =
new SessionCommand("UPDATE_ACTIVITY_CONTEXT", Bundle.EMPTY);
mediaController.sendCustomCommand(updateContextCommand, commandArgs);
handleRelaying();
}
@Override
@@ -334,7 +273,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
});
setDcEventListener(); // reset event listener
handleRelaying();
if (fragment != null) {
@@ -361,13 +299,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
titleView.setTitle(glideRequests, dcChat);
try {
int accId = rpc.getSelectedAccountId();
DcHelper.getNotificationCenter(this).updateVisibleChat(accId, chatId);
} catch (RpcException e) {
Log.e(TAG, "rpc.getSelectedAccountId() failed", e);
}
DcHelper.getNotificationCenter(this).updateVisibleChat(dcContext.getAccountId(), chatId);
attachmentManager.onResume();
}
@@ -381,6 +313,7 @@ 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
@@ -400,11 +333,6 @@ 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();
}
@@ -461,7 +389,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
break;
case GROUP_EDIT:
dcChat = DcHelper.getContext(context).getChat(chatId);
dcChat = dcContext.getChat(chatId);
titleView.setTitle(glideRequests, dcChat);
break;
@@ -617,11 +545,8 @@ 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_audio_call) {
CallUtil.startCall(this, chatId, false);
return true;
} else if (itemId == R.id.menu_start_video_call) {
CallUtil.startCall(this, chatId, true);
} else if (itemId == R.id.menu_start_call) {
CallUtil.startCall(this, chatId);
return true;
} else if (itemId == R.id.menu_all_media) {
handleAllMedia();
@@ -643,6 +568,15 @@ 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();
@@ -665,7 +599,6 @@ 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);
@@ -677,13 +610,6 @@ 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);
@@ -693,7 +619,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void handleMuteNotifications() {
DcContext dcContext = DcHelper.getContext(context);
if(!dcChat.isMuted()) {
MuteDialog.show(this, duration -> {
dcContext.setChatMuteDuration(chatId, duration);
@@ -729,7 +654,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
AlertDialog dialog = new AlertDialog.Builder(this)
.setMessage(getString(R.string.ask_leave_group))
.setPositiveButton(leaveLabel, (d, which) -> {
DcHelper.getContext(context).removeContactFromChat(chatId, DcContact.DC_CONTACT_ID_SELF);
dcContext.removeContactFromChat(chatId, DcContact.DC_CONTACT_ID_SELF);
Toast.makeText(this, getString(R.string.done), Toast.LENGTH_SHORT).show();
})
.setNegativeButton(R.string.cancel, null)
@@ -738,7 +663,6 @@ 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);
@@ -754,7 +678,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) -> {
DcHelper.getContext(context).deleteChat(chatId);
dcContext.deleteChat(chatId);
DirectShareUtil.clearShortcut(this, chatId);
finish();
})
@@ -784,7 +708,6 @@ 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);
@@ -833,7 +756,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
} else {
Uri uri = uriList.isEmpty()? null : uriList.get(0);
DcHelper.getContext(context).setDraft(chatId, SendRelayedMessageUtil.createMessage(this, uri, ShareUtil.getSharedType(this), null, null, getSharedText(this)));
dcContext.setDraft(chatId, SendRelayedMessageUtil.createMessage(this, uri, ShareUtil.getSharedType(this), null, null, getSharedText(this)));
}
initializeDraft();
}
@@ -848,7 +771,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private ListenableFuture<Boolean> initializeDraft() {
isEditing = false;
final SettableFuture<Boolean> future = new SettableFuture<>();
DcMsg draft = DcHelper.getContext(context).getDraft(chatId);
DcMsg draft = dcContext.getDraft(chatId);
final String sharedText = ShareUtil.getSharedText(this);
if (!draft.isOk()) {
@@ -1009,15 +932,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void initializeBackground() {
int accId;
try {
accId = rpc.getSelectedAccountId();
} catch (RpcException e) {
Log.e(TAG, "rpc.getSelectedAccountId() failed", e);
return;
}
String backgroundImagePath = Prefs.getBackgroundImagePath(this, accId);
String backgroundImagePath = Prefs.getBackgroundImagePath(this, dcContext.getAccountId());
Drawable background;
if(!backgroundImagePath.isEmpty()) {
background = Drawable.createFromPath(backgroundImagePath);
@@ -1043,40 +958,33 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void initializeResources() {
int selectedAccId = DcHelper.getContext(context).getAccountId();
int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, selectedAccId);
if (accountId != selectedAccId) {
switchedProfile = true;
int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, dcContext.getAccountId());
if (accountId != dcContext.getAccountId()) {
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 = DcHelper.getContext(context).getChat(chatId);
dcChat = dcContext.getChat(chatId);
recipient = new Recipient(this, dcChat);
glideRequests = GlideApp.with(this);
setComposePanelVisibility(true);
setComposePanelVisibility();
initializeContactRequest();
}
private void setComposePanelVisibility(boolean isInitialization) {
private void setComposePanelVisibility() {
if (dcChat.canSend()) {
composePanel.setVisibility(View.VISIBLE);
attachmentManager.setHidden(false);
inputPanel.setSubjectVisible(!dcChat.isEncrypted());
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);
if (isInitialization) {
ViewUtil.forceApplyWindowInsets(findViewById(R.id.root_layout), true, false, true, false);
fragment.handleAddBottomInsets();
}
}
}
@@ -1113,11 +1021,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
return new SettableFuture<>(false);
}
return attachmentManager.setMedia(glideRequests, uri, null, mediaType, 0, 0, chatId, playbackViewModel);
return attachmentManager.setMedia(glideRequests, uri, null, mediaType, 0, 0, chatId);
}
private ListenableFuture<Boolean> setMedia(DcMsg msg, @NonNull MediaType mediaType) {
return attachmentManager.setMedia(glideRequests, Uri.fromFile(new File(msg.getFile())), msg, mediaType, 0, 0, chatId, playbackViewModel);
return attachmentManager.setMedia(glideRequests, Uri.fromFile(new File(msg.getFile())), msg, mediaType, 0, 0, chatId);
}
private void addAttachmentContactInfo(int contactId) {
@@ -1126,11 +1034,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
try {
byte[] vcard = rpc.makeVcard(rpc.getSelectedAccountId(), Collections.singletonList(contactId)).getBytes();
byte[] vcard = rpc.makeVcard(dcContext.getAccountId(), 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, "RPC failed", e);
Log.e(TAG, "makeVcard() failed", e);
}
}
@@ -1169,11 +1077,6 @@ 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;
@@ -1306,12 +1209,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (refreshFragment) {
fragment.reload(recipient, chatId);
try {
int accId = rpc.getSelectedAccountId();
DcHelper.getNotificationCenter(this).updateVisibleChat(accId, chatId);
} catch (RpcException e) {
Log.e(TAG, "rpc.getSelectedAccountId() failed", e);
}
DcHelper.getNotificationCenter(this).updateVisibleChat(dcContext.getAccountId(), chatId);
}
fragment.scrollToBottom();
@@ -1473,7 +1371,6 @@ 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());
@@ -1485,14 +1382,6 @@ 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) {
@@ -1603,7 +1492,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, DcHelper.getContext(context).getContact(msg.getFromId()));
Recipient author = new Recipient(this, dcContext.getContact(msg.getFromId()));
SlideDeck slideDeck = new SlideDeck();
if (msg.hasFile()) {
@@ -1626,7 +1515,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void handleEditMessage(DcMsg msg) {
isEditing = true;
Recipient author = new Recipient(this, DcHelper.getContext(context).getContact(msg.getFromId()));
Recipient author = new Recipient(this, dcContext.getContact(msg.getFromId()));
SlideDeck slideDeck = new SlideDeck();
String text = msg.getSummarytext(500);
@@ -1651,9 +1540,6 @@ 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)
@@ -1661,7 +1547,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
dcChat = dcContext.getChat(chatId);
titleView.setTitle(glideRequests, dcChat);
initializeSecurity(isSecureText, isDefaultSms);
setComposePanelVisibility(false);
setComposePanelVisibility();
initializeContactRequest();
} else if ((eventId == DcContext.DC_EVENT_INCOMING_MSG
|| eventId == DcContext.DC_EVENT_MSG_READ)
@@ -1740,7 +1626,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
String normQuery = query.trim();
searchResult = DcHelper.getContext(context).searchMsgs(chatId, normQuery);
searchResult = dcContext.searchMsgs(chatId, normQuery);
if(searchResult.length>0) {
searchResultPosition = searchResult.length - 1;
@@ -1771,7 +1657,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
messageRequestBottomView.setVisibility(View.VISIBLE);
messageRequestBottomView.setAcceptOnClickListener(v -> {
DcHelper.getContext(context).acceptChat(chatId);
dcContext.acceptChat(chatId);
messageRequestBottomView.setVisibility(View.GONE);
composePanel.setVisibility(View.VISIBLE);
});
@@ -1789,7 +1675,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);
DcHelper.getContext(context).blockChat(chatId);
dcContext.blockChat(chatId);
Bundle extras = new Bundle();
extras.putInt(ConversationListFragment.RELOAD_LIST, 1);
handleReturnToConversationList(extras);
@@ -34,8 +34,6 @@ 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;
@@ -61,7 +59,6 @@ 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>
@@ -100,8 +97,6 @@ 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) {
@@ -175,14 +170,6 @@ 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
*/
@@ -250,7 +237,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, playbackViewModel, audioPlayPauseListener);
holder.getItem().bind(getMsg(position), dcChat, glideRequests, batchSelected, recipient, pulseHighlight);
}
@Override
@@ -41,7 +41,6 @@ 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;
@@ -53,7 +52,6 @@ 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;
@@ -70,7 +68,6 @@ 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;
@@ -78,9 +75,6 @@ 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
{
@@ -103,21 +97,17 @@ 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);
rpc = DcHelper.getRpc(getContext());
this.dcContext = DcHelper.getContext(getContext());
DcEventCenter eventCenter = DcHelper.getEventCenter(getContext());
eventCenter.addObserver(DcContext.DC_EVENT_INCOMING_MSG, this);
@@ -146,7 +136,6 @@ 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());
@@ -165,32 +154,18 @@ 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 onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
public void onActivityCreated(Bundle bundle) {
super.onActivityCreated(bundle);
initializeResources();
initializeListAdapter();
}
private void setNoMessageText() {
private void setNoMessageText() {
DcChat dcChat = getListAdapter().getChat();
if(dcChat.isMultiUser()){
if (dcChat.isInBroadcast() || dcChat.isOutBroadcast()) {
@@ -217,28 +192,6 @@ 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);
@@ -255,13 +208,8 @@ public class ConversationFragment extends MessageSelectorFragment
@Override
public void onResume() {
super.onResume();
Util.runOnBackground(() -> {
try {
rpc.marknoticedChat(rpc.getSelectedAccountId(), (int) chatId);
} catch (RpcException e) {
Log.e(TAG, "RPC error", e);
}
});
Util.runOnBackground(() -> dcContext.marknoticedChat((int) chatId));
if (list.getAdapter() != null) {
list.getAdapter().notifyDataSetChanged();
}
@@ -333,10 +281,6 @@ 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);
@@ -344,12 +288,7 @@ public class ConversationFragment extends MessageSelectorFragment
dateDecoration = new StickyHeaderDecoration(adapter, false, false);
list.addItemDecoration(dateDecoration);
int freshMsgs = 0;
try {
freshMsgs = rpc.getFreshMsgCnt(rpc.getSelectedAccountId(), (int) chatId);
} catch (RpcException e) {
Log.e(TAG, "RPC error", e);
}
int freshMsgs = dcContext.getFreshMsgCount((int) chatId);
SetStartingPositionLinearLayoutManager layoutManager = (SetStartingPositionLinearLayoutManager) list.getLayoutManager();
if (startingPosition > -1) {
layoutManager.setStartingPosition(startingPosition);
@@ -453,14 +392,7 @@ public class ConversationFragment extends MessageSelectorFragment
}
public void handleClearChat() {
AudioPlaybackViewModel playbackViewModel =
new ViewModelProvider(requireActivity()).get(AudioPlaybackViewModel.class);
handleDeleteMessages(
(int) chatId,
getListAdapter().getMessageIds(),
playbackViewModel::stopByIds,
playbackViewModel::stopByIds);
handleDeleteMessages((int) chatId, getListAdapter().getMessageIds());
}
private ConversationAdapter getListAdapter() {
@@ -516,7 +448,6 @@ 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) {
@@ -545,13 +476,9 @@ public class ConversationFragment extends MessageSelectorFragment
private void handleForwardMessage(final Set<DcMsg> messageRecords) {
Intent composeIntent = new Intent();
int[] msgIds = DcMsg.msgSetToIds(messageRecords);
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);
}
setForwardingMessageIds(composeIntent, msgIds);
ConversationListRelayingActivity.start(this, composeIntent);
getActivity().overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out);
}
@SuppressLint("RestrictedApi")
@@ -577,7 +504,6 @@ 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);
@@ -594,14 +520,10 @@ public class ConversationFragment extends MessageSelectorFragment
private void handleToggleSave(final Set<DcMsg> messageRecords) {
DcMsg msg = getSelectedMessageRecord(messageRecords);
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);
if (msg.getSavedMsgId() != 0) {
dcContext.deleteMsgs(new int[]{msg.getSavedMsgId()});
} else {
dcContext.saveMsgs(new int[]{msg.getId()});
}
}
@@ -671,7 +593,7 @@ public class ConversationFragment extends MessageSelectorFragment
}
private void updateLocationButton() {
floatingLocationButton.setVisibility(DcHelper.getContext(getContext()).isSendingLocationsToChat((int) chatId)? View.VISIBLE : View.GONE);
floatingLocationButton.setVisibility(dcContext.isSendingLocationsToChat((int) chatId)? View.VISIBLE : View.GONE);
}
private void scrollAndHighlight(final int pos, boolean smooth) {
@@ -807,20 +729,16 @@ public class ConversationFragment extends MessageSelectorFragment
return;
}
ArrayList<Integer> ids = new ArrayList<>(lastPos - firstPos + 1);
int[] ids = new int[lastPos - firstPos + 1];
int index = 0;
for(int pos = firstPos; pos <= lastPos; pos++) {
DcMsg message = ((ConversationAdapter) list.getAdapter()).getMsg(pos);
if (message.getFromId() != DC_CONTACT_ID_SELF) {
ids.add(message.getId());
ids[index] = message.getId();
index++;
}
}
Util.runOnAnyBackgroundThread(() -> {
try {
rpc.markseenMsgs(rpc.getSelectedAccountId(), ids);
} catch (RpcException e) {
Log.e(TAG, "RPC error", e);
}
});
Util.runOnAnyBackgroundThread(() -> dcContext.markseenMsgs(ids));
}
private class ConversationFragmentItemClickListener implements ItemClickListener {
@@ -865,7 +783,7 @@ public class ConversationFragment extends MessageSelectorFragment
startActivity(intent);
}
else {
String self_mail = DcHelper.getContext(getContext()).getConfig("configured_mail_user");
String self_mail = dcContext.getConfig("configured_mail_user");
if (self_mail != null && !self_mail.isEmpty()
&& messageRecord.getText().contains(self_mail)
&& getListAdapter().getChat().isDeviceTalk()) {
@@ -904,7 +822,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, DcHelper.getContext(getContext()));
int start = DcMsg.getMessagePosition(original, dcContext);
intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, start);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
((ConversationActivity) getActivity()).hideSoftKeyboard();
@@ -920,7 +838,7 @@ public class ConversationFragment extends MessageSelectorFragment
@Override
public void onJumpToOriginalClicked(DcMsg messageRecord) {
jumpToOriginal(DcHelper.getContext(getContext()).getMsg(messageRecord.getOriginalMsgId()));
jumpToOriginal(dcContext.getMsg(messageRecord.getOriginalMsgId()));
}
@Override
@@ -939,12 +857,12 @@ public class ConversationFragment extends MessageSelectorFragment
@Override
public void onDownloadClicked(DcMsg messageRecord) {
DcHelper.getContext(getContext()).downloadFullMsg(messageRecord.getId());
dcContext.downloadFullMsg(messageRecord.getId());
}
@Override
public void onReactionClicked(DcMsg messageRecord) {
ReactionsDetailsFragment dialog = ReactionsDetailsFragment.newInstance(messageRecord.getId());
ReactionsDetailsFragment dialog = new ReactionsDetailsFragment(messageRecord.getId());
dialog.show(getActivity().getSupportFragmentManager(), null);
}
}
@@ -993,15 +911,12 @@ 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(), playbackViewModel::stopByIds, playbackViewModel::stopByIds);
handleDeleteMessages((int) chatId, getListAdapter().getSelectedItems());
return true;
} else if (itemId == R.id.menu_context_share) {
DcHelper.openForViewOrShare(getContext(), getSelectedMessageRecord(getListAdapter().getSelectedItems()).getId(), Intent.ACTION_SEND);
@@ -41,7 +41,8 @@ import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcMsg;
import org.thoughtcrime.securesms.calls.CallUtil;
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
import org.thoughtcrime.securesms.components.AudioView;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.BorderlessImageView;
import org.thoughtcrime.securesms.components.CallItemView;
@@ -51,8 +52,6 @@ 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;
@@ -72,6 +71,7 @@ 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,11 +181,9 @@ public class ConversationItem extends BaseConversationItem
@NonNull GlideRequests glideRequests,
@NonNull Set<DcMsg> batchSelected,
@NonNull Recipient recipients,
boolean pulseHighlight,
@Nullable AudioPlaybackViewModel playbackViewModel,
AudioView.OnActionListener audioPlayPauseListener)
boolean pulseHighlight)
{
bindPartial(messageRecord, dcChat, batchSelected, pulseHighlight, recipients);
bind(messageRecord, dcChat, batchSelected, pulseHighlight, recipients);
this.glideRequests = glideRequests;
this.showSender = ((dcChat.isMultiUser() || dcChat.isSelfTalk()) && !messageRecord.isOutgoing()) || messageRecord.getOverrideSenderName() != null;
@@ -206,7 +204,7 @@ public class ConversationItem extends BaseConversationItem
setGutterSizes(messageRecord, showSender);
setMessageShape(messageRecord);
setMediaAttributes(messageRecord, showSender, playbackViewModel, audioPlayPauseListener);
setMediaAttributes(messageRecord, showSender);
setBodyText(messageRecord);
setBubbleState(messageRecord);
setContactPhoto();
@@ -484,10 +482,24 @@ public class ConversationItem extends BaseConversationItem
}
private void setMediaAttributes(@NonNull DcMsg messageRecord,
boolean showSender,
AudioPlaybackViewModel playbackViewModel,
AudioView.OnActionListener audioPlayPauseListener)
boolean showSender)
{
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);
@@ -499,9 +511,12 @@ public class ConversationItem extends BaseConversationItem
//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().setPlaybackViewModel(playbackViewModel);
audioViewStub.get().setOnActionListener(audioPlayPauseListener);
audioViewStub.get().setAudio(new AudioSlide(context, messageRecord), duration);
audioViewStub.get().setOnClickListener(passthroughClickListener);
audioViewStub.get().setOnLongClickListener(passthroughClickListener);
@@ -985,9 +1000,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, callInfo.hasVideo);
CallUtil.openCall(getContext(), accId, chatId, callId, callInfo.sdpOffer);
} else {
CallUtil.startCall(getContext(), accId, chatId, callInfo.hasVideo);
CallUtil.startCall(getContext(), accId, chatId);
}
}
}
@@ -16,6 +16,8 @@
*/
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;
@@ -23,12 +25,10 @@ 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,7 +46,6 @@ 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;
@@ -77,7 +76,6 @@ import org.thoughtcrime.securesms.search.SearchFragment;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.ScreenLockUtil;
import org.thoughtcrime.securesms.util.ShareUtil;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.SendRelayedMessageUtil;
@@ -99,7 +97,6 @@ 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";
private static final int REQUEST_CODE_CONFIRM_CREDENTIALS_DELETE_PROFILE = ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS+1;
private ConversationListFragment conversationListFragment;
public TextView title;
@@ -111,11 +108,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
private ViewGroup fragmentContainer;
private ViewGroup selfAvatarContainer;
/** used to store temporarily scanned QR to pass it back to QrCodeHandler when ScreenLockUtil is used */
private String qrData = null;
/** used to store temporarily profile ID to delete after authorization is granted via ScreenLockUtil */
private int deleteProfileId = 0;
@Override
protected void onPreCreate() {
dynamicTheme = new DynamicNoActionBarTheme();
@@ -124,9 +116,36 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
@Override
protected void onCreate(Bundle icicle, boolean ready) {
addDeviceMessages(getIntent().getBooleanExtra(FROM_WELCOME, false));
if (getIntent().getIntExtra(ACCOUNT_ID_EXTRA, -1) <= 0) {
getIntent().putExtra(ACCOUNT_ID_EXTRA, DcHelper.getContext(this).getAccountId());
// 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();
}
// create view
@@ -150,41 +169,13 @@ 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, false));
selfAvatar.setOnClickListener(v -> AccountManager.getInstance().showSwitchAccountMenu(this));
findViewById(R.id.avatar_and_title).setOnClickListener(v -> {
if (!isRelayingMessageContent(this)) {
AccountManager.getInstance().showSwitchAccountMenu(this, false);
AccountManager.getInstance().showSwitchAccountMenu(this);
}
});
@@ -259,29 +250,24 @@ 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() {
int selectedAccId = DcHelper.getContext(this).getAccountId();
int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, selectedAccId);
DcContext dcContext = DcHelper.getContext(this);
int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, dcContext.getAccountId());
if (getIntent().getBooleanExtra(CLEAR_NOTIFICATIONS, false)) {
DcHelper.getNotificationCenter(this).removeAllNotifications(accountId);
}
if (accountId != selectedAccId) {
AccountManager.getInstance().switchAccount(this, accountId);
onProfileSwitched(accountId);
} else {
refreshAvatar();
refreshUnreadIndicator();
refreshTitle();
if (accountId != dcContext.getAccountId()) {
AccountManager.getInstance().switchAccountAndStartActivity(this, accountId);
}
refreshAvatar();
refreshUnreadIndicator();
refreshTitle();
handleOpenpgp4fpr();
if (isDirectSharing(this)) {
openConversation(getDirectSharingChatId(this), -1);
@@ -459,16 +445,13 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
startActivity(new Intent(this, ProxySettingsActivity.class));
return true;
} else if (itemId == android.R.id.home) {
getOnBackPressedDispatcher().onBackPressed();
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;
@@ -501,7 +484,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
};
SaveAttachmentTask saveTask = new SaveAttachmentTask(this);
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachments);
getOnBackPressedDispatcher().onBackPressed();
onBackPressed();
}
private void handleOpenpgp4fpr() {
@@ -514,7 +497,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
if (uri.getScheme().equalsIgnoreCase(OPENPGP4FPR) || Util.isInviteURL(uri)) {
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.handleOnlySecureJoinQr(uri.toString(), SecurejoinSource.ExternalLink, null);
qrCodeHandler.handleQrData(uri.toString(), SecurejoinSource.ExternalLink, null);
}
}
}
@@ -536,17 +519,15 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
searchToolbar.clearFocus();
final DcContext dcContext = DcHelper.getContext(this);
int fwdAccId = getForwardedMessageAccountId(this);
if (fwdAccId == dcContext.getAccountId() && dcContext.getChat(chatId).isSelfTalk()) {
if (isForwarding(this) && 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(ConversationActivity.ACCOUNT_ID_EXTRA, dcContext.getAccountId());
intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, chatId);
intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, startingPosition);
intent.putExtra(CHAT_ID_EXTRA, chatId);
intent.putExtra(STARTING_POSITION_EXTRA, startingPosition);
if (isRelayingMessageContent(this)) {
acquireRelayMessageContent(this, intent);
}
@@ -566,6 +547,15 @@ 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)) {
@@ -582,102 +572,14 @@ 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.loadChatlist();
}
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);
if (!result) {
deleteProfile(profileId);
}
}
private void deleteProfile(int profileId) {
DcAccounts accounts = DcHelper.getAccounts(this);
boolean selected = profileId == accounts.getSelectedAccount().getAccountId();
DcHelper.getNotificationCenter(this).removeAllNotifications(profileId);
accounts.removeAccount(profileId);
if (selected) {
DcContext selAcc = accounts.getSelectedAccount();
if (selAcc.isOk()) {
AccountManager.getInstance().switchAccount(this, selAcc.getAccountId());
onProfileSwitched(selAcc.getAccountId());
} else {
AccountManager.getInstance().switchAccountAndStartActivity(this, 0);
}
} else {
AccountManager.getInstance().showSwitchAccountMenu(this, false);
}
// title update needed to show "Delta Chat" in case there is only one profile left
refreshTitle();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK) return;
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
switch (requestCode) {
case IntentIntegrator.REQUEST_CODE:
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
qrData = scanResult.getContents();
qrCodeHandler.handleQrData(qrData, SecurejoinSource.Scan, SecurejoinUiPath.QrIcon);
break;
case ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS:
// QrCodeHandler requested user authorization before adding a relay
// and it was granted, so proceed to add the relay
if (qrData != null) {
qrCodeHandler.addRelay(qrData);
qrData = null;
}
break;
case REQUEST_CODE_CONFIRM_CREDENTIALS_DELETE_PROFILE:
if (deleteProfileId != 0) {
deleteProfile(deleteProfileId);
deleteProfileId = 0;
}
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.onScanPerformed(scanResult, SecurejoinUiPath.QrIcon);
break;
default:
break;
@@ -12,8 +12,6 @@ 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;
@@ -35,21 +33,6 @@ 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
@@ -80,7 +63,7 @@ public class ConversationListArchiveActivity extends PassphraseRequiredActionBar
int itemId = item.getItemId();
if (itemId == android.R.id.home) {
getOnBackPressedDispatcher().onBackPressed();
onBackPressed();
return true;
} else if (itemId == R.id.mark_as_read) {
DcHelper.getContext(this).marknoticedChat(DcChat.DC_CHAT_ID_ARCHIVED_LINK);
@@ -90,6 +73,20 @@ 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);
@@ -260,7 +260,7 @@ public class ConversationListFragment extends BaseConversationListFragment
});
}
public void loadChatlist() {
private void loadChatlist() {
int listflags = 0;
if (archive) {
listflags |= DcContext.DC_GCL_ARCHIVED_ONLY;
@@ -124,6 +124,7 @@ 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));
@@ -150,7 +151,7 @@ public class ConversationListItem extends RelativeLayout
thread.getVisibility()==DcChat.DC_CHAT_VISIBILITY_PINNED? R.drawable.ic_pinned_chatlist : 0, 0
);
setStatusIcons(thread, state);
setStatusIcons(thread.getVisibility(), state, unreadCount, thread.isContactRequest(), thread.isMuted() || chatId == DcChat.DC_CHAT_ID_ARCHIVED_LINK);
setBatchState(batchMode);
setBgColor(thread);
@@ -239,14 +240,14 @@ public class ConversationListItem extends RelativeLayout
return msgId;
}
private void setStatusIcons(ThreadRecord thread, int state) {
if (thread.getVisibility() == DcChat.DC_CHAT_VISIBILITY_ARCHIVED)
private void setStatusIcons(int visibility, int state, int unreadCount, boolean isContactRequest, boolean isMuted) {
if (visibility==DcChat.DC_CHAT_VISIBILITY_ARCHIVED)
{
archivedBadgeView.setVisibility(View.VISIBLE);
requestBadgeView.setVisibility(thread.isContactRequest() ? View.VISIBLE : View.GONE);
requestBadgeView.setVisibility(isContactRequest ? View.VISIBLE : View.GONE);
deliveryStatusIndicator.setNone();
}
else if (thread.isContactRequest()) {
else if (isContactRequest) {
requestBadgeView.setVisibility(View.VISIBLE);
archivedBadgeView.setVisibility(View.GONE);
deliveryStatusIndicator.setNone();
@@ -258,16 +259,14 @@ public class ConversationListItem extends RelativeLayout
if (state == DcMsg.DC_STATE_OUT_FAILED) {
deliveryStatusIndicator.setFailed();
} 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 if (state == DcMsg.DC_STATE_OUT_PREPARING) {
deliveryStatusIndicator.setPreparing();
} else if (state == DcMsg.DC_STATE_OUT_PENDING) {
deliveryStatusIndicator.setPending();
} else {
deliveryStatusIndicator.setNone();
}
@@ -279,12 +278,11 @@ public class ConversationListItem extends RelativeLayout
}
}
int unreadCount = thread.getUnreadCount();
if(unreadCount==0 || thread.isContactRequest()) {
if(unreadCount==0 || isContactRequest) {
unreadIndicator.setVisibility(View.GONE);
} else {
final int color;
if(thread.isMuted() || chatId == DcChat.DC_CHAT_ID_ARCHIVED_LINK){
if(isMuted){
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,8 +15,6 @@ 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;
@@ -63,11 +61,9 @@ public class ConversationUpdateItem extends BaseConversationItem
@NonNull GlideRequests glideRequests,
@NonNull Set<DcMsg> batchSelected,
@NonNull Recipient conversationRecipient,
boolean pulseUpdate,
@Nullable AudioPlaybackViewModel playbackViewModel,
AudioView.OnActionListener audioPlayPauseListener)
boolean pulseUpdate)
{
bindPartial(messageRecord, dcChat, batchSelected, pulseUpdate, conversationRecipient);
bind(messageRecord, dcChat, batchSelected, pulseUpdate, conversationRecipient);
setGenericInfoRecord(messageRecord);
}
@@ -21,7 +21,6 @@ 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;
@@ -53,6 +52,8 @@ 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;
@@ -60,6 +61,7 @@ public class CreateProfileActivity extends BaseActionBarActivity {
private EditText name;
private EditText statusView;
private boolean fromWelcome;
private boolean avatarChanged;
private boolean imageLoaded;
@@ -70,11 +72,12 @@ 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(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(!this.fromWelcome);
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp);
attachmentManager = new AttachmentManager(this, () -> {});
@@ -83,18 +86,6 @@ 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
@@ -109,7 +100,7 @@ public class CreateProfileActivity extends BaseActionBarActivity {
super.onOptionsItemSelected(item);
int itemId = item.getItemId();
if (itemId == android.R.id.home) {
getOnBackPressedDispatcher().onBackPressed();
onBackPressed();
return true;
} else if (itemId == R.id.menu_create_profile) {
updateProfile();
@@ -118,6 +109,17 @@ 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);
@@ -185,7 +187,13 @@ public class CreateProfileActivity extends BaseActionBarActivity {
// add padding to avoid content hidden behind system bars
ViewUtil.applyWindowInsets(container);
loginSuccessText.setVisibility(View.GONE);
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);
}
}
private void initializeProfileName() {
@@ -252,6 +260,11 @@ 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();
@@ -149,7 +149,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
initializeAvatarView();
SelectedContactsAdapter adapter = new SelectedContactsAdapter(this, GlideApp.with(this), broadcast);
SelectedContactsAdapter adapter = new SelectedContactsAdapter(this, GlideApp.with(this), broadcast, unencrypted);
adapter.setItemClickListener(this);
lv.setAdapter(adapter);
@@ -25,6 +25,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.appcompat.app.AlertDialog;
@@ -40,6 +41,7 @@ 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;
@@ -54,7 +56,6 @@ 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;
@@ -75,6 +76,7 @@ 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;
@@ -105,9 +107,9 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
Objects.requireNonNull(getSupportActionBar()).setTitle(R.string.onboarding_create_instant_account);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
boolean configured = DcHelper.getContext(this).isConfigured() == 1;
boolean fromWelcome = getIntent().getBooleanExtra(FROM_WELCOME, false);
if (configured) {
if (DcHelper.getContext(this).isConfigured() == 1) {
// 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();
@@ -115,11 +117,25 @@ 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;
}
finish();
return;
// if URI is unexpectedly null, then fallback to new profile creation
AccountManager.getInstance().beginAccountCreation(this);
}
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;
@@ -194,8 +210,10 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
case IntentIntegrator.REQUEST_CODE:
String qrRaw = data.getStringExtra(RegistrationQrActivity.QRDATA_EXTRA);
if (qrRaw == null) {
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
qrRaw = scanResult.getContents();
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if (scanResult != null && scanResult.getFormatName() != null) {
qrRaw = scanResult.getContents();
}
}
if (qrRaw != null) {
setProviderFromQr(qrRaw);
@@ -325,10 +343,7 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
signUpBtn.setOnClickListener(view -> createProfile());
Button otherServerButton = findViewById(R.id.use_other_server);
otherServerButton.setText(
TextUtil.markAsExternal(getString(R.string.instant_onboarding_other_server)));
otherServerButton.setOnClickListener((v) -> {
findViewById(R.id.use_other_server).setOnClickListener((v) -> {
IntentUtils.showInBrowser(this, INSTANCES_URL);
});
findViewById(R.id.login_button).setOnClickListener((v) -> {
@@ -352,11 +367,9 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
}
if (DEFAULT_CHATMAIL_HOST.equals(providerHost)) {
privacyPolicyBtn.setText(TextUtil.markAsExternal(
getString(R.string.instant_onboarding_agree_default2, providerHost)));
privacyPolicyBtn.setText(getString(R.string.instant_onboarding_agree_default2, providerHost));
} else {
privacyPolicyBtn.setText(TextUtil.markAsExternal(
getString(R.string.instant_onboarding_agree_instance, providerHost)));
privacyPolicyBtn.setText(getString(R.string.instant_onboarding_agree_instance, providerHost));
}
}
}
@@ -5,9 +5,6 @@ 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;
@@ -47,18 +44,6 @@ 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 : ""));
}
@@ -66,28 +51,6 @@ 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;
}
@@ -114,6 +77,15 @@ 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;
}
public static String grabLogcat() {
private static String grabLogcat(LogViewFragment fragment) {
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();
buildDescription(fragment) + "\n" + grabLogcat(fragment);
}
@Override
@@ -2,7 +2,6 @@ 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;
@@ -27,13 +26,13 @@ import org.thoughtcrime.securesms.util.StorageUtil;
import org.thoughtcrime.securesms.util.Util;
import java.util.Set;
import java.util.function.Consumer;
public abstract class MessageSelectorFragment
extends Fragment
implements DcEventCenter.DcEventDelegate
{
protected ActionMode actionMode;
protected DcContext dcContext;
protected abstract void setCorrectMenuVisibility(Menu menu);
@@ -49,7 +48,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(DcHelper.getContext(getContext()).getMsgInfo(dcMsg.getId()));
detailsText.setText(dcContext.getMsgInfo(dcMsg.getId()));
AlertDialog d = new AlertDialog.Builder(getActivity())
.setView(view)
@@ -59,15 +58,10 @@ public abstract class MessageSelectorFragment
}
protected void handleDeleteMessages(int chatId, final Set<DcMsg> messageRecords) {
handleDeleteMessages(chatId, DcMsg.msgSetToIds(messageRecords), null, null);
handleDeleteMessages(chatId, DcMsg.msgSetToIds(messageRecords));
}
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());
protected void handleDeleteMessages(int chatId, final int[] messageIds) {
DcChat dcChat = dcContext.getChat(chatId);
boolean canDeleteForAll = true;
if (dcChat.isEncrypted() && dcChat.canSend() && !dcChat.isSelfTalk()) {
@@ -85,24 +79,20 @@ 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, deleteForMeListener);
.setPositiveButton(positiveBtnLabel, (d, which) -> {
Util.runOnAnyBackgroundThread(() -> dcContext.deleteMsgs(messageIds));
if (actionMode != null) actionMode.finish();
});
if(canDeleteForAll) {
DialogInterface.OnClickListener deleteForAllListener = (d, which) -> {
builder.setNegativeButton(R.string.delete_for_everyone, (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);
@@ -144,7 +134,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, DcHelper.getContext(getContext())));
intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, DcMsg.getMessagePosition(dcMsg, dcContext));
startActivity(intent);
}
@@ -154,7 +144,6 @@ 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,7 +22,10 @@ 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;
@@ -34,8 +37,8 @@ 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;
/**
@@ -53,6 +56,53 @@ 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
@@ -90,13 +140,11 @@ public class NewConversationActivity extends ContactSelectionActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK) return;
switch (requestCode) {
case IntentIntegrator.REQUEST_CODE:
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.handleOnlySecureJoinQr(scanResult.getContents(), SecurejoinSource.Scan, SecurejoinUiPath.NewContact);
qrCodeHandler.onScanPerformed(scanResult, SecurejoinUiPath.NewContact);
break;
default:
break;
@@ -38,8 +38,8 @@ public class ProfileAdapter extends RecyclerView.Adapter
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 +140,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_BLOCKED) {
} else if (viewType == ITEM_LAST_SEEN || viewType == ITEM_INTRODUCED_BY || viewType == ITEM_ADDRESS) {
final ProfileTextItem item = (ProfileTextItem)layoutInflater.inflate(R.layout.profile_text_item_small, parent, false);
return new ViewHolder(item);
} else {
@@ -163,7 +163,11 @@ public class ProfileAdapter extends RecyclerView.Adapter
String addr = null;
if (contactId == DcContact.DC_CONTACT_ID_ADD_MEMBER) {
name = context.getString(R.string.group_add_members);
if (isOutBroadcast) {
name = context.getString(R.string.add_recipients);
} else {
name = context.getString(R.string.group_add_members);
}
}
else if (contactId == DcContact.DC_CONTACT_ID_QR_INVITE) {
name = context.getString(R.string.qrshow_title);
@@ -206,11 +210,14 @@ 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 && data.viewType != ITEM_BLOCKED;
boolean tintIcon = data.viewType != ITEM_INTRODUCED_BY;
item.set(data.label, data.icon, tintIcon);
if (data.viewType == ITEM_BLOCKED) {
if (data.viewType == ITEM_LAST_SEEN || data.viewType == ITEM_ADDRESS) {
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());
@@ -305,10 +312,6 @@ 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) {
@@ -346,6 +349,10 @@ 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();
@@ -310,27 +310,11 @@ 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);
List<Integer> deselected = data.getIntegerArrayListExtra(ContactMultiSelectionActivity.DESELECTED_CONTACTS_EXTRA);
if(selected == null) return;
Util.runOnAnyBackgroundThread(() -> {
if (deselected != null) {
// Remove members that were deselected
int[] members = dcContext.getChatContacts(chatId);
for (int contactId : deselected) {
for (int memberId : members) {
if (memberId == contactId) {
dcContext.removeContactFromChat(chatId, memberId);
break;
}
}
}
}
if (selected != null) {
// Add new members
for (Integer contactId : selected) {
if (contactId != null) {
dcContext.addContactToChat(chatId, contactId);
}
for (Integer contactId : selected) {
if (contactId!=null) {
dcContext.addContactToChat(chatId, contactId);
}
}
});
@@ -104,25 +104,26 @@ 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 if (getIntent().getData() != null) {
streamExtras.add(getIntent().getData());
} 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);
}
}
if (needsFilePermission(streamExtras)) {
@@ -264,7 +265,6 @@ 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), true, true, true, true, true, false);
ViewUtil.applyWindowInsets(findViewById(R.id.content_container));
}
webView.setWebViewClient(new WebViewClient() {
@@ -304,7 +304,7 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
// invite-links should be handled directly
String schema = url.split(":")[0].toLowerCase();
if (schema.equals("openpgp4fpr") || url.startsWith("https://" + Util.INVITE_DOMAIN + "/")) {
new QrCodeHandler(this).handleOnlySecureJoinQr(url, SecurejoinSource.InternalLink, null);
new QrCodeHandler(this).handleQrData(url, SecurejoinSource.InternalLink, null);
return true; // abort internal loading
}
@@ -15,7 +15,6 @@ 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;
@@ -23,6 +22,7 @@ import androidx.appcompat.app.AlertDialog;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import com.b44t.messenger.DcLot;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
@@ -33,11 +33,9 @@ import org.thoughtcrime.securesms.mms.AttachmentManager;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.qr.BackupTransferActivity;
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;
@@ -69,48 +67,16 @@ 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) -> startActivity(new Intent(this, InstantOnboardingActivity.class)));
findViewById(R.id.add_as_second_device_button).setOnClickListener((v) -> showSignInDialogWithPermission());
findViewById(R.id.signup_button).setOnClickListener((v) -> startInstantOnboardingActivity());
findViewById(R.id.add_as_second_device_button).setOnClickListener((v) -> startAddAsSecondDeviceActivity());
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();
@@ -131,7 +97,7 @@ public class WelcomeActivity extends BaseActionBarActivity implements DcEventCen
switch (item.getItemId()) {
case android.R.id.home:
getOnBackPressedDispatcher().onBackPressed();
onBackPressed();
return true;
}
@@ -165,6 +131,12 @@ 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)
@@ -348,14 +320,39 @@ public class WelcomeActivity extends BaseActionBarActivity implements DcEventCen
if (requestCode==IntentIntegrator.REQUEST_CODE) {
String qrRaw = data.getStringExtra(RegistrationQrActivity.QRDATA_EXTRA);
if (qrRaw == null) {
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if (scanResult == null || scanResult.getFormatName() == null) {
return; // aborted
}
qrRaw = scanResult.getContents();
}
if (!new QrCodeHandler(this).handleBackupQr(qrRaw)) {
new AlertDialog.Builder(this)
.setMessage(R.string.qraccount_qr_code_cannot_be_used)
.setPositiveButton(R.string.ok, null)
.show();
DcLot qrParsed = dcContext.checkQr(qrRaw);
switch (qrParsed.getState()) {
case DcContext.DC_QR_BACKUP2:
final String finalQrRaw = qrRaw;
new AlertDialog.Builder(this)
.setTitle(R.string.multidevice_receiver_title)
.setMessage(R.string.multidevice_receiver_scanning_ask)
.setPositiveButton(R.string.perm_continue, (dialog, which) -> startBackupTransfer(finalQrRaw))
.setNegativeButton(R.string.cancel, null)
.setCancelable(false)
.show();
break;
case DcContext.DC_QR_BACKUP_TOO_NEW:
new AlertDialog.Builder(this)
.setTitle(R.string.multidevice_receiver_title)
.setMessage(R.string.multidevice_receiver_needs_update)
.setPositiveButton(R.string.ok, null)
.show();
break;
default:
new AlertDialog.Builder(this)
.setMessage(R.string.qraccount_qr_code_cannot_be_used)
.setPositiveButton(R.string.ok, null)
.show();
break;
}
} else if (requestCode == PICK_BACKUP) {
Uri uri = (data != null ? data.getData() : null);
@@ -380,4 +377,14 @@ 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();
}
}
}
@@ -47,31 +47,18 @@ import chat.delta.rpc.RpcException;
public class AccountSelectionListFragment extends DialogFragment implements DcEventCenter.DcEventDelegate
{
private static final String TAG = AccountSelectionListFragment.class.getSimpleName();
private static final String ARG_SELECT_ONLY = "select_only";
private RecyclerView recyclerView;
private AccountSelectionListAdapter adapter;
private boolean selectOnly;
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) {
selectOnly = getArguments() != null && getArguments().getBoolean(ARG_SELECT_ONLY, false);
AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity())
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
.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);
@@ -106,20 +93,18 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv
DcAccounts accounts = DcHelper.getAccounts(getActivity());
int[] accountIds = accounts.getAll();
int[] ids = new int[(selectOnly? 0 : 1) + accountIds.length];
int[] ids = new int[accountIds.length + 1];
int j = 0;
for (int accountId : accountIds) {
ids[j++] = accountId;
}
if (!selectOnly) ids[j] = DC_CONTACT_ID_ADD_ACCOUNT;
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;
@@ -147,7 +132,7 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv
private void onContextItemSelected(MenuItem item, int accountId) {
int itemId = item.getItemId();
if (itemId == R.id.delete) {
onDeleteProfile(accountId);
onDeleteAccount(accountId);
} else if (itemId == R.id.menu_mute_notifications) {
onToggleMute(accountId);
} else if (itemId == R.id.menu_set_tag) {
@@ -182,7 +167,8 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv
}
private void onSetTag(int accountId) {
ConversationListActivity activity = (ConversationListActivity)requireActivity();
Activity activity = getActivity();
if (activity == null) return;
AccountSelectionListFragment.this.dismiss();
DcContext dcContext = DcHelper.getAccounts(activity).getAccount(accountId);
@@ -198,15 +184,16 @@ 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, selectOnly);
AccountManager.getInstance().showSwitchAccountMenu(activity);
})
.setNegativeButton(R.string.cancel, (d, b) -> AccountManager.getInstance().showSwitchAccountMenu(activity, selectOnly))
.setNegativeButton(R.string.cancel, (d, b) -> AccountManager.getInstance().showSwitchAccountMenu(activity))
.show();
}
private void onDeleteProfile(int accountId) {
private void onDeleteAccount(int accountId) {
Activity activity = getActivity();
AccountSelectionListFragment.this.dismiss();
ConversationListActivity activity = (ConversationListActivity)requireActivity();
if (activity == null) return;
DcAccounts accounts = DcHelper.getAccounts(activity);
Rpc rpc = DcHelper.getRpc(activity);
@@ -241,8 +228,23 @@ 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, selectOnly))
.setPositiveButton(R.string.delete, (d2, w2) -> activity.onDeleteProfile(accountId))
.setNegativeButton(R.string.cancel, (d, which) -> AccountManager.getInstance().showSwitchAccountMenu(activity))
.setPositiveButton(R.string.delete, (d2, which2) -> {
boolean selected = accountId == accounts.getSelectedAccount().getAccountId();
DcHelper.getNotificationCenter(activity).removeAllNotifications(accountId);
accounts.removeAccount(accountId);
if (selected) {
DcContext selAcc = accounts.getSelectedAccount();
AccountManager.getInstance().switchAccountAndStartActivity(activity, selAcc.isOk()? selAcc.getAccountId() : 0);
} else {
AccountManager.getInstance().showSwitchAccountMenu(activity);
}
// title update needed to show "Delta Chat" in case there is only one profile left
if (activity instanceof ConversationListActivity) {
((ConversationListActivity)activity).refreshTitle();
}
})
.show();
Util.redPositiveButton(dialog);
}
@@ -258,14 +260,13 @@ 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().switchAccount(activity, accountId);
activity.onProfileSwitched(accountId);
AccountManager.getInstance().switchAccountAndStartActivity(activity, accountId);
}
}
}
@@ -87,6 +87,10 @@ public class AccountSelectionListItem extends LinearLayout {
}
addrOrTag = dcContext.getConfig(CONFIG_PRIVATE_TAG);
if ("".equals(addrOrTag) && !dcContext.isChatmail()) {
addrOrTag = self.getAddr();
}
unreadCount = dcContext.getFreshMsgs().length;
enableSwitch.setChecked(dcContext.isEnabled());
@@ -0,0 +1,356 @@
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,14 +43,12 @@ 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")
@@ -60,9 +58,7 @@ 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);
@@ -98,7 +94,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 + query + hash);
webView.loadUrl(url + hash);
}).onAnyDenied(this::finish)
.execute();
}
@@ -172,7 +168,7 @@ public class CallActivity extends WebViewActivity implements DcEventCenter.DcEve
@JavascriptInterface
public void startCall(String payload) {
try {
callId = rpc.placeOutgoingCall(accId, chatId, payload, hasVideo);
callId = rpc.placeOutgoingCall(accId, chatId, payload);
} catch (RpcException e) {
Log.e(TAG, "Error", e);
}
@@ -18,29 +18,28 @@ import java.nio.charset.StandardCharsets;
public class CallUtil {
private static final String TAG = CallUtil.class.getSimpleName();
public static void startCall(Activity activity, int chatId, boolean hasVideo) {
public static void startCall(Activity activity, int chatId) {
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, hasVideo);
startCall(activity, accId, chatId);
})
.execute();
}
public static void startCall(Context context, int accId, int chatId, boolean hasVideo) {
public static void startCall(Context context, int accId, int chatId) {
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, boolean hasVideo) {
public static void openCall(Context context, int accId, int chatId, int callId, String payload) {
String base64 = Base64.encodeToString(payload.getBytes(StandardCharsets.UTF_8), Base64.NO_WRAP);
String hash = "";
try {
@@ -54,7 +53,6 @@ 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);
}
@@ -0,0 +1,301 @@
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,14 +67,10 @@ 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_audio_call : R.string.incoming_audio_call);
title.setText(isOutgoing? R.string.outgoing_call : R.string.incoming_call);
}
icon.setImageResource(callInfo.hasVideo? R.drawable.ic_videocam_white_24dp : R.drawable.baseline_call_24);
int[] attrs;
if (isOutgoing) {
attrs = new int[]{
@@ -15,51 +15,39 @@ 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(context, null);
init(null);
}
public ConversationItemFooter(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
init(attrs);
}
public ConversationItemFooter(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
init(attrs);
}
private void init(Context context, @Nullable AttributeSet attrs) {
this.context = context;
this.rpc = DcHelper.getRpc(context);
private void init(@Nullable AttributeSet attrs) {
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);
@@ -91,33 +79,13 @@ public class ConversationItemFooter extends LinearLayout {
}
locationIndicatorView.setVisibility(messageRecord.hasLocation() ? View.VISIBLE : View.GONE);
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);
presentDeliveryStatus(messageRecord);
}
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);
@@ -138,7 +106,7 @@ public class ConversationItemFooter extends LinearLayout {
}
}
private void presentDeliveryStatus(@NonNull DcMsg messageRecord, boolean isOutChannel) {
private void presentDeliveryStatus(@NonNull DcMsg messageRecord) {
// 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;
@@ -146,7 +114,7 @@ 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 || isOutChannel) deliveryStatusView.setNone();
else if (!messageRecord.isOutgoing() || isCall) deliveryStatusView.setNone();
else if (messageRecord.isRemoteRead()) deliveryStatusView.setRead();
else if (messageRecord.isDelivered()) deliveryStatusView.setSent();
else if (messageRecord.isPreparing()) deliveryStatusView.setPreparing();
@@ -202,8 +202,7 @@ public class InputPanel extends ConstraintLayout
public void setSubjectVisible(boolean visible) {
subjectText.setVisibility(visible ? View.VISIBLE : View.GONE);
// don't make it visible if visible is false to avoid showing it while recording audio and an event triggers setSubjectVisible(false)
if (visible) emojiToggle.setVisibility(View.GONE);
emojiToggle.setVisibility(!visible ? View.VISIBLE : View.GONE);
}
public String getSubject() {
@@ -11,9 +11,6 @@ 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;
@@ -22,7 +19,6 @@ 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);
@@ -76,22 +72,8 @@ public class RemovableEditableMediaView extends FrameLayout {
return current;
}
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 setRemoveClickListener(View.OnClickListener listener) {
this.remove.setOnClickListener(listener);
}
public void setEditClickListener(View.OnClickListener listener) {
@@ -1,58 +0,0 @@
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;
}
}
@@ -1,243 +0,0 @@
package org.thoughtcrime.securesms.components.audioplay;
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;
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 @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 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) {
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();
super.onCleared();
}
}
@@ -1,311 +0,0 @@
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;
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;
private Uri audioUri;
private AudioPlaybackViewModel viewModel;
private final Observer<AudioPlaybackState> stateObserver = this::onPlaybackStateChanged;
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);
this.timestamp.setText("00:00");
// 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() {
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) {
timestamp.setText(DateUtils.getFormatedDuration(progress));
}
}
@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);
}
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);
}
// ViewModel is used directly for simplicity, since there is no reuse yet
this.viewModel = viewModel;
if (viewModel != null) {
viewModel.getPlaybackState().observeForever(stateObserver);
}
}
public void setAudio(final @NonNull AudioSlide audio, int duration)
{
msgId = audio.getDcMsgId();
audioUri = audio.getUri();
playPauseButton.setImageDrawable(playDrawable);
seekBar.setEnabled(true);
seekBar.setProgress(0);
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.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 = (int) state.getDuration();
int position = (int) state.getCurrentPosition();
if (duration > 0) {
seekBar.setMax(duration);
seekBar.setProgress(position);
timestamp.setText(DateUtils.getFormatedDuration(position));
}
}
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);
}
}
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;
}
}
}
@@ -5,6 +5,7 @@ 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;
@@ -13,6 +14,7 @@ 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;
@@ -121,6 +123,10 @@ 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 {
@@ -128,8 +134,12 @@ public class AccountManager {
}
activity.finishAffinity();
if (destAccountId == 0) {
activity.startActivity(new Intent(activity, WelcomeActivity.class));
if (destAccountId==0) {
Intent intent = new Intent(activity, WelcomeActivity.class);
if (backupQr != null) {
intent.putExtra(WelcomeActivity.BACKUP_QR_EXTRA, backupQr);
}
activity.startActivity(intent);
} else {
activity.startActivity(new Intent(activity.getApplicationContext(), ConversationListActivity.class));
}
@@ -137,21 +147,12 @@ public class AccountManager {
// ui
public void showSwitchAccountMenu(ConversationListActivity activity, boolean selectOnly) {
AccountSelectionListFragment dialog = AccountSelectionListFragment.newInstance(selectOnly);
public void showSwitchAccountMenu(Activity activity) {
AccountSelectionListFragment dialog = new AccountSelectionListFragment();
dialog.show(((FragmentActivity) activity).getSupportFragmentManager(), null);
}
public void addAccountFromSecondDevice(Activity activity, String 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);
switchAccountAndStartActivity(activity, 0, backupQr);
}
}
@@ -164,23 +164,6 @@ 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();
@@ -222,7 +205,20 @@ public class DcEventCenter {
return 0;
}
handleLogging(event);
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;
}
if (accountId != context.getDcContext().getAccountId()) {
return 0;
@@ -60,6 +60,7 @@ public class DcHelper {
public static final String CONFIG_MEDIA_QUALITY = "media_quality";
public static final String CONFIG_PROXY_ENABLED = "proxy_enabled";
public static final String CONFIG_PROXY_URL = "proxy_url";
public static final String CONFIG_WEBXDC_REALTIME_ENABLED = "webxdc_realtime_enabled";
public static final String CONFIG_PRIVATE_TAG = "private_tag";
public static final String CONFIG_STATS_SENDING = "stats_sending";
public static final String CONFIG_STATS_ID = "stats_id";
@@ -77,11 +78,11 @@ public class DcHelper {
}
public static DcEventCenter getEventCenter(@NonNull Context context) {
return ApplicationContext.getInstance(context).getEventCenter();
return ApplicationContext.getInstance(context).eventCenter;
}
public static NotificationCenter getNotificationCenter(@NonNull Context context) {
return ApplicationContext.getInstance(context).getNotificationCenter();
return ApplicationContext.getInstance(context).notificationCenter;
}
public static boolean isConfigured(Context context) {
@@ -210,6 +211,8 @@ 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));
@@ -221,10 +224,6 @@ 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));
}
public static File getImexDir() {
@@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.qr.QrCodeHandler;
import org.thoughtcrime.securesms.util.ViewUtil;
import chat.delta.rpc.types.SecurejoinSource;
import chat.delta.rpc.types.SecurejoinUiPath;
public class NewContactActivity extends PassphraseRequiredActionBarActivity
@@ -105,10 +104,10 @@ public class NewContactActivity extends PassphraseRequiredActionBarActivity
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == IntentIntegrator.REQUEST_CODE) {
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
if (requestCode == IntentIntegrator.REQUEST_CODE) {
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.handleOnlySecureJoinQr(scanResult.getContents(), SecurejoinSource.Scan, SecurejoinUiPath.NewContact);
qrCodeHandler.onScanPerformed(scanResult, SecurejoinUiPath.NewContact);
}
}
}
@@ -101,10 +101,6 @@ public class ThreadRecord {
return spannable;
}
public boolean isOutChannel() {
return recipient.getChat() != null && recipient.getChat().isOutBroadcast();
}
public int getUnreadCount() {
return unreadCount;
}
@@ -16,8 +16,6 @@ import java.util.Observer;
import static android.content.Context.BIND_AUTO_CREATE;
import com.b44t.messenger.DcContext;
public class DcLocationManager implements Observer {
private static final String TAG = DcLocationManager.class.getSimpleName();
@@ -42,10 +40,10 @@ public class DcLocationManager implements Observer {
}
};
public DcLocationManager(Context context, DcContext dcContext) {
public DcLocationManager(Context context) {
this.context = context.getApplicationContext();
DcLocation.getInstance().addObserver(this);
if (dcContext.isSendingLocationsToChat(0)) {
if (DcHelper.getContext(context).isSendingLocationsToChat(0)) {
startLocationEngine();
}
}
@@ -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.addRemoveClickListener(new RemoveButtonListener());
removableMediaView.setRemoveClickListener(new RemoveButtonListener());
removableMediaView.setEditClickListener(new EditButtonListener());
thumbnail.setOnClickListener(new ThumbnailClickListener());
}
@@ -152,6 +152,8 @@ public class AttachmentManager {
markGarbage(getSlideUri());
slide = Optional.absent();
audioView.cleanup();
}
}
@@ -233,8 +235,7 @@ public class AttachmentManager {
@NonNull final MediaType mediaType,
final int width,
final int height,
final int chatId,
AudioPlaybackViewModel playbackViewModel)
final int chatId)
{
inflateStub();
@@ -284,12 +285,26 @@ public class AttachmentManager {
setAttachmentPresent(true);
if (slide.hasAudio()) {
audioView.setPlaybackViewModel(playbackViewModel);
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);
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));
@@ -458,7 +473,7 @@ public class AttachmentManager {
public static void selectLocation(Activity activity, int chatId) {
ApplicationContext applicationContext = ApplicationContext.getInstance(activity);
DcLocationManager dcLocationManager = applicationContext.getLocationManager();
DcLocationManager dcLocationManager = applicationContext.dcLocationManager;
if (DcHelper.getContext(applicationContext).isSendingLocationsToChat(chatId)) {
dcLocationManager.stopSharingLocation(chatId);
@@ -61,8 +61,6 @@ 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;
@@ -166,7 +164,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, boolean hasVideo) {
public PendingIntent getOpenCallIntent(ChatData chatData, int callId, String payload, boolean autoAccept) {
final Intent chatIntent = new Intent(context, ConversationActivity.class)
.putExtra(ConversationActivity.ACCOUNT_ID_EXTRA, chatData.accountId)
.putExtra(ConversationActivity.CHAT_ID_EXTRA, chatData.chatId)
@@ -186,7 +184,6 @@ 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)
@@ -430,13 +427,6 @@ 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();
@@ -444,7 +434,7 @@ public class NotificationCenter {
String notificationChannel = getCallNotificationChannel(notificationManager, chatData, name);
PendingIntent declineCallIntent = getDeclineCallIntent(chatData, callId);
PendingIntent openCallIntent = getOpenCallIntent(chatData, callId, payload, false, hasVideo);
PendingIntent openCallIntent = getOpenCallIntent(chatData, callId, payload, false);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, notificationChannel)
.setSmallIcon(R.drawable.icon_notification)
@@ -469,7 +459,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, hasVideo)).build());
getOpenCallIntent(chatData, callId, payload, true)).build());
Bitmap bitmap = getAvatar(dcChat);
if (bitmap != null) {
@@ -7,6 +7,7 @@ import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_MVBOX_MOVE;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_ONLY_FETCH_MVBOX;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_STATS_SENDING;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SHOW_EMAILS;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_WEBXDC_REALTIME_ENABLED;
import android.content.Context;
import android.content.Intent;
@@ -16,6 +17,7 @@ import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -32,8 +34,8 @@ import org.thoughtcrime.securesms.relay.RelayListActivity;
import org.thoughtcrime.securesms.StatsSending;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.proxy.ProxySettingsActivity;
import org.thoughtcrime.securesms.util.IntentUtils;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.ScreenLockUtil;
import org.thoughtcrime.securesms.util.StreamUtil;
import org.thoughtcrime.securesms.util.Util;
@@ -55,6 +57,7 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment
CheckBoxPreference multiDeviceCheckbox;
CheckBoxPreference mvboxMoveCheckbox;
CheckBoxPreference onlyFetchMvboxCheckbox;
CheckBoxPreference webxdcRealtimeCheckbox;
@Override
public void onCreate(Bundle paramBundle) {
@@ -120,6 +123,15 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment
}));
}
webxdcRealtimeCheckbox = (CheckBoxPreference) this.findPreference("pref_webxdc_realtime_enabled");
if (webxdcRealtimeCheckbox != null) {
webxdcRealtimeCheckbox.setOnPreferenceChangeListener((preference, newValue) -> {
boolean enabled = (Boolean) newValue;
dcContext.setConfigInt(CONFIG_WEBXDC_REALTIME_ENABLED, enabled? 1 : 0);
return true;
});
}
Preference submitDebugLog = this.findPreference("pref_view_log");
if (submitDebugLog != null) {
submitDebugLog.setOnPreferenceClickListener(new ViewLogListener());
@@ -158,10 +170,7 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment
Preference relayListBtn = this.findPreference("pref_relay_list_button");
if (relayListBtn != null) {
relayListBtn.setOnPreferenceClickListener(((preference) -> {
boolean result = ScreenLockUtil.applyScreenLock(requireActivity(), getString(R.string.transports), getString(R.string.enter_system_secret_to_continue), REQUEST_CODE_CONFIRM_CREDENTIALS_ACCOUNT);
if (!result) {
openRelayListActivity();
}
openRelayListActivity();
return true;
}));
}
@@ -189,6 +198,7 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment
multiDeviceCheckbox.setChecked(0!=dcContext.getConfigInt(CONFIG_BCC_SELF));
mvboxMoveCheckbox.setChecked(0!=dcContext.getConfigInt(CONFIG_MVBOX_MOVE));
onlyFetchMvboxCheckbox.setChecked(0!=dcContext.getConfigInt(CONFIG_ONLY_FETCH_MVBOX));
webxdcRealtimeCheckbox.setChecked(0!=dcContext.getConfigInt(CONFIG_WEBXDC_REALTIME_ENABLED));
}
@Override
@@ -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 = 512;
public static final int AVATAR_SIZE = 640;
public static void setGroupAvatar(Context context, int chatId, Bitmap bitmap) {
DcContext dcContext = DcHelper.getContext(context);
@@ -122,8 +122,26 @@ public class PersistentBlobProvider {
}
public Uri createForExternal(@NonNull Context context, @NonNull String mimeType) throws IOException, IllegalStateException, NullPointerException {
File target = new File(getExternalDir(context), System.currentTimeMillis() + "." + getExtensionFromMimeType(mimeType));
return FileProviderUtil.getUriFor(context, target);
String filename = System.currentTimeMillis() + "." + getExtensionFromMimeType(mimeType);
// Try external cache first
try {
File externalDir = getExternalDir(context);
File target = new File(externalDir, filename);
return FileProviderUtil.getUriFor(context, target);
} catch (IllegalArgumentException e) {
// FileProvider doesn't support the external cache path (e.g., on removable SD card).
// Note: getExternalDir() already falls back to internal cache when external cache is null,
// but when external cache exists on a removable SD card, FileProvider may reject it.
// In that case, we explicitly use internal cache which FileProvider always supports.
Log.w(TAG, "FileProvider doesn't support external cache path, falling back to internal cache", e);
File internalDir = context.getCacheDir();
if (internalDir == null) {
throw new IOException("no cache directory available");
}
File target = new File(internalDir, filename);
return FileProviderUtil.getUriFor(context, target);
}
}
public boolean delete(@NonNull Context context, @NonNull Uri uri) {
@@ -11,7 +11,6 @@ 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;
@@ -57,8 +56,6 @@ 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);
@@ -92,15 +89,6 @@ 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
@@ -119,13 +107,18 @@ 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) {
getOnBackPressedDispatcher().onBackPressed();
finishOrAskToFinish();
return true;
} else if (itemId == R.id.troubleshooting) {
DcHelper.openHelp(this, "#multiclient");
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.qr;
import android.Manifest;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
@@ -26,10 +27,11 @@ import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.client.android.Intents;
import com.google.zxing.common.HybridBinarizer;
import org.thoughtcrime.securesms.BaseActionBarActivity;
import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.NewConversationActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.contacts.NewContactActivity;
@@ -42,6 +44,9 @@ import org.thoughtcrime.securesms.util.ViewUtil;
import java.io.FileNotFoundException;
import java.io.InputStream;
import chat.delta.rpc.types.SecurejoinSource;
import chat.delta.rpc.types.SecurejoinUiPath;
public class QrActivity extends BaseActionBarActivity implements View.OnClickListener {
private final static String TAG = QrActivity.class.getSimpleName();
@@ -149,7 +154,8 @@ public class QrActivity extends BaseActionBarActivity implements View.OnClickLis
} else if (itemId == R.id.load_from_image) {
AttachmentManager.selectImage(this, REQUEST_CODE_IMAGE);
} else if (itemId == R.id.paste) {
setQrResult(Util.getTextFromClipboard(this));
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.handleQrData(Util.getTextFromClipboard(this), SecurejoinSource.Clipboard, getUiPath());
}
return false;
@@ -197,10 +203,10 @@ public class QrActivity extends BaseActionBarActivity implements View.OnClickLis
RGBLuminanceSource source = new RGBLuminanceSource(width, height, pixels);
BinaryBitmap bBitmap = new BinaryBitmap(new HybridBinarizer(source));
MultiFormatReader reader = new MultiFormatReader();
try {
Result result = reader.decode(bBitmap);
setQrResult(result.getText());
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.handleQrData(result.getText(), SecurejoinSource.ImageLoaded, getUiPath());
} catch (NotFoundException e) {
Log.e(TAG, "decode exception", e);
Toast.makeText(this, getString(R.string.qrscan_failed), Toast.LENGTH_LONG).show();
@@ -213,11 +219,19 @@ public class QrActivity extends BaseActionBarActivity implements View.OnClickLis
}
}
private void setQrResult(String qrData) {
Intent intent = new Intent();
intent.putExtra(Intents.Scan.RESULT, qrData);
setResult(RESULT_OK, intent);
finish();
private SecurejoinUiPath getUiPath() {
SecurejoinUiPath uiPath = null;
ComponentName caller = this.getCallingActivity();
if (caller != null) {
if (caller.getClassName().equals(NewConversationActivity.class.getName())) {
uiPath = SecurejoinUiPath.NewContact;
} else if (caller.getClassName().equals(ConversationListActivity.class.getName())
// RoutingActivity is an alias for ConversationListActivity
|| caller.getClassName().endsWith(".RoutingActivity")) {
uiPath = SecurejoinUiPath.QrIcon;
}
}
return uiPath;
}
@Override
@@ -19,7 +19,6 @@ import org.thoughtcrime.securesms.connect.AccountManager;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.relay.RelayListActivity;
import org.thoughtcrime.securesms.util.IntentUtils;
import org.thoughtcrime.securesms.util.ScreenLockUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.views.ProgressDialog;
@@ -54,186 +53,118 @@ public class QrCodeHandler {
accId = dcContext.getAccountId();
}
/** Process only QR about getting in contact or joining chats */
public void handleOnlySecureJoinQr(String rawString, SecurejoinSource source, SecurejoinUiPath uiPath) {
final DcLot qrParsed = dcContext.checkQr(rawString);
if (!handleSecureJoinQr(qrParsed, rawString, source, uiPath)) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
handleDefault(builder, rawString, qrParsed);
builder.create().show();
public void onScanPerformed(IntentResult scanResult, SecurejoinUiPath uipath) {
if (scanResult == null || scanResult.getFormatName() == null) {
return; // aborted
}
handleQrData(scanResult.getContents(), SecurejoinSource.Scan, uipath);
}
}
private boolean handleSecureJoinQr(DcLot qrParsed, String rawString, SecurejoinSource source, SecurejoinUiPath uiPath) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
String name = dcContext.getContact(qrParsed.getId()).getDisplayName();
switch (qrParsed.getState()) {
case DcContext.DC_QR_ASK_VERIFYCONTACT:
case DcContext.DC_QR_ASK_VERIFYGROUP:
case DcContext.DC_QR_ASK_JOIN_BROADCAST:
showVerifyContactOrGroup(builder, rawString, qrParsed, name, source, uiPath);
break;
case DcContext.DC_QR_WITHDRAW_VERIFYCONTACT:
case DcContext.DC_QR_WITHDRAW_VERIFYGROUP:
case DcContext.DC_QR_WITHDRAW_JOINBROADCAST:
String message = qrParsed.getState() == DcContext.DC_QR_WITHDRAW_VERIFYCONTACT ? activity.getString(R.string.withdraw_verifycontact_explain)
: qrParsed.getState() == DcContext.DC_QR_WITHDRAW_VERIFYCONTACT ? activity.getString(R.string.withdraw_verifygroup_explain, qrParsed.getText1())
: activity.getString(R.string.withdraw_joinbroadcast_explain, qrParsed.getText1());
builder.setTitle(R.string.qrshow_title);
builder.setMessage(message);
builder.setNeutralButton(R.string.reset, (dialog, which) -> {
dcContext.setConfigFromQr(rawString);
});
builder.setPositiveButton(R.string.ok, null);
Util.redButton(builder.show(), AlertDialog.BUTTON_NEUTRAL);
return true;
case DcContext.DC_QR_REVIVE_VERIFYCONTACT:
case DcContext.DC_QR_REVIVE_VERIFYGROUP:
case DcContext.DC_QR_REVIVE_JOINBROADCAST:
builder.setTitle(R.string.qrshow_title);
builder.setMessage(activity.getString(R.string.revive_verifycontact_explain));
builder.setNeutralButton(R.string.revive_qr_code, (dialog, which) -> {
dcContext.setConfigFromQr(rawString);
});
builder.setPositiveButton(R.string.ok, null);
break;
case DcContext.DC_QR_FPR_WITHOUT_ADDR:
showVerifyFingerprintWithoutAddress(builder, qrParsed);
break;
case DcContext.DC_QR_FPR_MISMATCH:
showFingerPrintError(builder, name);
break;
case DcContext.DC_QR_FPR_OK:
case DcContext.DC_QR_ADDR:
showFingerprintOrQrSuccess(builder, qrParsed, name);
break;
default:
return false;
}
builder.create().show();
return true;
}
/** Process only QR about adding relays/profiles (DCACCOUNT: / DCLOGIN:) */
public void handleOnlyAddRelayQr(String rawString) {
final DcLot qrParsed = dcContext.checkQr(rawString);
if (!handleAddRelayQr(qrParsed, rawString)) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
handleDefault(builder, rawString, qrParsed);
builder.create().show();
}
}
private boolean handleAddRelayQr(DcLot qrParsed, String rawString) {
switch (qrParsed.getState()) {
case DcContext.DC_QR_ACCOUNT:
case DcContext.DC_QR_LOGIN:
public void handleQrData(String rawString, SecurejoinSource source, SecurejoinUiPath uiPath) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.confirm_add_transport);
builder.setMessage(qrParsed.getText1());
builder.setPositiveButton(R.string.ok, (d, w) -> {
if (activity instanceof RelayListActivity) {
// no need to protect with lock, RelayListActivity is already protected
addRelay(rawString);
} else {
boolean result = ScreenLockUtil.applyScreenLock(activity, activity.getString(R.string.add_transport), activity.getString(R.string.enter_system_secret_to_continue), ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS);
if (!result) {
addRelay(rawString);
}
}
});
builder.setNegativeButton(R.string.cancel, null);
builder.setCancelable(false);
final DcLot qrParsed = dcContext.checkQr(rawString);
String name = dcContext.getContact(qrParsed.getId()).getDisplayName();
switch (qrParsed.getState()) {
case DcContext.DC_QR_ASK_VERIFYCONTACT:
case DcContext.DC_QR_ASK_VERIFYGROUP:
case DcContext.DC_QR_ASK_JOIN_BROADCAST:
showVerifyContactOrGroup(activity, builder, rawString, qrParsed, name, source, uiPath);
break;
case DcContext.DC_QR_FPR_WITHOUT_ADDR:
showVerifyFingerprintWithoutAddress(builder, qrParsed);
break;
case DcContext.DC_QR_FPR_MISMATCH:
showFingerPrintError(builder, name);
break;
case DcContext.DC_QR_FPR_OK:
case DcContext.DC_QR_ADDR:
showFingerprintOrQrSuccess(builder, qrParsed, name);
break;
case DcContext.DC_QR_URL:
showQrUrl(builder, qrParsed);
break;
case DcContext.DC_QR_ACCOUNT:
case DcContext.DC_QR_LOGIN:
final String scope = qrParsed.getText1();
setAddTransportDialog(activity, builder, rawString, scope);
builder.setNegativeButton(R.string.cancel, null);
builder.setCancelable(false);
break;
case DcContext.DC_QR_BACKUP2:
builder.setTitle(R.string.multidevice_receiver_title);
builder.setMessage(activity.getString(R.string.multidevice_receiver_scanning_ask) + "\n\n" + activity.getString(R.string.multidevice_same_network_hint));
builder.setPositiveButton(R.string.perm_continue, (dialog, which) -> {
AccountManager.getInstance().addAccountFromSecondDevice(activity, rawString);
});
builder.setNegativeButton(R.string.cancel, null);
builder.setCancelable(false);
AlertDialog alertDialog = builder.create();
alertDialog.show();
BackupTransferActivity.appendSSID(activity, alertDialog.findViewById(android.R.id.message));
return;
case DcContext.DC_QR_BACKUP_TOO_NEW:
builder.setTitle(R.string.multidevice_receiver_title);
builder.setMessage(activity.getString(R.string.multidevice_receiver_needs_update));
builder.setNegativeButton(R.string.ok, null);
break;
case DcContext.DC_QR_PROXY:
builder.setTitle(R.string.proxy_use_proxy);
builder.setMessage(activity.getString(R.string.proxy_use_proxy_confirm, qrParsed.getText1()));
builder.setPositiveButton(R.string.proxy_use_proxy, (dlg, btn) -> {
dcContext.setConfigFromQr(rawString);
dcContext.restartIo();
showDoneToast(activity);
});
if (rawString.toLowerCase().startsWith("http")) {
builder.setNeutralButton(R.string.open, (d, b) -> IntentUtils.showInBrowser(activity, rawString));
}
builder.setNegativeButton(R.string.cancel, null);
builder.setCancelable(false);
break;
case DcContext.DC_QR_WITHDRAW_VERIFYCONTACT:
case DcContext.DC_QR_WITHDRAW_VERIFYGROUP:
case DcContext.DC_QR_WITHDRAW_JOINBROADCAST:
String message = qrParsed.getState() == DcContext.DC_QR_WITHDRAW_VERIFYCONTACT ? activity.getString(R.string.withdraw_verifycontact_explain)
: qrParsed.getState() == DcContext.DC_QR_WITHDRAW_VERIFYCONTACT ? activity.getString(R.string.withdraw_verifygroup_explain, qrParsed.getText1())
: activity.getString(R.string.withdraw_joinbroadcast_explain, qrParsed.getText1());
builder.setTitle(R.string.qrshow_title);
builder.setMessage(message);
builder.setNeutralButton(R.string.reset, (dialog, which) -> {
dcContext.setConfigFromQr(rawString);
});
builder.setPositiveButton(R.string.ok, null);
AlertDialog withdrawDialog = builder.show();
Util.redButton(withdrawDialog, AlertDialog.BUTTON_NEUTRAL);
return;
case DcContext.DC_QR_REVIVE_VERIFYCONTACT:
case DcContext.DC_QR_REVIVE_VERIFYGROUP:
case DcContext.DC_QR_REVIVE_JOINBROADCAST:
builder.setTitle(R.string.qrshow_title);
builder.setMessage(activity.getString(R.string.revive_verifycontact_explain));
builder.setNeutralButton(R.string.revive_qr_code, (dialog, which) -> {
dcContext.setConfigFromQr(rawString);
});
builder.setPositiveButton(R.string.ok, null);
break;
default:
handleDefault(builder, rawString, qrParsed);
break;
}
builder.create().show();
return true;
default:
return false;
}
}
/** Process a proxy QR, returns true if a dialog was shown, false if the QR is not a proxy QR */
public boolean handleProxyQr(String rawString) {
return handleProxyQr(dcContext.checkQr(rawString), rawString);
}
private boolean handleProxyQr(DcLot qrParsed, String rawString) {
if (qrParsed.getState() == DcContext.DC_QR_PROXY) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.proxy_use_proxy);
builder.setMessage(activity.getString(R.string.proxy_use_proxy_confirm, qrParsed.getText1()));
builder.setPositiveButton(R.string.proxy_use_proxy, (dlg, btn) -> {
dcContext.setConfigFromQr(rawString);
dcContext.restartIo();
showDoneToast();
});
if (rawString.toLowerCase().startsWith("http")) {
builder.setNeutralButton(R.string.open, (d, b) -> IntentUtils.showInBrowser(activity, rawString));
}
builder.setNegativeButton(R.string.cancel, null);
builder.setCancelable(false);
builder.create().show();
return true;
}
return false;
}
/** Process a backup QR, returns true if a dialog was shown, false if the QR is not a backup QR */
public boolean handleBackupQr(String rawString) {
return handleBackupQr(dcContext.checkQr(rawString), rawString);
}
private boolean handleBackupQr(DcLot qrParsed, String rawString) {
switch (qrParsed.getState()) {
case DcContext.DC_QR_BACKUP2:
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.multidevice_receiver_title);
builder.setMessage(activity.getString(R.string.multidevice_receiver_scanning_ask) + "\n\n" + activity.getString(R.string.multidevice_same_network_hint));
builder.setPositiveButton(R.string.perm_continue, (dialog, which) -> {
AccountManager.getInstance().addAccountFromSecondDevice(activity, rawString);
});
builder.setNegativeButton(R.string.cancel, null);
builder.setCancelable(false);
AlertDialog alertDialog = builder.create();
alertDialog.show();
BackupTransferActivity.appendSSID(activity, alertDialog.findViewById(android.R.id.message));
return true;
case DcContext.DC_QR_BACKUP_TOO_NEW:
new AlertDialog.Builder(activity)
.setTitle(R.string.multidevice_receiver_title)
.setMessage(activity.getString(R.string.multidevice_receiver_needs_update))
.setNegativeButton(R.string.ok, null)
.create().show();
return true;
default:
return false;
}
}
/** Handle any kind of QR showing an AlertDialog adapted to the QR type. */
public void handleQrData(String rawString, SecurejoinSource source, SecurejoinUiPath uiPath) {
final DcLot qrParsed = dcContext.checkQr(rawString);
if (handleSecureJoinQr(qrParsed, rawString, source, uiPath)
|| handleAddRelayQr(qrParsed, rawString)
|| handleProxyQr(qrParsed, rawString)
|| handleBackupQr(qrParsed, rawString)) return;
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
if (qrParsed.getState() == DcContext.DC_QR_URL) {
showQrUrl(builder, qrParsed);
} else {
handleDefault(builder, rawString, qrParsed);
}
builder.create().show();
}
private void handleDefault(AlertDialog.Builder builder, String qrRawString, DcLot qrParsed) {
String msg;
@@ -256,7 +187,7 @@ public class QrCodeHandler {
builder.setPositiveButton(android.R.string.ok, null);
builder.setNeutralButton(R.string.menu_copy_to_clipboard, (dialog, which) -> {
Util.writeTextToClipboard(activity, scannedText);
showDoneToast();
showDoneToast(activity);
});
}
@@ -268,11 +199,11 @@ public class QrCodeHandler {
builder.setNegativeButton(android.R.string.cancel, null);
builder.setNeutralButton(R.string.menu_copy_to_clipboard, (dialog, which) -> {
Util.writeTextToClipboard(activity, url);
showDoneToast();
showDoneToast(activity);
});
}
private void showDoneToast() {
private void showDoneToast(Activity activity) {
Toast.makeText(activity, activity.getString(R.string.done), Toast.LENGTH_SHORT).show();
}
@@ -301,11 +232,12 @@ public class QrCodeHandler {
builder.setPositiveButton(android.R.string.ok, null);
builder.setNeutralButton(R.string.menu_copy_to_clipboard, (dialog, which) -> {
Util.writeTextToClipboard(activity, qrParsed.getText1());
showDoneToast();
showDoneToast(activity);
});
}
private void showVerifyContactOrGroup(AlertDialog.Builder builder,
private void showVerifyContactOrGroup(Activity activity,
AlertDialog.Builder builder,
String qrRawString,
DcLot qrParsed,
String name,
@@ -343,47 +275,51 @@ public class QrCodeHandler {
builder.setNegativeButton(android.R.string.cancel, null);
}
public void addRelay(String qrData) {
ProgressDialog progressDialog = new ProgressDialog(activity);
progressDialog.setMessage(activity.getResources().getString(R.string.one_moment));
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.setCancelable(false);
String cancel = activity.getResources().getString(android.R.string.cancel);
progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, cancel, (d, w) -> {
dcContext.stopOngoingProcess();
});
progressDialog.show();
private void setAddTransportDialog(Activity activity, AlertDialog.Builder builder, String qrData, String transportName) {
builder.setTitle(R.string.confirm_add_transport);
builder.setMessage(transportName);
builder.setPositiveButton(R.string.ok, (d, w) -> {
ProgressDialog progressDialog = new ProgressDialog(activity);
progressDialog.setMessage(activity.getResources().getString(R.string.one_moment));
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.setCancelable(false);
String cancel = activity.getResources().getString(android.R.string.cancel);
progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, cancel, (d2, w2) -> {
dcContext.stopOngoingProcess();
});
progressDialog.show();
Util.runOnAnyBackgroundThread(() -> {
String error = null;
try {
rpc.addTransportFromQr(accId, qrData);
} catch (RpcException e) {
Log.w(TAG, e);
error = e.getMessage();
}
final String finalError = error;
Util.runOnMain(() -> {
if (!progressDialog.isShowing()) return; // canceled dialog, nothing to do
if (finalError != null) {
new AlertDialog.Builder(activity)
.setTitle(R.string.error)
.setMessage(finalError)
.setPositiveButton(R.string.ok, null)
.show();
} else {
showDoneToast();
if (!(activity instanceof RelayListActivity)) {
activity.startActivity(new Intent(activity, RelayListActivity.class));
}
}
try {
progressDialog.dismiss();
} catch (IllegalArgumentException e) {
// see https://stackoverflow.com/a/5102572/4557005
Log.w(TAG, e);
}
Util.runOnAnyBackgroundThread(() -> {
String error = null;
try {
rpc.addTransportFromQr(accId, qrData);
} catch (RpcException e) {
Log.w(TAG, e);
error = e.getMessage();
}
final String finalError = error;
Util.runOnMain(() -> {
if (!progressDialog.isShowing()) return; // canceled dialog, nothing to do
if (finalError != null) {
new AlertDialog.Builder(activity)
.setTitle(R.string.error)
.setMessage(finalError)
.setPositiveButton(R.string.ok, null)
.show();
} else {
showDoneToast(activity);
if (!(activity instanceof RelayListActivity)) {
activity.startActivity(new Intent(activity, RelayListActivity.class));
}
}
try {
progressDialog.dismiss();
} catch (IllegalArgumentException e) {
// see https://stackoverflow.com/a/5102572/4557005
Log.w(TAG, e);
}
});
});
});
});
}
}
@@ -36,24 +36,19 @@ 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 int msgId;
private final int 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;
public ReactionsDetailsFragment(int msgId) {
super();
this.msgId = msgId;
}
@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();
@@ -24,7 +24,6 @@ import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.qr.QrActivity;
import org.thoughtcrime.securesms.qr.QrCodeHandler;
import org.thoughtcrime.securesms.util.ScreenLockUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -33,6 +32,8 @@ import java.util.List;
import chat.delta.rpc.Rpc;
import chat.delta.rpc.RpcException;
import chat.delta.rpc.types.EnteredLoginParam;
import chat.delta.rpc.types.SecurejoinSource;
import chat.delta.rpc.types.SecurejoinUiPath;
public class RelayListActivity extends BaseActionBarActivity
implements RelayListAdapter.OnRelayClickListener, DcEventCenter.DcEventDelegate {
@@ -44,9 +45,6 @@ public class RelayListActivity extends BaseActionBarActivity
private Rpc rpc;
private int accId;
/** QR provided via Intent extras needs to be saved to pass it to QrCodeHandler when authorization finishes */
private String qrData = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -69,15 +67,6 @@ public class RelayListActivity extends BaseActionBarActivity
// Apply insets to prevent fab from being covered by system bars
ViewUtil.applyWindowInsetsAsMargin(fabAdd);
qrData = getIntent().getStringExtra(EXTRA_QR_DATA);
if (qrData != null) {
// when the activity is opened with a QR data, we need to ask for authorization first
boolean result = ScreenLockUtil.applyScreenLock(this, getString(R.string.add_transport), getString(R.string.enter_system_secret_to_continue), ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS);
if (!result) {
new QrCodeHandler(this).handleOnlyAddRelayQr(qrData);
}
}
fabAdd.setOnClickListener(v -> {
new IntentIntegrator(this).setCaptureActivity(QrActivity.class).addExtra(QrActivity.EXTRA_SCAN_RELAY, true).initiateScan();
});
@@ -97,7 +86,12 @@ public class RelayListActivity extends BaseActionBarActivity
DcEventCenter eventCenter = DcHelper.getEventCenter(this);
eventCenter.addObserver(DcContext.DC_EVENT_CONFIGURE_PROGRESS, this);
eventCenter.addObserver(DcContext.DC_EVENT_TRANSPORTS_MODIFIED, this);
String qrdata = getIntent().getStringExtra(EXTRA_QR_DATA);
if (qrdata != null) {
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.handleQrData(qrdata, SecurejoinSource.Unknown, SecurejoinUiPath.Unknown);
}
}
@Override
@@ -178,31 +172,17 @@ public class RelayListActivity extends BaseActionBarActivity
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK) {
// if user canceled unlocking, then finish
if (requestCode == ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS) finish();
return;
}
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
if (requestCode == IntentIntegrator.REQUEST_CODE) {
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
qrCodeHandler.handleOnlyAddRelayQr(scanResult.getContents());
} else if (requestCode == ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS) {
// user authorized, then proceed to handle the QR data
if (qrData != null) {
qrCodeHandler.handleOnlyAddRelayQr(qrData);
qrData = null;
}
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.onScanPerformed(scanResult, SecurejoinUiPath.Unknown);
}
}
@Override
public void handleEvent(@NonNull DcEvent event) {
int eventId = event.getId();
if (eventId == DcContext.DC_EVENT_CONFIGURE_PROGRESS) {
if (event.getData1Int() == 1000) loadRelays();
} else if (eventId == DcContext.DC_EVENT_TRANSPORTS_MODIFIED) {
if (eventId == DcContext.DC_EVENT_CONFIGURE_PROGRESS && event.getData1Int() == 1000) {
loadRelays();
}
}
@@ -27,4 +27,11 @@ 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();
}
} */
}

Some files were not shown because too many files have changed in this diff Show More