Compare commits

...

317 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] f0e95a59d1 Simplify sendComplete: remove chatId param and dead refreshFragment logic
sendComplete is only ever called from the message-send path, never the
draft path, so the passed chatId always equals this.chatId. The
refreshFragment branch and this.chatId = chatId assignment were dead
code. Removing them also eliminates the class of bug where an old
chatId could accidentally revert the activity's current chat.

Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2026-03-13 20:09:26 +00:00
copilot-swe-agent[bot] d44ad7de0c Fix introduced bug: restore sendComplete to use dcChat.getId()
The previous fix captured `currentChatId` to stop the setDraft race
condition, but also changed `sendComplete(dcChat.getId())` to
`sendComplete(currentChatId)`. Java lambdas access instance fields via
`this`, so `dcChat.getId()` in the lambda reads the current value when
it runs — after `initializeResources()` updates `dcChat` to the private
chat, `sendComplete` naturally received the private chat ID. Passing
`currentChatId` (old group chat ID) instead caused `sendComplete` to
write `this.chatId = GROUP_CHAT_ID`, reverting the activity to the old
chat and triggering an unwanted fragment reload.

Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2026-03-13 20:00:04 +00:00
copilot-swe-agent[bot] 3e0a283633 Fix: reply privately sometimes doesn't quote the message
Two fixes for the race condition that caused "reply privately" to
sometimes open the 1:1 chat without the quoted message:

1. In processComposeControls(), capture dcChat.getId() as currentChatId
   before the background thread starts. Without this, initializeResources()
   can change dcChat to the new private chat before the background thread
   runs, causing it to overwrite the quote draft with an empty/wrong draft.

2. In InputPanel.setQuote(), if getMeasuredHeight() returns 0 after
   measure(0,0) (view not yet laid out), fall back to WRAP_CONTENT instead
   of animating to 0 height which would keep the quote view at 1px height.

Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2026-03-13 18:00:02 +00:00
copilot-swe-agent[bot] ac01395482 Initial plan 2026-03-13 17:36:33 +00:00
adb fc1adc4863 Merge pull request #131 from ArcaneChat/adb/merge-upstream
merge upstream
2026-03-11 20:18:26 +01:00
adbenitez d68c32ff26 fix ProgressDialog 2026-03-11 20:16:29 +01:00
adbenitez b67e9d795e Merge remote-tracking branch 'upstream/main' into adb/test 2026-03-11 20:02:57 +01:00
adbenitez ae7be8a841 apply code formatter 2026-03-11 18:53:16 +01:00
adb 1894425ad2 Merge pull request #4271 from deltachat/adb/apply-code-format4
apply code formatter, part 4
2026-03-11 17:29:32 +01:00
adbenitez 3460e7e405 apply code formatter, part 4 2026-03-11 17:10:29 +01:00
adb ca40ae7869 Merge pull request #4270 from deltachat/adb/apply-code-format3
apply code formatter part 3
2026-03-11 17:01:17 +01:00
adbenitez cb05abb85f apply code formatter part 3 2026-03-11 16:33:21 +01:00
adb 8a76f99701 Merge pull request #4269 from deltachat/adb/apply-code-format2
apply code formatter part 2
2026-03-11 14:34:13 +01:00
adbenitez fd5a3b9f8e apply code formatter part 2 2026-03-11 14:16:44 +01:00
adb 6cab1d1cf4 Merge pull request #4268 from deltachat/adb/apply-code-format1
apply code format part 1
2026-03-11 14:06:27 +01:00
biörn be8009c950 fix: leave and delete finishes activity (#4266)
* fix: leave and delete finishes activity

the chat is gone - otherwise, we end up in ghost chat zero :)

moreover, the fix removes shortcuts from the homescreen.

a toast is no longer needed, as there is enough visual feedback;
we're also not showing a toast on plain deletion.

* use consistent string and clarify deletion scope
2026-03-11 14:01:34 +01:00
adbenitez f6c1fa2f5c apply code format part 1 2026-03-11 13:40:09 +01:00
biörn 6949be6fd8 leave and delete (#4262)
* deleting groups/channels is available after leaving only

* update CHANGELOG

* Update src/main/java/org/thoughtcrime/securesms/ConversationActivity.java

Co-authored-by: adb <adb@merlinux.eu>

* mailing lists are not encrypted

---------

Co-authored-by: adb <adb@merlinux.eu>
2026-03-11 00:40:04 +01:00
adb f95a6e8db7 Merge pull request #4264 from deltachat/adb/make-encr-info-selectable
make it possible to select/copy the fingerprints in encryption info dialog
2026-03-10 21:14:02 +01:00
biörn b81757d579 simplify 'call' wording (#4263) 2026-03-10 20:33:26 +01:00
adbenitez 30c0026840 make it possible to select/copy the fingerprints in encryption info dialog 2026-03-10 19:40:06 +01:00
adbenitez efb4bf0536 Merge remote-tracking branch 'upstream/main' 2026-03-10 19:08:40 +01:00
adb cd2fc0df0f Merge pull request #4230 from deltachat/adb/sticker-picker
add basic sticker picker
2026-03-10 14:49:17 +01:00
adbenitez 51380cf906 update changelog 2026-03-09 20:47:03 +01:00
adbenitez ded0c44ce3 improve StickerPickerView's code 2026-03-09 20:45:46 +01:00
adbenitez 61f5064929 move setupViews() to onFinishInflate() 2026-03-09 19:07:19 +01:00
adb f316ebea20 Merge branch 'main' into adb/sticker-picker 2026-03-09 18:18:24 +01:00
wchen342 dc1a029e4a Merge pull request #4250 from deltachat/wch423/remove-notification-msg-delete
Remove notification when message is deleted
2026-03-09 13:03:31 -04:00
wchen342 6bbacae54d Merge branch 'main' into wch423/remove-notification-msg-delete 2026-03-09 12:52:26 -04:00
wch423 972d372bd4 Add back exception handling for notify() 2026-03-09 17:17:28 +01:00
biörn fe7fb19adf add a hint about sending original images (#4259)
* add a hint about sending original images

* force items having needed size
2026-03-09 16:49:36 +01:00
biörn 5112389dae remove unused CustomDefaultPreference (#4257)
* remove unused CustomDefaultPreference

* remove now superfluous null check

* remove now unused resources
2026-03-09 16:48:17 +01:00
biörn c190a7b50a update some call translator notes (#4256) 2026-03-09 12:08:19 +01:00
biörn 7ce8ccff78 unify status line (#4255)
* less formal duration string

* use standard status footer

* move call icon to call information

* add duration line

* update CHANGELOG
2026-03-09 10:54:54 +01:00
biörn 7eb0a99fc7 add strings for the 'Calls' notification setting (#4249)
* add strings for the 'Calls' notification setting

* Update src/main/res/values/strings.xml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/main/res/values/strings.xml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-09 01:21:29 +01:00
adb cbdd8f19ed Merge branch 'main' into wch423/remove-notification-msg-delete 2026-03-06 17:15:28 +01:00
adb 5fdf319cda Merge pull request #4254 from deltachat/Hocuri-patch-1
CI: Change preview APK architecture from 32-bit to 64-bit
2026-03-06 14:55:48 +01:00
wch423 837e2313dd Make notifications rebuild up on message deletion 2026-03-06 00:26:59 +01:00
Hocuri e45fd2545e Change build target from armeabi-v7a to arm64-v8a 2026-03-05 18:27:08 +01:00
wch423 15537ea4b2 Remove notification when message is deleted 2026-03-05 00:40:10 +01:00
B. Petersen df97f4069a remove deprecated strings 2026-03-03 23:57:10 +01:00
B. Petersen dea8b2bed9 tweak 'view count' translator's hint 2026-03-02 16:54:56 +01:00
B. Petersen 0d130a4218 add view-count string 2026-03-01 13:24:38 +01:00
adbenitez b4ee89c1e8 Merge remote-tracking branch 'upstream/main' 2026-02-27 18:56:48 +01:00
adb b2e7b8fdb6 Merge branch 'main' into adb/sticker-picker 2026-02-27 18:51:14 +01:00
adb a494feb272 Merge pull request #4245 from deltachat/update-core-and-stuff-2026-02-27
Update core to 2.44.0
2026-02-27 18:49:20 +01:00
adbenitez 5efce4345a update RPC 2026-02-27 18:45:37 +01:00
adbenitez b7703ce71f update changelog 2026-02-27 17:25:24 +01:00
adbenitez c0ed32b645 update translations 2026-02-27 17:25:11 +01:00
adbenitez 7e4f408059 update deltachat-core-rust to 'chore(release): prepare for 2.44.0' of 'v2.44.0' 2026-02-27 17:18:33 +01:00
adbenitez a1993426de Merge remote-tracking branch 'upstream/main' 2026-02-24 01:30:11 +01:00
adbenitez bbb6ce8def fix ConversationFragment 2026-02-24 01:29:39 +01:00
adb c601595477 Merge pull request #4238 from deltachat/adb/avoid-deprecated-onActivityResult
migrate away from deprecated `Fragment.onActivityResult()`
2026-02-23 19:11:30 +01:00
adbenitez f909174119 tweak ConversationListActivity and RelayListActivity
don't relay in onActivityResult
2026-02-23 18:59:54 +01:00
adbenitez b0762a8b46 Merge remote-tracking branch 'upstream/main' 2026-02-22 13:00:11 +01:00
adb 545f987f31 Merge branch 'main' into adb/avoid-deprecated-onActivityResult 2026-02-21 23:48:11 +01:00
adb b625c57d2d Merge pull request #4240 from deltachat/adb/improve-channel-creation
don't set channel description on channel creation if it is empty
2026-02-21 23:47:57 +01:00
adbenitez 022d24bc5f Merge branch 'adb/improve-channel-creation' of https://github.com/deltachat/deltachat-android into adb/improve-channel-creation 2026-02-21 23:47:20 +01:00
adbenitez c5c09008a6 fix missing parenthesis 2026-02-21 23:47:03 +01:00
adb 7679cc8bba Merge branch 'main' into adb/avoid-deprecated-onActivityResult 2026-02-21 23:34:03 +01:00
adb 4807d05861 Merge branch 'main' into adb/improve-channel-creation 2026-02-21 23:33:20 +01:00
adb 34eedf5f07 Merge pull request #4239 from deltachat/adb/improve-description-changed-info-clicked
when "description changed by Foo" is clicked, open chat profile
2026-02-21 23:32:57 +01:00
adbenitez 941c186142 don't set channel description on channel creation if it is empty
when user creates a channel, only call rpc.setChatDescription if
there is actually some description to set, otherwise there will be a
"you changed the description" info-message when the user didn't set anything
2026-02-21 23:10:37 +01:00
adbenitez b54ac50ea5 when "description changed by Foo" is clicked, open chat profile 2026-02-21 22:59:19 +01:00
adb b99426b7c6 Merge pull request #118 from ArcaneChat/copilot/setup-copilot-instructions-again
Enhance Copilot instructions with build timing, CI/CD details, and troubleshooting
2026-02-19 05:33:38 +01:00
adbenitez de1eedc63f update .github/copilot-instructions.md 2026-02-19 05:32:04 +01:00
adbenitez c3dac043cb remove ScreenLockUtil.applyScreenLock(Activity, String, String, int) 2026-02-19 04:59:24 +01:00
adbenitez 00b52d4fe6 don't call registerForActivityResult in onActivityCreated 2026-02-19 03:34:05 +01:00
adbenitez b66bf595e2 avoid call to fragment.onActivityResult in ApplicationPreferencesActivity 2026-02-19 03:08:46 +01:00
adbenitez 7c24e836fe avoid deprecated ContactSelectionListFragment.onActivityResult() 2026-02-19 02:26:09 +01:00
adbenitez 694f3bf013 remove unused import in ListSummaryPreferenceFragment 2026-02-19 02:08:47 +01:00
adbenitez f6f42991e8 avoid deprecated NotificationsPreferenceFragment.onActivityResult() 2026-02-19 02:08:24 +01:00
adbenitez 23f6803be9 avoid deprecated ChatsPreferenceFragment.onActivityResult()
also remove now unused fields in `ListSummaryPreferenceFragment`
2026-02-19 00:05:39 +01:00
adbenitez cdcb7fab4b avoid deprecated AdvancedPreferenceFragment.onActivityResult() 2026-02-18 23:53:30 +01:00
adbenitez 5ee3607312 avoid deprecated ConversationFragment.onActivityResult() 2026-02-18 23:32:54 +01:00
adbenitez 4b9642f51f add more logging to ProfileFragment 2026-02-18 23:29:54 +01:00
adbenitez 4abbf3091f avoid deprecated in ProfileFragment.onActivityResult() 2026-02-18 22:58:21 +01:00
copilot-swe-agent[bot] ec4db3e58f Enhance Copilot instructions with detailed build, test, and CI/CD information
Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2026-02-18 20:52:39 +00:00
copilot-swe-agent[bot] 0d2175b641 Initial plan 2026-02-18 20:49:42 +00:00
adb a821ee363a Merge branch 'main' into adb/sticker-picker 2026-02-18 19:59:07 +01:00
adb f43083acef Merge pull request #4214 from deltachat/adb/chat-description
allow to set chat description
2026-02-18 19:58:02 +01:00
adb 7aed78a935 Update src/main/res/values/strings.xml
Co-authored-by: biörn <r10s@b44t.com>
2026-02-18 19:34:59 +01:00
adbenitez 26c30fe3dc Merge branch 'adb/chat-description' of https://github.com/deltachat/deltachat-android into adb/chat-description 2026-02-18 19:32:49 +01:00
adbenitez 5550b8b1f5 update changelog 2026-02-18 19:32:00 +01:00
adb c94512fb24 Merge branch 'main' into adb/chat-description 2026-02-18 19:30:30 +01:00
B. Petersen d1db6e5a8a apply suggestion from @wchen342 2026-02-18 14:44:56 +01:00
B. Petersen d352583237 avoid 'recode', which is too technical as well 2026-02-18 14:44:56 +01:00
B. Petersen c8469be136 clearify, how to recode multiple videos 2026-02-18 14:44:56 +01:00
adb 97620b8a3c Merge pull request #114 from ArcaneChat/copilot/improve-attachment-selector
Convert attachment selector to horizontal scrollable row with uniform cell sizing
2026-02-18 04:40:13 +01:00
adbenitez 24317b38bf tweak src/main/res/layout/attachment_type_selector.xml 2026-02-18 04:35:01 +01:00
copilot-swe-agent[bot] 6b28ed15e3 Fix cell sizing: uniform width with ellipsized text
Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2026-02-18 03:02:49 +00:00
copilot-swe-agent[bot] b8b203e517 Convert attachment selector from fixed grid to scrollable row
Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2026-02-18 02:47:04 +00:00
copilot-swe-agent[bot] e5c1e477f0 Initial plan 2026-02-18 02:44:54 +00:00
adbenitez 64752d3bae Merge remote-tracking branch 'upstream/adb/chat-description' 2026-02-17 23:39:53 +01:00
adbenitez 6145e0d2df Merge remote-tracking branch 'upstream/main' 2026-02-17 23:29:06 +01:00
adbenitez b365284743 fix Rpc class 2026-02-17 22:37:56 +01:00
adb af62041c14 Merge branch 'main' into adb/chat-description 2026-02-17 22:36:07 +01:00
adbenitez 910dbf56fd update core 2026-02-17 22:34:55 +01:00
adb 4eca0dea4a Merge pull request #4237 from deltachat/prep-2.43.0
prepare 2.43.0
2026-02-17 22:24:24 +01:00
adbenitez a76c17fd45 prepare 2.43.0 2026-02-17 21:50:36 +01:00
adb b00aeec03a Merge pull request #4236 from deltachat/update-core-and-stuff-2026-02-17
Update core to 2.43.0
2026-02-17 21:15:35 +01:00
adbenitez ffe147fde8 update RPC bindings 2026-02-17 21:09:06 +01:00
adbenitez 02a5bb06a9 update translations 2026-02-17 21:08:44 +01:00
adbenitez a8997738bf update changelog 2026-02-17 20:07:51 +01:00
adbenitez 202690d02e update deltachat-core-rust to 'chore(release): prepare for 2.43.0' of 'v2.43.0' 2026-02-17 20:04:23 +01:00
wchen342 6d14bbdbc7 Merge pull request #4234 from deltachat/wch423/fix-audio-seekbar-reset
Prevent seek bar from being reset when message list changes
2026-02-17 14:03:08 -05:00
adbenitez b0699ab9be sort stickers 2026-02-17 18:04:56 +01:00
wch423 03a0b53eee Update function name 2026-02-17 16:34:33 +01:00
wch423 a044181a75 Update progress bar for playing audio; Clear progress for any other audio that is not playing to avoid confusion 2026-02-17 16:31:06 +01:00
B. Petersen e9ae9dc5bf add more translation context to strings 2026-02-17 15:27:13 +01:00
B. Petersen cbfa3b7e58 clarify 'Audio' string 2026-02-17 13:52:21 +01:00
adbenitez 499f4aafc8 add basic sticker picker 2026-02-17 04:14:16 +01:00
adbenitez bf57d3bd73 add src/main/res/values-eu/strings_arcanechat.xml 2026-02-17 03:47:52 +01:00
adbenitez 6200f354ca set custom icon for debug build 2026-02-17 03:47:30 +01:00
wchen342 c8dfb08dcb Merge pull request #4228 from deltachat/wch423/fix-audio-duration
Add duration extraction for audio files
2026-02-16 16:03:06 -05:00
wch423 543e9d91e4 Make sure longs are always in range 2026-02-16 20:08:31 +01:00
wch423 dec5879919 Add duration extraction for audio files 2026-02-16 19:01:55 +01:00
wchen342 a1d21d8562 Merge pull request #4226 from deltachat/wch423/fix-video-crash
Fix video player crash
2026-02-16 04:55:34 -05:00
adbenitez 253ed877e5 add comment to src/main/res/values/strings_arcanechat.xml
hint to translators to point out that the channel is in English
2026-02-15 17:40:57 +01:00
wch423 b809d24ab1 Change PlayerView to StyledPlayerView to avoid conflict between ExoPlayer2 and Media3 2026-02-13 18:05:37 +01:00
adbenitez 78aee0a487 Merge remote-tracking branch 'upstream/main' 2026-02-13 02:19:21 +01:00
adb d16254c146 Merge pull request #4223 from deltachat/adb/issue-4208
remove old buttons in relays list
2026-02-12 20:10:01 +01:00
adb 7ab3ef8453 Merge pull request #4224 from deltachat/adb/issue-4207-part2
don't tint error icon red in chatlist and info-messages
2026-02-12 20:09:43 +01:00
adbenitez 4b695a3293 update changelog 2026-02-12 00:01:16 +01:00
adbenitez da4c382c9a add context menu to relay items 2026-02-11 23:58:48 +01:00
adbenitez 80973960f5 don't tint error icon red in chatlist and info-messages 2026-02-11 20:54:05 +01:00
adbenitez 7be75f7008 remove old buttons 2026-02-11 20:27:01 +01:00
adbenitez 6fa114c6c0 Merge remote-tracking branch 'upstream/main' 2026-02-11 20:22:37 +01:00
adbenitez 767b5f2bae update channel link 2026-02-11 20:04:59 +01:00
adb ed3a21b992 Merge pull request #4222 from deltachat/prep-2.42.0
prepare 2.42.0
2026-02-10 21:41:59 +01:00
adbenitez 899c2b5647 prepare 2.42.0 2026-02-10 21:24:21 +01:00
adb 61e616a53b Merge pull request #4221 from deltachat/update-core-and-stuff-2026-02-10
Update core to 2.42.0
2026-02-10 21:23:09 +01:00
adbenitez 3bf4504de9 update strings 2026-02-10 21:20:52 +01:00
adbenitez cd0740d895 update changelog 2026-02-10 20:31:58 +01:00
adbenitez 9f99edc159 update deltachat-core-rust to 'chore(release): prepare for 2.42.0' of 'v2.42.0' 2026-02-10 20:26:54 +01:00
wchen342 60568f23f5 Merge pull request #4217 from deltachat/wch423/onboarding-invite-code
Allow scanning invite codes from onboarding
2026-02-09 16:36:23 -05:00
wchen342 6dc8bd7ba8 Merge branch 'main' into wch423/onboarding-invite-code 2026-02-09 16:25:34 -05:00
wch423 ff4ecb3bda Fix missing callback on successful scan 2026-02-09 22:19:11 +01:00
wch423 c71e71359a Remove extra constant 2026-02-09 22:11:13 +01:00
wch423 d5a09fa25e Only show confirmation dialog if it is an invitation link for contact or group 2026-02-09 21:56:43 +01:00
adb f52c2be2c2 Merge pull request #4219 from deltachat/adb/disable-chat-edge-to-edge
disable insets change in setComposePanelVisibility()
2026-02-09 21:46:46 +01:00
wch423 2233b93108 Remove color change on the OK button 2026-02-09 21:42:06 +01:00
adbenitez d76eb3239a disable insets change in setComposePanelVisibility() 2026-02-09 21:05:00 +01:00
adb a982bd2bbb Merge branch 'main' into wch423/onboarding-invite-code 2026-02-09 18:52:33 +01:00
wchen342 9ef3edcc0e Merge pull request #4189 from deltachat/wch423/audio-background-play
Add background audio playback for voice messages
2026-02-09 12:45:01 -05:00
wch423 de7c54b886 Revert changes to rpc 2026-02-09 15:32:37 +01:00
adb 9c9f966597 Merge branch 'main' into wch423/audio-background-play 2026-02-09 02:24:54 +01:00
adb 12e77789d3 Merge branch 'main' into wch423/onboarding-invite-code 2026-02-09 02:24:39 +01:00
adb bd640072ab Merge pull request #4216 from deltachat/adb/avoid-races-loading-chatlist
use conversationListFragment.loadChatlistAsync() to avoid races
2026-02-09 01:00:38 +01:00
adb 0a9c46fbfc Merge branch 'main' into adb/avoid-races-loading-chatlist 2026-02-07 17:37:29 +01:00
wch423 1cc4f11484 Add CHANGELOG 2026-02-07 17:04:27 +01:00
wch423 9386f2f9cb Fix QR scanning not restarted after cancel 2026-02-07 16:56:49 +01:00
wchen342 45d73d4604 Merge pull request #4215 from deltachat/adb/issue-4207
tune down error icon, don't use special bright alarming color
2026-02-07 10:22:32 -05:00
Hocuri a5892330dd Translate stock strings 2026-02-07 11:30:40 +01:00
adbenitez 8a0e2d72a4 use conversationListFragment.loadChatlistAsync() to avoid races 2026-02-07 06:22:31 +01:00
adbenitez 2629c65564 tune down error icon, don't use special bright alarming color 2026-02-07 02:39:37 +01:00
adbenitez 10694a6809 display chat description in chat profile 2026-02-07 01:49:59 +01:00
adbenitez aa3d177243 allow to set chat description 2026-02-07 01:23:26 +01:00
adbenitez 4d35d4edeb temporarily select core branch with new chat description api 2026-02-06 23:08:53 +01:00
wch423 bbaba3cd33 Fix observer not added in a specific case 2026-02-06 22:30:35 +01:00
wch423 d1f002a132 Remove desugar and use AndroidX version of Consumer 2026-02-06 21:04:31 +01:00
wch423 9ce9a91c95 Add confirm dialog for invitation qr codes 2026-02-06 21:01:36 +01:00
wch423 7844e146b1 Join and launch chat directly after creating new account 2026-02-06 18:50:57 +01:00
adbenitez eb997eca00 fix bug introduced by upstream merge 2026-02-06 18:35:30 +01:00
adb 30124de2a8 Merge branch 'main' into wch423/audio-background-play 2026-02-06 18:34:56 +01:00
wch423 1dcf7e4860 Adding new text for scanning invite code when onboarding 2026-02-06 16:50:45 +01:00
B. Petersen 9ffc904ae5 more detailed call strings 2026-02-06 11:45:56 +01:00
wchen342 2704749b44 Merge pull request #4198 from deltachat/wch423/edge-to-edge-device-channel
Make device messages and subscribed channels edge to edge
2026-02-05 15:15:40 -05:00
wch423 1c174b5b70 Stop draft audio playback when (1) the conversation exits; or (2) the message is sent 2026-02-05 21:12:09 +01:00
adbenitez 8b5dd70d75 Merge remote-tracking branch 'upstream/main' 2026-02-05 20:25:35 +01:00
adb f022316ad7 Merge branch 'main' into wch423/audio-background-play 2026-02-05 19:44:50 +01:00
wch423 3386f5c5f7 Make !CanSend -> CanSend still trigger Inset changes 2026-02-05 19:07:43 +01:00
adb 57eead3a34 Merge branch 'main' into wch423/edge-to-edge-device-channel 2026-02-05 18:33:52 +01:00
adb 2868b51835 Merge pull request #4212 from deltachat/prep-2.40.0
prepare 2.40.0
2026-02-05 17:44:28 +01:00
wch423 82118db71b Make inset changes only happen on initialization of activity 2026-02-05 17:27:21 +01:00
adbenitez 14f55ca6b1 prepare 2.40.0 2026-02-05 17:25:20 +01:00
adb cebfa12142 Merge branch 'main' into wch423/edge-to-edge-device-channel 2026-02-05 16:55:30 +01:00
adb 96c8c21b78 Merge pull request #4209 from deltachat/adb/issue-4206
hide delete-contact icon in ContactSelectionListFragment
2026-02-05 16:55:07 +01:00
adb 9ab1b1f3a7 Merge branch 'main' into adb/issue-4206 2026-02-05 16:54:55 +01:00
adb 94a0e426f8 Merge pull request #4210 from deltachat/adb/allow-to-pick-call-mode
show menu to select audio/video call modes
2026-02-05 16:48:27 +01:00
adbenitez 006f8ae826 use hasVideo instead of audioOnly 2026-02-05 16:37:42 +01:00
wch423 2889266522 Force dispatch all inset changes immediately in ConversationFragment 2026-02-05 16:28:38 +01:00
adb 5e6fccf143 Merge branch 'main' into wch423/edge-to-edge-device-channel 2026-02-05 15:41:17 +01:00
adbenitez 3847e20d18 set tint for icons in the call submenu 2026-02-05 02:31:38 +01:00
adbenitez fc69212a51 update changelog 2026-02-05 01:41:29 +01:00
adbenitez 8999f54ba2 upgrade calls web app 2026-02-05 01:40:27 +01:00
adbenitez f470e92300 show different icon depending on voice/video call modes 2026-02-05 01:35:20 +01:00
adbenitez 3ac49e3e58 set the call mode when calling Rpc.placeOutgoingCall 2026-02-05 01:20:11 +01:00
adb 8dd9cfec5b Merge branch 'main' into adb/allow-to-pick-call-mode 2026-02-05 01:17:12 +01:00
adb 0588214ee7 Merge pull request #4211 from deltachat/update-core-and-stuff-2026-02-04
Update core to 2.40.0
2026-02-05 00:37:53 +01:00
adbenitez 4ec49a031c adapt to new Rpc.placeOutgoingCall API 2026-02-05 00:24:51 +01:00
adbenitez a31d7d6d3e update the RPC bindings 2026-02-04 23:39:04 +01:00
adbenitez 64bbe9866d update translations 2026-02-04 23:38:33 +01:00
adbenitez 5d7ab84efc update changelog 2026-02-04 23:35:14 +01:00
adbenitez 11f73a88e8 update deltachat-core-rust to 'chore(release): prepare for 2.40.0' of 'v2.40.0' 2026-02-04 23:31:30 +01:00
adbenitez dd0e847976 take into consideration if the call is audio/video call when accepting it 2026-02-04 23:17:42 +01:00
wch423 71ed333468 Stop playback when attachment preview is removed;
Stop playback when message containing audio is removed
2026-02-04 22:49:18 +01:00
adbenitez 41d94ae3ee if it is an audio-only call start with video disabled 2026-02-04 22:14:50 +01:00
wch423 b66bc1f863 Handle specific case when leaving group; also move floating button to avoid overaapping with system bar 2026-02-04 19:36:51 +01:00
adbenitez df7d80319c show menu to select audio/video call modes 2026-02-04 19:36:34 +01:00
adbenitez 134145d166 update changelog 2026-02-04 17:13:48 +01:00
adbenitez 688a103c10 hide delete-contact icon in ContactSelectionListFragment 2026-02-04 16:45:29 +01:00
adb e2efa1f913 Merge pull request #4201 from deltachat/adb/issue-4195
fix mailto handling and remove unused code
2026-02-03 23:27:48 +01:00
adb aebd5c66f7 Merge branch 'main' into wch423/audio-background-play 2026-02-03 21:51:02 +01:00
adbenitez 15c60c6b12 fix mailto handling 2026-02-03 20:57:47 +01:00
wch423 f319ba2b83 Allow list scrolling to extend to edge 2026-02-03 19:40:02 +01:00
wch423 93f12e7367 Make message list respect bottom bar height 2026-02-03 19:40:00 +01:00
wch423 10acb07f82 Make device messages and subscribed channels edge to edge 2026-02-03 19:39:56 +01:00
adb ee9a8dd53a Merge branch 'main' into adb/issue-4195 2026-02-03 18:22:39 +01:00
adb 0a55023bdb Merge pull request #4205 from deltachat/adb/issue-4202
remove deselected members
2026-02-03 18:22:02 +01:00
adb fec9f8b4d3 Merge pull request #4204 from deltachat/adb/issue-4203
Fix layout problems inside in-chat apps
2026-02-03 17:53:22 +01:00
wch423 13374df709 Bug fixes and minor changes 2026-02-03 17:41:31 +01:00
adbenitez 7fa04dc3c0 update changelog 2026-02-03 01:03:05 +01:00
adbenitez 9d4e0e4e21 remove deselected members 2026-02-03 00:40:00 +01:00
adbenitez 473c28ab07 Fix layout problems inside in-chat apps 2026-02-02 20:31:42 +01:00
adbenitez de47feac40 update changelog 2026-01-30 20:21:48 +01:00
adbenitez 84894ff538 fix mailto handling and remove unused code 2026-01-30 20:17:04 +01:00
adb f254c35749 Merge pull request #4199 from deltachat/r10s/improve-details-strings
message info: title-case the send/received titles
2026-01-30 17:48:37 +01:00
adb 1ff4e069ea Merge branch 'main' into wch423/audio-background-play 2026-01-29 22:17:24 +01:00
B. Petersen d574d33596 title-case the message-info send/received titles
we nowhere in the app use lower case field names
2026-01-29 18:33:57 +01:00
wchen342 f6756fc34b Merge pull request #4191 from deltachat/adb/issue-4157
use event emmiter before DcAccounts creation
2026-01-29 10:34:57 -05:00
wchen342 7c27eb47fc Merge pull request #4196 from deltachat/adb/issue-4194
avoid crash caused by custom Fragment constructors
2026-01-29 10:19:15 -05:00
adbenitez 8d5a55c24c update changelog 2026-01-29 03:47:53 +01:00
adbenitez 2e9aa79b02 simplify changes in AccountSelectionListFragment 2026-01-29 03:44:42 +01:00
adbenitez 1f9264225b remove custom constructor in ReactionsDetailsFragment 2026-01-29 03:30:17 +01:00
adbenitez 18e145faaf avoid custom constructor in AccountSelectionListFragment 2026-01-29 02:17:06 +01:00
adbenitez 47cf70120a add logcat to crash report 2026-01-28 23:21:37 +01:00
adbenitez 86451a00ff Merge remote-tracking branch 'upstream/main' 2026-01-28 22:28:40 +01:00
wch423 34be7aab17 Cleanup imports 2026-01-28 21:30:51 +01:00
wch423 59139ed242 Add support for attachment draft; Distinguish between different messages; Prevent unrelated activities from changing the pending intent 2026-01-28 21:28:43 +01:00
wch423 11f3964bdc Add support for attachment draft; Distinguish between different messages 2026-01-28 21:28:42 +01:00
wch423 7683408d18 Allow going back to previous activity from the notification; Support AllMediaActivity 2026-01-28 21:28:40 +01:00
wch423 b74e793654 Finish ViewModel for audio playback; Refine UI set up for AudioView 2026-01-28 21:28:38 +01:00
wch423 4bd74324d2 Try to decouple view and business logics 2026-01-28 21:28:36 +01:00
wch423 9a121b3039 Fix UI update problems;
Make notification clickable
2026-01-28 21:28:33 +01:00
wch423 a9832c9c53 Make audio view use Media3 for playback 2026-01-28 21:28:31 +01:00
adbenitez 1e4c8bc291 use event emmiter before DcAccounts creation 2026-01-27 19:05:38 +01:00
wchen342 c45a47e53c Merge pull request #4187 from deltachat/adb/issue-4176
show views count in channels (to the admin)
2026-01-27 09:35:25 -05:00
adb a07723d515 Merge branch 'main' into adb/issue-4176 2026-01-27 03:02:39 +01:00
adb 02d7a6fb1f Merge pull request #4190 from deltachat/adb/crash-sooner-in-DcAccounts
throw exception if DcAccounts.createAccountsCPtr() returned null pointer
2026-01-27 03:02:15 +01:00
adb d62e8fa864 Merge branch 'main' into adb/crash-sooner-in-DcAccounts 2026-01-27 01:25:30 +01:00
adbenitez a03fd337ef throw exception if createAccountsCPtr() returned null pointer 2026-01-27 01:20:27 +01:00
72374 27c0f45430 fix: Change maximum height and width of avatar-images to the maximum allowed by Chatmail Core (#4185)
Avatar-images with a resolution larger than 512x512 will be resized by Chatmail Core, while media-quality is set to balanced, which is currently the highest quality-setting.

Co-authored-by: Hocuri <hocuri@gmx.de>
2026-01-26 23:27:56 +00:00
adbenitez a0f1232261 update changelog 2026-01-26 22:30:57 +01:00
adbenitez 5640d31080 remove checkmark from chatlist for outgoing channel messages 2026-01-26 22:29:03 +01:00
adbenitez 1542fc67ca show number of views a channel message have 2026-01-26 21:31:41 +01:00
adbenitez a99a4ec85b hide delivery checkmark in channels 2026-01-26 18:23:55 +01:00
adb fa17b134cf Merge pull request #4180 from deltachat/adb/issue-4055
allow to switch profile while sharing/forwarding
2026-01-26 17:05:49 +01:00
adbenitez 6898a37704 fix WelcomeActivity 2026-01-24 02:44:18 +01:00
adbenitez 45b6a5c565 Merge remote-tracking branch 'upstream/main' 2026-01-24 02:44:04 +01:00
adb 7330e6ba39 Merge branch 'main' into adb/issue-4055 2026-01-24 00:57:40 +01:00
adb 8f405914a2 Merge pull request #4186 from deltachat/update-core-2.39.0
update core to 2.39.0
2026-01-24 00:57:09 +01:00
adbenitez 6d09a6f318 update changelog 2026-01-24 00:34:18 +01:00
adbenitez b349a7e601 update deltachat-core-rust to 'chore(release): prepare for 2.39.0' of 'v2.39.0' 2026-01-24 00:31:58 +01:00
adbenitez 0d610895d2 fix function 2026-01-23 22:13:59 +01:00
adbenitez a65c677ecc update changelog 2026-01-23 22:10:06 +01:00
adbenitez db93aeacc0 fix event listener in ConversationActivity 2026-01-23 22:05:41 +01:00
adb 09ff0d888b Merge branch 'main' into adb/issue-4055 2026-01-23 17:43:51 +01:00
adb b9c5e741e8 Merge pull request #4184 from deltachat/update-core-and-stuff-2026-01-23
Update core to 3.38.0
2026-01-23 17:43:28 +01:00
adbenitez 620d86c903 update RPC bindings 2026-01-23 17:41:25 +01:00
adbenitez 14e31650e7 update translations 2026-01-23 17:35:19 +01:00
adbenitez 9848cb5847 update changelog 2026-01-23 17:27:49 +01:00
adbenitez bf7db005ef update deltachat-core-rust to 'chore(release): prepare for 2.38.0' of 'v2.38.0' 2026-01-23 15:31:28 +01:00
adbenitez aa032dbb37 fix some bugs
* avoid using wrong DcContext after switching account in ConversationListActivity
* avoid getContext() in ConversationFragment, use Rpc instead
2026-01-22 20:45:33 +01:00
adbenitez 9e1b9775e2 fix ConversationListActivity 2026-01-22 18:43:54 +01:00
adbenitez fcd16d9e32 don't allow to delete or create new profile while sharing/forwarding 2026-01-22 17:50:16 +01:00
adbenitez 2a4bd2a001 add device messages on account switching 2026-01-22 17:31:32 +01:00
adbenitez eacd089c8f improve code in ConversationListActivity 2026-01-22 17:18:49 +01:00
adbenitez 9ed355efd1 don't save dcContext in MessageSelectorFragment and derivates 2026-01-21 20:59:39 +01:00
adbenitez d1bb25004d Merge branch 'adb/issue-4055' of https://github.com/deltachat/deltachat-android into adb/issue-4055 2026-01-21 20:21:28 +01:00
adbenitez 8d1b010d1b tweak SendRelayedMessageUtil 2026-01-21 20:21:10 +01:00
adb 8a9b76af3a Merge branch 'main' into adb/issue-4055 2026-01-21 20:13:24 +01:00
adb 4bbd9428e3 Merge pull request #4182 from deltachat/wch423/backpress-migration
Migrate deprecated `onBackPressed` calls
2026-01-21 20:05:14 +01:00
adb 83646003b6 Merge branch 'main' into adb/issue-4055 2026-01-21 19:45:10 +01:00
wch423 947887f335 Migrate deprecated onBackPressed calls 2026-01-21 18:59:56 +01:00
wchen342 69631cd857 Merge pull request #4181 from deltachat/adb/improve-InstantOnboardingActivity
avoid account rollback in InstantOnboardingActivity
2026-01-21 10:30:01 -05:00
adbenitez 0722426425 allow to forward messages to different account 2026-01-21 00:22:43 +01:00
adbenitez 98d54bbe6a prevent returning to old chat after switching account 2026-01-21 00:20:02 +01:00
adbenitez c1dc0cf127 ConversationActivity: use currently selected account
instead of storing dcContext once on creation, the selected account
might change
2026-01-21 00:14:41 +01:00
adbenitez 44a1ce42f0 avoid account rollback in InstantOnboardingActivity 2026-01-20 20:01:40 +01:00
adbenitez a0c6cef0fa add menu item to switch account 2026-01-20 19:44:32 +01:00
adbenitez f96e89956c account switcher: make switching accounts smoother 2026-01-20 16:31:02 +01:00
wchen342 ce9ded9d8b Merge pull request #4178 from deltachat/adb/issue-4174
Properly update menu and other actionMode state in ContactSelectionListFragment
2026-01-20 10:10:45 -05:00
adb cb2d9f16ac Merge branch 'main' into adb/issue-4174 2026-01-20 15:47:44 +01:00
wchen342 c2a13ae9dd Merge pull request #4179 from deltachat/adb/issue-4175
avoid empty profiles after using "add as second device" from welcome screen
2026-01-20 09:35:51 -05:00
adbenitez f8c2403e61 update changelog 2026-01-19 22:26:35 +01:00
adbenitez 616b495426 only create a new account if the selected one is already configured 2026-01-19 22:13:47 +01:00
adbenitez a22baa7fc3 update changelog 2026-01-19 19:29:12 +01:00
adbenitez 0b9bb9ff76 fix: properly update menu and other actionMode state in ContactSelectionListFragment 2026-01-19 19:18:41 +01:00
adb ef96192d8b Merge pull request #4173 from deltachat/wch423/fix-resume-wrong-activity
Do not launch WelcomeActivity as singleTask
2026-01-19 18:32:00 +01:00
B. Petersen 13c34d9480 make clear, the blockage comes from user action 2026-01-19 17:47:48 +01:00
adb 610f4cebc2 Merge pull request #4171 from deltachat/wch423/add-hint-empty-group-search
Show hint during Contact search if results are empty
2026-01-19 17:45:18 +01:00
adbenitez d4d15eee7c avoid showing "no results for null" if search term is empty 2026-01-19 17:39:26 +01:00
wch423 ee3886285b Show "No results found for" during Contact search if results are empty 2026-01-19 15:46:00 +01:00
wchen342 a32c20473f Merge pull request #4172 from deltachat/wch423/blocked-contact-profile-indicator
Add indication for blocked contacts in user profile
2026-01-19 08:19:25 -05:00
wch423 db956d93cf Do not launch WelcomeActivity as singleTask 2026-01-16 19:50:29 +01:00
wch423 6a5a75131e Add indication for blocked contacts in user profile 2026-01-16 17:44:34 +01:00
wchen342 a676a47ed4 Merge pull request #4170 from deltachat/adb/issue-3913
refresh chatlist when going back from chat
2026-01-16 13:51:04 +01:00
adbenitez 2067122d2c update changelog 2026-01-15 23:41:41 +01:00
adbenitez 8a0fe892f4 refresh chatlist when going back from chat 2026-01-15 23:33:57 +01:00
adb 29a6f4eb31 Merge pull request #4167 from deltachat/wch423/mark-external-links
Mark external links with a special character
2026-01-15 19:39:51 +01:00
adb f610592bff Merge branch 'main' into wch423/mark-external-links 2026-01-15 19:20:37 +01:00
adb 898008e2a1 Merge pull request #4168 from deltachat/wch423/larger-qr-code
Use new generation method so the QR code is larger
2026-01-15 19:20:01 +01:00
adb 9eaa3ca182 Merge branch 'main' into wch423/larger-qr-code 2026-01-15 18:59:13 +01:00
adb c98818e5cf Merge pull request #4166 from deltachat/wch423/notification-permission-second-device
Ask for Notification Permission when adding second device
2026-01-15 18:57:49 +01:00
wch423 7643ecd90a Use new generation method so the QR code is larger 2026-01-15 18:25:56 +01:00
wch423 e4993bd302 Add CHANGELOG 2026-01-15 16:58:52 +01:00
wch423 32ee198128 Add util methods to handle RTL/LTR text concatenations;
Add markers to external links
2026-01-15 16:56:29 +01:00
wch423 ab0d33b3c0 Ask for Notification Permission when adding second device 2026-01-15 14:07:58 +01:00
adb 2c8b781817 Merge pull request #4165 from deltachat/wch423/fix-qr-scanner-no-error-dialog
Fix dialogs not showing when pasted QR codes are not valid
2026-01-14 21:31:59 +01:00
wch423 e59655c456 Fix dialogs not showing when pasted QR codes are not valid 2026-01-14 21:21:17 +01:00
adb 2ce7e5a3c7 Merge pull request #4164 from deltachat/wch423/remove-profile-email
Remove email address from profile
2026-01-14 16:28:25 +01:00
wch423 79c7419b71 Update CHANGELOG.md 2026-01-14 16:16:55 +01:00
wch423 2efaf1df93 Remove email address from profile 2026-01-14 15:59:24 +01:00
adb 814ac7d976 Merge pull request #4162 from deltachat/adb/remove-from-welcome-in-CreateProfileActivity
remove FROM_WELCOME in CreateProfileActivity
2026-01-13 20:40:18 +01:00
adbenitez b0e5c1c634 remove FROM_WELCOME in CreateProfileActivity 2026-01-13 20:23:41 +01:00
B. Petersen bae998f811 truncate file names in the middle
that way, the end is always visible,
which contains important information more often than the middle.

this is common knowledge since the 90's or so
however, as quite some previously stable ux patterns, lost on the way.
(cmp. Scott Jensen, https://www.youtube.com/watch?v=1fZTOjd_bOQ )
2026-01-08 17:54:50 +01:00
802 changed files with 31936 additions and 25918 deletions
+148 -9
View File
@@ -8,7 +8,7 @@ ArcaneChat is a Delta Chat Android client built on top of the official Delta Cha
- **Language:** Java (Java 8 compatibility)
- **Build System:** Gradle with Android Gradle Plugin 8.11.1
- **Min SDK:** 21 (Android 5.0)
- **Target SDK:** 35 (Android 15)
- **Target SDK:** 36 (Android 16)
- **NDK Version:** 27.0.12077973
- **Native Components:** Rust (deltachat-core-rust submodule)
- **UI Framework:** Android SDK, Material Design Components
@@ -17,13 +17,23 @@ ArcaneChat is a Delta Chat Android client built on top of the official Delta Cha
## Repository Structure
- `src/main/` - Main application source code
- `src/main/java/org/thoughtcrime/securesms/` - Main UI components
- `src/main/java/com/b44t/messenger/` - Delta Chat core integration
- `src/main/java/chat/delta/rpc/` - JSON-RPC bindings (generated, don't edit manually)
- `src/main/res/` - Android resources (layouts, strings, drawables)
- `src/androidTest/` - Instrumented tests (UI tests, benchmarks)
- `src/androidTest/java/com/b44t/messenger/uitests/` - UI tests
- `src/androidTest/java/com/b44t/messenger/uibenchmarks/` - Performance benchmarks
- `src/gplay/` - Google Play flavor-specific code
- `src/foss/` - F-Droid/FOSS flavor-specific code
- `jni/deltachat-core-rust/` - Native Rust core library (submodule)
- `jni/deltachat-core-rust/` - Native Rust core library (submodule, **don't edit directly**)
- `scripts/` - Build and helper scripts
- `scripts/ndk-make.sh` - Build native libraries
- `scripts/install-toolchains.sh` - Install Rust cross-compilation toolchains
- `scripts/generate-rpc-bindings.sh` - Generate JSON-RPC bindings
- `docs/` - Documentation
- `fastlane/` - App store metadata and screenshots
- `.github/workflows/` - CI/CD workflows (GitHub Actions)
## Build Instructions
@@ -33,17 +43,36 @@ ArcaneChat is a Delta Chat Android client built on top of the official Delta Cha
```bash
git submodule update --init --recursive
```
This MUST be done first before any build attempts.
2. **Build native libraries:**
2. **Set up environment variables:**
```bash
export ANDROID_NDK_ROOT=/path/to/ndk/27.0.12077973
export PATH=${PATH}:${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/:${ANDROID_NDK_ROOT}
```
Note: Path format varies by OS (linux-x86_64, darwin-x86_64, etc.)
3. **Install Rust toolchains:**
```bash
scripts/install-toolchains.sh
```
Required for building the native Rust components.
4. **Build native libraries:**
```bash
scripts/ndk-make.sh
```
Note: First run may take significant time as it builds for all architectures (armeabi-v7a, arm64-v8a, x86, x86_64)
**IMPORTANT:** First run takes 30-60 minutes as it builds for all architectures (armeabi-v7a, arm64-v8a, x86, x86_64).
For faster development builds, build for a single architecture:
```bash
scripts/ndk-make.sh armeabi-v7a
```
3. **Build APK:**
5. **Build APK:**
```bash
./gradlew assembleDebug
```
Build time: ~2-5 minutes after native libraries are built.
### Build Flavors
@@ -55,6 +84,24 @@ ArcaneChat is a Delta Chat Android client built on top of the official Delta Cha
- Debug APKs: `build/outputs/apk/gplay/debug/` and `build/outputs/apk/fat/debug/`
- Release APKs require signing configuration in `~/.gradle/gradle.properties`
### Common Build Issues
1. **Missing NDK or incorrect version:**
- Error: `ANDROID_NDK_ROOT not set` or native library missing
- Solution: Install NDK 27.0.12077973 and set ANDROID_NDK_ROOT environment variable
2. **Submodules not initialized:**
- Error: Missing deltachat-core-rust files
- Solution: Run `git submodule update --init --recursive`
3. **Gradle wrapper validation:**
- Always validate gradle wrapper before building: `./gradlew wrapper --gradle-version=current`
- Wrapper is validated in CI via `gradle/actions/wrapper-validation@v4`
4. **Clean build issues:**
- If build fails, try: `./gradlew clean && scripts/ndk-make.sh && ./gradlew assembleDebug`
- Remove `build/` directory if clean doesn't work
## Testing
### Running Unit Tests
@@ -62,16 +109,19 @@ ArcaneChat is a Delta Chat Android client built on top of the official Delta Cha
```bash
./gradlew test
```
Expected duration: 1-3 minutes
### Running Instrumented Tests
1. **Disable animations** on your device/emulator:
- Developer Options → Set "Window animation scale", "Transition animation scale", and "Animator duration scale" to 0x
- **CRITICAL:** Tests will fail if animations are enabled
2. **Run tests:**
```bash
./gradlew connectedAndroidTest
```
Expected duration: 10-30 minutes depending on device/emulator
### Online Tests
@@ -129,6 +179,14 @@ TEST_MAIL_PW=yourpassword
- Java bindings are in `src/main/java/com/b44t/messenger/Dc*.java`
- JSON-RPC bindings in `chat.delta.rpc.*` package (generated via dcrpcgen)
### Generating JSON-RPC Bindings
To regenerate JSON-RPC bindings after core changes:
```bash
./scripts/generate-rpc-bindings.sh
```
**Note:** Requires Rust tooling and [dcrpcgen tool](https://github.com/chatmail/dcrpcgen) installed
### Working with Translations
- Translations managed via Transifex (not in repository)
@@ -142,6 +200,43 @@ Decode crash symbols:
$ANDROID_NDK_ROOT/ndk-stack --sym obj/local/armeabi-v7a --dump crash.txt > decoded.txt
```
## Validation and Quality Checks
### Pre-commit Checks
Before committing changes, always run:
1. **Gradle wrapper validation:**
```bash
./gradlew wrapper --gradle-version=current
```
2. **Build verification:**
```bash
./gradlew assembleDebug
```
3. **Unit tests:**
```bash
./gradlew test
```
4. **Code style:** Match existing code style in modified files (no automatic formatter configured)
### When to Rebuild Native Libraries
Rebuild native libraries (`scripts/ndk-make.sh`) when:
- Updating deltachat-core-rust submodule
- Modifying anything in `jni/` directory
- Changing NDK version
- After `git clean -fdx` or fresh clone
**DO NOT** rebuild native libraries for:
- Pure Java/Kotlin code changes
- Resource file changes
- Gradle configuration changes (unless changing native library linking)
- Documentation updates
## WebXDC Support
ArcaneChat has extended WebXDC support:
@@ -152,12 +247,24 @@ ArcaneChat has extended WebXDC support:
## Important Files
- `build.gradle` - Main build configuration
- `build.gradle` - Main build configuration (Android Gradle Plugin 8.11.1, Java 8 compatibility)
- `CONTRIBUTING.md` - Contribution guidelines
- `BUILDING.md` - Detailed build setup
- `BUILDING.md` - Detailed build setup instructions
- `RELEASE.md` - Release process
- `proguard-rules.pro` - ProGuard configuration
- `google-services.json` - Firebase configuration (gplay flavor)
- `proguard-rules.pro` - ProGuard configuration (enabled for both debug and release)
- `google-services.json` - Firebase configuration (gplay flavor only)
- `settings.gradle` - Gradle settings
- `.github/workflows/` - CI/CD configuration
## Dependencies and Constraints
- **Java Version:** Java 8 compatibility (do not use Java 9+ features)
- **Gradle:** Use wrapper (`./gradlew`) to ensure correct Gradle version
- **NDK:** Must use version 27.0.12077973 (specified in build.gradle)
- **Min SDK:** 21 (Android 5.0) - code must be compatible
- **Target SDK:** 36 (Android 16) - test on this API level when possible
- **ProGuard:** Always enabled - ensure ProGuard rules are correct for new dependencies
- **Multi-dex:** Enabled - app exceeds 65k method limit
## Package Structure
@@ -173,3 +280,35 @@ ArcaneChat has extended WebXDC support:
- Native library must be rebuilt after core changes
- ProGuard is enabled in both debug and release builds
- Multi-dex is enabled due to app size
## CI/CD Workflows
### Preview APK Workflow (.github/workflows/preview-apk.yml)
Runs on every pull request to build and upload a preview APK:
1. **Setup steps:**
- Checks out repository with submodules
- Validates Fastlane metadata
- Sets up Rust cache (working-directory: jni/deltachat-core-rust)
- Sets up Java 17 (Temurin distribution)
- Sets up Android SDK
- Caches Gradle dependencies
- Sets up NDK r27
2. **Build process:**
```bash
scripts/install-toolchains.sh && scripts/ndk-make.sh armeabi-v7a
./gradlew --no-daemon -PABI_FILTER=armeabi-v7a assembleFossDebug
```
Note: Builds only armeabi-v7a for faster CI builds
3. **Output:** Uploads APK artifact to GitHub Actions
### Important CI Considerations
- Always validate Gradle wrapper before committing changes
- Fastlane metadata must be valid (validated in CI)
- Use `--no-daemon` flag for Gradle in CI environments
- CI builds use FOSS flavor to avoid Google Services dependencies
- Expected CI build time: 15-25 minutes for full workflow
+2 -2
View File
@@ -45,10 +45,10 @@ jobs:
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
run: |
export PATH="${PATH}:${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/"
scripts/install-toolchains.sh && scripts/ndk-make.sh armeabi-v7a
scripts/install-toolchains.sh && scripts/ndk-make.sh arm64-v8a
- name: Build APK
run: ./gradlew --no-daemon -PABI_FILTER=armeabi-v7a assembleFossDebug
run: ./gradlew --no-daemon -PABI_FILTER=arm64-v8a assembleFossDebug
- name: Upload APK
uses: actions/upload-artifact@v4
+39 -1
View File
@@ -2,9 +2,47 @@
## Unreleased
* Allow to set chat description
* Unified date display in call bubbles
* Explain at "Settings / Chats / Outgoing Media Quality" how to send original quality
* Add a basic sticker picker
* Leave groups and channels before deletion
* Fix: keep original sent timestamp for resent messages
* Fix: make clicking on broadcast member-added messages work always
* Fix: remove notification when a message is deleted by sender
* Update to core 2.44.0
## v2.43.0
2026-02
* Improve switch speed when changing profiles
* Allow to switch profile when sharing or forwarding
* Display message views count for channel owners
* Don't notify notification-to-all from in-chat apps if the chat is muted
* Allow to see inbox quota for all relays in connectivity screen
* Update to core 2.36.0
* Truncate file names in the middle, not at the end; important information are more often at the end
* Remove email address from user profile
* Mark external links with " ↗" to make them clear
* Make QR code larger on "Add Second Device" screen
* Add indication for blocked contacts in user profile
* Allow to start calls with video disabled
* Show hint for empty contact search results
* Add background playing for voice messages and other audio files
* Allow scanning Invitation Code when creating a new profile
* Add context menu in long-pressing relays items instead of showing buttons
* Enhanced video player UI
* Fix: Show dialog if pasted QR codes are invalid
* Fix: Refresh chat list when returning from conversation if selected profile changed
* Fix: Update menu when using "select all" in contact selection
* Fix: Avoid empty profiles after using "add as second device" from welcome screen
* Fix: Remove from group deselected members in the contact selection list
* Fix multi-device seen messages synchronization when using multiple relays
* Fix mailto handling
* Fix layout problems inside in-chat apps
* Fix real-time for in-chat apps that need it
* Avoid crash when the app is minimized with profile switcher or reactions dialogs open
* Remove "trash icon" option from contact selection list when adding members to group
* Update to core 2.43.0
## v2.35.0
2026-01
+8 -3
View File
@@ -33,8 +33,8 @@ android {
useLibrary 'org.apache.http.legacy'
defaultConfig {
versionCode 30000736
versionName "2.36.0"
versionCode 30000737
versionName "2.43.0"
applicationId "chat.delta.lite"
multiDexEnabled true
@@ -211,6 +211,8 @@ dependencies {
implementation "io.noties.markwon:inline-parser:$markwon_version"
implementation 'com.airbnb.android:lottie:4.2.2' // Lottie animations support.
def media3_version = "1.8.0" // 1.9.0 need minSdkVersion 23
implementation 'androidx.concurrent:concurrent-futures:1.3.0'
implementation 'androidx.sharetarget:sharetarget:1.2.0'
implementation 'androidx.webkit:webkit:1.14.0'
@@ -231,8 +233,11 @@ dependencies {
implementation 'androidx.work:work-runtime:2.9.1'
implementation 'androidx.emoji2:emoji2-emojipicker:1.5.0'
implementation 'com.google.guava:guava:31.1-android'
implementation 'com.google.android.exoplayer:exoplayer-core:2.19.1' // plays video and audio
implementation 'com.google.android.exoplayer:exoplayer-core:2.19.1' // FIXME: exoplayer dependencies kept for Video, but we shall migrate them at some point
implementation 'com.google.android.exoplayer:exoplayer-ui:2.19.1'
implementation "androidx.media3:media3-exoplayer:$media3_version"
implementation "androidx.media3:media3-session:$media3_version"
implementation "androidx.media3:media3-ui:$media3_version"
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
implementation 'com.google.zxing:core:3.3.0' // fixed version to support SDK<24
implementation ('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false } // QR Code scanner
+39 -3
View File
@@ -153,6 +153,42 @@ static uint32_t* jintArray2uint32Pointer(JNIEnv* env, jintArray ja, int* ret_icn
}
/************************************************************
* DcEventChannel
************************************************************/
static dc_event_channel_t* get_dc_event_channel(JNIEnv *env, jobject obj)
{
static jfieldID fid = 0;
if (fid==0) {
jclass cls = (*env)->GetObjectClass(env, obj);
fid = (*env)->GetFieldID(env, cls, "eventChannelCPtr", "J" /*Signature, J=long*/);
}
if (fid) {
return (dc_event_channel_t*)(*env)->GetLongField(env, obj, fid);
}
return NULL;
}
JNIEXPORT jlong Java_com_b44t_messenger_DcEventChannel_createEventChannelCPtr(JNIEnv *env, jobject obj)
{
return (jlong)dc_event_channel_new();
}
JNIEXPORT void Java_com_b44t_messenger_DcEventChannel_unrefEventChannelCPtr(JNIEnv *env, jobject obj)
{
dc_event_channel_unref(get_dc_event_channel(env, obj));
}
JNIEXPORT jlong Java_com_b44t_messenger_DcEventChannel_getEventEmitterCPtr(JNIEnv *env, jobject obj)
{
return (jlong)dc_event_channel_get_event_emitter(get_dc_event_channel(env, obj));
}
/*******************************************************************************
* DcAccounts
******************************************************************************/
@@ -172,11 +208,11 @@ static dc_accounts_t* get_dc_accounts(JNIEnv *env, jobject obj)
}
JNIEXPORT jlong Java_com_b44t_messenger_DcAccounts_createAccountsCPtr(JNIEnv *env, jobject obj, jstring dir)
JNIEXPORT jlong Java_com_b44t_messenger_DcAccounts_createAccountsCPtr(JNIEnv *env, jobject obj, jstring dir, jobject chanObj)
{
CHAR_REF(dir);
int writable = 1;
jlong accountsCPtr = (jlong)dc_accounts_new(dirPtr, writable);
jlong accountsCPtr = (jlong)dc_accounts_new_with_event_channel(dirPtr, writable, get_dc_event_channel(env, chanObj));
CHAR_UNREF(dir);
return accountsCPtr;
}
@@ -1957,7 +1993,7 @@ JNIEXPORT jstring Java_com_b44t_messenger_DcBackupProvider_getQr(JNIEnv *env, jo
JNIEXPORT jstring Java_com_b44t_messenger_DcBackupProvider_getQrSvg(JNIEnv *env, jobject obj)
{
char* temp = dc_backup_provider_get_qr_svg(get_dc_backup_provider(env, obj));
char* temp = dc_create_qr_svg(dc_backup_provider_get_qr(get_dc_backup_provider(env, obj)));
jstring ret = JSTRING_NEW(temp);
dc_str_unref(temp);
return ret;
@@ -11,7 +11,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.test.espresso.NoMatchingViewException;
import androidx.test.espresso.UiController;
@@ -19,7 +18,6 @@ import androidx.test.espresso.ViewAction;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.util.TreeIterables;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import org.hamcrest.Matcher;
import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R;
@@ -60,10 +58,12 @@ public class TestUtils {
}
@NonNull
public static ActivityScenarioRule<ConversationListActivity> getOfflineActivityRule(boolean useExistingChats) {
public static ActivityScenarioRule<ConversationListActivity> getOfflineActivityRule(
boolean useExistingChats) {
Intent intent =
Intent.makeMainActivity(
new ComponentName(getInstrumentation().getTargetContext(), ConversationListActivity.class));
Intent.makeMainActivity(
new ComponentName(
getInstrumentation().getTargetContext(), ConversationListActivity.class));
if (!useExistingChats) {
createOfflineAccount();
}
@@ -72,18 +72,22 @@ public class TestUtils {
}
@NonNull
public static <T extends Activity> ActivityScenarioRule<T> getOnlineActivityRule(Class<T> activityClass) {
public static <T extends Activity> ActivityScenarioRule<T> getOnlineActivityRule(
Class<T> activityClass) {
Context context = getInstrumentation().getTargetContext();
AccountManager.getInstance().beginAccountCreation(context);
prepare();
return new ActivityScenarioRule<>(new Intent(getInstrumentation().getTargetContext(), activityClass));
return new ActivityScenarioRule<>(
new Intent(getInstrumentation().getTargetContext(), activityClass));
}
private static void prepare() {
Prefs.setBooleanPreference(getInstrumentation().getTargetContext(), Prefs.DOZE_ASKED_DIRECTLY, true);
Prefs.setBooleanPreference(
getInstrumentation().getTargetContext(), Prefs.DOZE_ASKED_DIRECTLY, true);
if (!AccessibilityUtil.areAnimationsDisabled(getInstrumentation().getTargetContext())) {
throw new RuntimeException("To run the tests, disable animations at Developer options' " +
"-> 'Window/Transition/Animator animation scale' -> Set all 3 to 'off'");
throw new RuntimeException(
"To run the tests, disable animations at Developer options' "
+ "-> 'Window/Transition/Animator animation scale' -> Set all 3 to 'off'");
}
}
@@ -116,26 +120,22 @@ public class TestUtils {
}
throw new NoMatchingViewException.Builder()
.withRootView(view)
.withViewMatcher(matcher)
.build();
.withRootView(view)
.withViewMatcher(matcher)
.build();
}
};
}
/**
* Perform action of implicitly waiting for a certain view.
* This differs from EspressoExtensions.searchFor in that,
* upon failure to locate an element, it will fetch a new root view
* in which to traverse searching for our @param match
* Perform action of implicitly waiting for a certain view. This differs from
* EspressoExtensions.searchFor in that, upon failure to locate an element, it will fetch a new
* root view in which to traverse searching for our @param match
*
* @param viewMatcher ViewMatcher used to find our view
*/
public static ViewInteraction waitForView(
Matcher<View> viewMatcher,
int waitMillis,
int waitMillisPerTry
) {
Matcher<View> viewMatcher, int waitMillis, int waitMillisPerTry) {
// Derive the max tries
int maxTries = (int) (waitMillis / waitMillisPerTry);
@@ -164,12 +164,11 @@ public class TestUtils {
}
/**
* Normally, you would do
* onView(withId(R.id.send_button)).perform(click());
* to send the draft message. However, in order to change the send button to the attach button
* while there is no draft, the send button is made invisible and the attach button is made
* visible instead. This confuses the test framework.<br/><br/>
*
* Normally, you would do onView(withId(R.id.send_button)).perform(click()); to send the draft
* message. However, in order to change the send button to the attach button while there is no
* draft, the send button is made invisible and the attach button is made visible instead. This
* confuses the test framework.<br>
* <br>
* So, this is a workaround for pressing the send button.
*/
public static void pressSend() {
@@ -10,14 +10,11 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import android.util.Log;
import androidx.test.espresso.contrib.RecyclerViewActions;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.b44t.messenger.TestUtils;
import org.junit.After;
import org.junit.Ignore;
import org.junit.Rule;
@@ -34,18 +31,19 @@ public class EnterChatsBenchmark {
// ==============================================================================================
// Set this to true if you already have at least 10 chats on your existing DeltaChat installation
// and want to traverse through them instead of 10 newly created chats
private final static boolean USE_EXISTING_CHATS = false;
private static final boolean USE_EXISTING_CHATS = false;
// ==============================================================================================
private final static int GO_THROUGH_ALL_CHATS_N_TIMES = 8;
private static final int GO_THROUGH_ALL_CHATS_N_TIMES = 8;
// ==============================================================================================
// PLEASE BACKUP YOUR ACCOUNT BEFORE RUNNING THIS!
// ==============================================================================================
private final static String TAG = EnterChatsBenchmark.class.getSimpleName();
private static final String TAG = EnterChatsBenchmark.class.getSimpleName();
@Rule
public ActivityScenarioRule<ConversationListActivity> activityRule = TestUtils.getOfflineActivityRule(USE_EXISTING_CHATS);
public ActivityScenarioRule<ConversationListActivity> activityRule =
TestUtils.getOfflineActivityRule(USE_EXISTING_CHATS);
@Test
public void createAndEnter10FilledChats() {
@@ -55,7 +53,9 @@ public class EnterChatsBenchmark {
for (int i = 0; i < GO_THROUGH_ALL_CHATS_N_TIMES; i++) {
times[i] = "" + timeGoToNChats(10); // 10 group chats were created
}
Log.i(TAG, "MEASURED RESULTS (Benchmark) - Going thorough all 10 chats: " + String.join(",", times));
Log.i(
TAG,
"MEASURED RESULTS (Benchmark) - Going thorough all 10 chats: " + String.join(",", times));
}
@Test
@@ -66,13 +66,17 @@ public class EnterChatsBenchmark {
for (int i = 0; i < GO_THROUGH_ALL_CHATS_N_TIMES; i++) {
times[i] = "" + timeGoToNChats(1);
}
Log.i(TAG, "MEASURED RESULTS (Benchmark) - Entering and leaving 1 empty chat: " + String.join(",", times));
Log.i(
TAG,
"MEASURED RESULTS (Benchmark) - Entering and leaving 1 empty chat: "
+ String.join(",", times));
}
@Test
public void enterFilledChat() {
if (!USE_EXISTING_CHATS) {
createChatAndGoBack("Group #1", true, "Hello!", "Some links: https://testrun.org", "And a command: /help");
createChatAndGoBack(
"Group #1", true, "Hello!", "Some links: https://testrun.org", "And a command: /help");
}
String[] times = new String[50];
@@ -82,7 +86,18 @@ public class EnterChatsBenchmark {
long end = System.currentTimeMillis();
long diff = end - start;
pressBack();
Log.i(TAG, "Measured (Benchmark) " + (i+1) + "/" + times.length + ": Entering 1 filled chat took " + diff + "ms " + "(going back took " + (System.currentTimeMillis() - end) + "ms)");
Log.i(
TAG,
"Measured (Benchmark) "
+ (i + 1)
+ "/"
+ times.length
+ ": Entering 1 filled chat took "
+ diff
+ "ms "
+ "(going back took "
+ (System.currentTimeMillis() - end)
+ "ms)");
times[i] = "" + diff;
}
@@ -91,16 +106,37 @@ public class EnterChatsBenchmark {
private void create10Chats(boolean fillWithMsgs) {
if (!USE_EXISTING_CHATS) {
createChatAndGoBack("Group #1", fillWithMsgs, "Hello!", "Some links: https://testrun.org", "And a command: /help");
createChatAndGoBack("Group #2", fillWithMsgs, "example.org, alice@example.org", "aaaaaaa", "bbbbbb");
createChatAndGoBack("Group #3", fillWithMsgs, repeat("Some string ", 600), repeat("Another string", 200), "Hi!!!");
createChatAndGoBack(
"Group #1",
fillWithMsgs,
"Hello!",
"Some links: https://testrun.org",
"And a command: /help");
createChatAndGoBack(
"Group #2", fillWithMsgs, "example.org, alice@example.org", "aaaaaaa", "bbbbbb");
createChatAndGoBack(
"Group #3",
fillWithMsgs,
repeat("Some string ", 600),
repeat("Another string", 200),
"Hi!!!");
createChatAndGoBack("Group #4", fillWithMsgs, "xyzabc", "Hi!!!!", "Let's meet!");
createChatAndGoBack("Group #5", fillWithMsgs, repeat("aaaa", 40), "bbbbbbbbbbbbbbbbbb", "ccccccccccccccc");
createChatAndGoBack("Group #6", fillWithMsgs, "aaaaaaaaaaa", repeat("Hi! ", 1000), "bbbbbbbbbb");
createChatAndGoBack("Group #7", fillWithMsgs, repeat("abcdefg ", 500), repeat("xxxxx", 100), "yrrrrrrrrrrrrr");
createChatAndGoBack("Group #8", fillWithMsgs, "and a number: 037362/384756", "ccccc", "Nice!");
createChatAndGoBack("Group #9", fillWithMsgs, "ddddddddddddddddd", "zuuuuuuuuuuuuuuuu", "ccccc");
createChatAndGoBack("Group #10", fillWithMsgs, repeat("xxxxxxyyyyy", 100), repeat("String!!", 10), "abcd");
createChatAndGoBack(
"Group #5", fillWithMsgs, repeat("aaaa", 40), "bbbbbbbbbbbbbbbbbb", "ccccccccccccccc");
createChatAndGoBack(
"Group #6", fillWithMsgs, "aaaaaaaaaaa", repeat("Hi! ", 1000), "bbbbbbbbbb");
createChatAndGoBack(
"Group #7",
fillWithMsgs,
repeat("abcdefg ", 500),
repeat("xxxxx", 100),
"yrrrrrrrrrrrrr");
createChatAndGoBack(
"Group #8", fillWithMsgs, "and a number: 037362/384756", "ccccc", "Nice!");
createChatAndGoBack(
"Group #9", fillWithMsgs, "ddddddddddddddddd", "zuuuuuuuuuuuuuuuu", "ccccc");
createChatAndGoBack(
"Group #10", fillWithMsgs, repeat("xxxxxxyyyyy", 100), repeat("String!!", 10), "abcd");
}
}
@@ -130,10 +166,10 @@ public class EnterChatsBenchmark {
onView(withContentDescription(R.string.group_create_button)).perform(click());
if (fillWithMsgs) {
for (String t: texts) {
for (String t : texts) {
sendText(t);
}
for (String t: texts) {
for (String t : texts) {
sendText(t);
}
}
@@ -14,11 +14,10 @@ import androidx.test.espresso.contrib.RecyclerViewActions;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.TestUtils;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -29,8 +28,6 @@ import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.connect.DcHelper;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ForwardingTest {
@@ -43,7 +40,8 @@ public class ForwardingTest {
}
@Rule
public final ActivityScenarioRule<ConversationListActivity> activityRule = TestUtils.getOfflineActivityRule(false);
public final ActivityScenarioRule<ConversationListActivity> activityRule =
TestUtils.getOfflineActivityRule(false);
@Before
public void createChats() {
@@ -51,10 +49,13 @@ public class ForwardingTest {
dcContext.createChatByContactId(DcContact.DC_CONTACT_ID_SELF);
// Disable bcc_self so that DC doesn't try to send messages to the server.
// If we didn't do this, messages would stay in DC_STATE_OUT_PENDING forever.
// The thing is, DC_STATE_OUT_PENDING show a rotating circle animation, and Espresso doesn't work
// The thing is, DC_STATE_OUT_PENDING show a rotating circle animation, and Espresso doesn't
// work
// with animations, and the tests would hang and never finish.
dcContext.setConfig("bcc_self", "0");
activityRule.getScenario().onActivity(a -> createdGroupId = DcHelper.getContext(a).createGroupChat( "group"));
activityRule
.getScenario()
.onActivity(a -> createdGroupId = DcHelper.getContext(a).createGroupChat("group"));
}
@After
@@ -68,7 +69,8 @@ public class ForwardingTest {
// The group is at position 0, self chat is at position 1, device talk is at position 2
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(2, click()));
onView(withId(R.id.title)).check(matches(withText(R.string.device_talk)));
onView(withId(android.R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(0, longClick()));
onView(withId(android.R.id.list))
.perform(RecyclerViewActions.actionOnItemAtPosition(0, longClick()));
onView(withId(R.id.menu_context_forward)).perform(click());
// Send it to self chat (which is sorted to the top because we're forwarding)
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
@@ -77,11 +79,13 @@ public class ForwardingTest {
pressBack();
onView(withId(R.id.toolbar_title)).check(matches(withText(R.string.connectivity_not_connected)));
onView(withId(R.id.toolbar_title))
.check(matches(withText(R.string.connectivity_not_connected)));
// Self chat moved up because we sent a message there
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
onView(withId(R.id.title)).check(matches(withText(R.string.saved_messages)));
onView(withId(android.R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(0, longClick()));
onView(withId(android.R.id.list))
.perform(RecyclerViewActions.actionOnItemAtPosition(0, longClick()));
onView(withId(R.id.menu_context_forward)).perform(click());
// Send it to the group
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(1, click()));
@@ -89,6 +93,7 @@ public class ForwardingTest {
onView(withId(R.id.title)).check(matches(withText("group")));
pressBack();
onView(withId(R.id.toolbar_title)).check(matches(withText(R.string.connectivity_not_connected)));
onView(withId(R.id.toolbar_title))
.check(matches(withText(R.string.connectivity_not_connected)));
}
}
@@ -14,15 +14,13 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import androidx.test.espresso.contrib.RecyclerViewActions;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.TestUtils;
import java.io.File;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -34,9 +32,6 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.ShareActivity;
import org.thoughtcrime.securesms.connect.DcHelper;
import java.io.File;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class SharingTest {
@@ -48,26 +43,34 @@ public class SharingTest {
private static int createdSingleChatId;
@Rule
public final ActivityScenarioRule<ConversationListActivity> activityRule = TestUtils.getOfflineActivityRule(false);
public final ActivityScenarioRule<ConversationListActivity> activityRule =
TestUtils.getOfflineActivityRule(false);
@Before
public void createGroup() {
activityRule.getScenario().onActivity(a -> createdGroupId = DcHelper.getContext(a).createGroupChat( "group"));
activityRule
.getScenario()
.onActivity(a -> createdGroupId = DcHelper.getContext(a).createGroupChat("group"));
}
@Before
public void createSingleChat() {
activityRule.getScenario().onActivity(a -> {
int contactId = DcHelper.getContext(a).createContact("", "abc@example.org");
createdSingleChatId = DcHelper.getContext(a).createChatByContactId(contactId);
});
activityRule
.getScenario()
.onActivity(
a -> {
int contactId = DcHelper.getContext(a).createContact("", "abc@example.org");
createdSingleChatId = DcHelper.getContext(a).createChatByContactId(contactId);
});
}
@Test
public void testNormalSharing() {
Intent i = new Intent(Intent.ACTION_SEND);
i.putExtra(Intent.EXTRA_TEXT, "Hello!");
i.setComponent(new ComponentName(getInstrumentation().getTargetContext().getApplicationContext(), ShareActivity.class));
i.setComponent(
new ComponentName(
getInstrumentation().getTargetContext().getApplicationContext(), ShareActivity.class));
activityRule.getScenario().onActivity(a -> a.startActivity(i));
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
@@ -77,9 +80,9 @@ public class SharingTest {
}
/**
* Test direct sharing from a screenshot.
* Also, this is the regression test for https://github.com/deltachat/deltachat-android/issues/2040
* where network changes during sharing lead to a bug
* Test direct sharing from a screenshot. Also, this is the regression test for
* https://github.com/deltachat/deltachat-android/issues/2040 where network changes during sharing
* lead to a bug
*/
@Test
public void testShareFromScreenshot() {
@@ -92,20 +95,25 @@ public class SharingTest {
pngImage = file;
}
}
Uri uri = Uri.parse("content://" + BuildConfig.APPLICATION_ID + ".attachments/" + Uri.encode(pngImage));
Uri uri =
Uri.parse(
"content://" + BuildConfig.APPLICATION_ID + ".attachments/" + Uri.encode(pngImage));
DcHelper.sharedFiles.put(pngImage, "image/png");
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("image/png");
i.putExtra(Intent.EXTRA_SUBJECT, "Screenshot (Sep 27, 2021 00:00:00");
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
i.setFlags(
Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
| Intent.FLAG_ACTIVITY_FORWARD_RESULT
| Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
| Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_GRANT_READ_URI_PERMISSION);
i.putExtra(Intent.EXTRA_STREAM, uri);
i.putExtra(ShareActivity.EXTRA_CHAT_ID, createdGroupId);
i.setComponent(new ComponentName(getInstrumentation().getTargetContext().getApplicationContext(), ShareActivity.class));
i.setComponent(
new ComponentName(
getInstrumentation().getTargetContext().getApplicationContext(), ShareActivity.class));
activityRule.getScenario().onActivity(a -> a.startActivity(i));
TestUtils.waitForView(withId(R.id.send_button), 10000, 50);
@@ -121,16 +129,32 @@ public class SharingTest {
}
/**
* Tests https://github.com/deltachat/interface/blob/master/user-testing/mailto-links.md#mailto-links:
* Tests
* https://github.com/deltachat/interface/blob/master/user-testing/mailto-links.md#mailto-links:
*
* <ul dir="auto">
* <li><a href="mailto:abc@example.org">Just an email address</a> - should open a chat with <code>abc@example.org</code> (and maybe ask whether a chat should be created if it does not exist already)</li>
* <li><a href="mailto:abc@example.org?subject=testing%20mailto%20uris">email address with subject</a> - should open a chat with <code>abc@example.org</code> and fill <code>testing mailto uris</code>; as we created the chat in the previous step, it should not ask <code>Chat with …</code> but directly open the chat</li>
* <li><a href="mailto:abc@example.org?body=this%20is%20a%20test">email address with body</a> - should open a chat with <code>abc@example.org</code>, draft <code>this is a test</code></li>
* <li><a href="mailto:abc@example.org?subject=testing%20mailto%20uris&amp;body=this%20is%20a%20test">email address with subject and body</a> - should open a chat with <code>abc@example.org</code>, draft <code>testing mailto uris</code> &lt;newline&gt; <code>this is a test</code></li>
* <li><a href="mailto:%20info@example.org">HTML encoding</a> - should open a chat with <code>info@example.org</code></li>
* <li><a href="mailto:simplebot@example.org?body=!web%20https%3A%2F%2Fduckduckgo.com%2Flite%3Fq%3Dduck%2520it">more HTML encoding</a> - should open a chat with <code>simplebot@example.org</code>, draft <code>!web https://duckduckgo.com/lite?q=duck%20it</code></li>
* <li><a href="mailto:?subject=bla&amp;body=blub">no email, just subject&amp;body</a> - this should let you choose a chat and create a draft <code>bla</code> &lt;newline&gt; <code>blub</code> there</li>
* <li><a href="mailto:abc@example.org">Just an email address</a> - should open a chat with
* <code>abc@example.org</code> (and maybe ask whether a chat should be created if it does
* not exist already)
* <li><a href="mailto:abc@example.org?subject=testing%20mailto%20uris">email address with
* subject</a> - should open a chat with <code>abc@example.org</code> and fill <code>
* testing mailto uris</code>; as we created the chat in the previous step, it should not
* ask <code>Chat with …</code> but directly open the chat
* <li><a href="mailto:abc@example.org?body=this%20is%20a%20test">email address with body</a> -
* should open a chat with <code>abc@example.org</code>, draft <code>this is a test</code>
* <li><a
* href="mailto:abc@example.org?subject=testing%20mailto%20uris&amp;body=this%20is%20a%20test">email
* address with subject and body</a> - should open a chat with <code>abc@example.org</code>,
* draft <code>testing mailto uris</code> &lt;newline&gt; <code>this is a test</code>
* <li><a href="mailto:%20info@example.org">HTML encoding</a> - should open a chat with <code>
* info@example.org</code>
* <li><a
* href="mailto:simplebot@example.org?body=!web%20https%3A%2F%2Fduckduckgo.com%2Flite%3Fq%3Dduck%2520it">more
* HTML encoding</a> - should open a chat with <code>simplebot@example.org</code>, draft
* <code>!web https://duckduckgo.com/lite?q=duck%20it</code>
* <li><a href="mailto:?subject=bla&amp;body=blub">no email, just subject&amp;body</a> - this
* should let you choose a chat and create a draft <code>bla</code> &lt;newline&gt; <code>
* blub</code> there
* </ul>
*/
@Test
@@ -140,7 +164,8 @@ public class SharingTest {
openLink("mailto:abc@example.org?subject=testing%20mailto%20uris");
onView(withId(R.id.subtitle)).check(matches(withText("abc@example.org")));
onView(withHint(R.string.chat_input_placeholder)).check(matches(withText("testing mailto uris")));
onView(withHint(R.string.chat_input_placeholder))
.check(matches(withText("testing mailto uris")));
openLink("mailto:abc@example.org?body=this%20is%20a%20test");
onView(withId(R.id.subtitle)).check(matches(withText("abc@example.org")));
@@ -148,17 +173,22 @@ public class SharingTest {
openLink("mailto:abc@example.org?subject=testing%20mailto%20uris&body=this%20is%20a%20test");
onView(withId(R.id.subtitle)).check(matches(withText("abc@example.org")));
onView(withHint(R.string.chat_input_placeholder)).check(matches(withText("testing mailto uris\nthis is a test")));
onView(withHint(R.string.chat_input_placeholder))
.check(matches(withText("testing mailto uris\nthis is a test")));
openLink("mailto:%20abc@example.org");
onView(withId(R.id.subtitle)).check(matches(withText("abc@example.org")));
openLink("mailto:abc@example.org?body=!web%20https%3A%2F%2Fduckduckgo.com%2Flite%3Fq%3Dduck%2520it");
openLink(
"mailto:abc@example.org?body=!web%20https%3A%2F%2Fduckduckgo.com%2Flite%3Fq%3Dduck%2520it");
onView(withId(R.id.subtitle)).check(matches(withText("abc@example.org")));
onView(withHint(R.string.chat_input_placeholder)).check(matches(withText("!web https://duckduckgo.com/lite?q=duck%20it")));
onView(withHint(R.string.chat_input_placeholder))
.check(matches(withText("!web https://duckduckgo.com/lite?q=duck%20it")));
openLink("mailto:?subject=bla&body=blub");
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
onView(withId(R.id.list))
.perform(
RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
onView(withId(R.id.subtitle)).check(matches(withText("abc@example.org")));
onView(withHint(R.string.chat_input_placeholder)).check(matches(withText("bla\nblub")));
}
@@ -170,49 +200,61 @@ public class SharingTest {
}
/**
*
*
* <ul dir="auto">
* <li>Open Saved Messages chat (could be any other chat too)</li>
* <li>Go to another app and share some text to DC</li>
* <li>In DC select Saved Messages. Edit the shared text if you like. <em>Don't</em> hit the Send button.</li>
* <li>Leave DC</li>
* <li>Open DC again from the "Recent apps"</li>
* <li>Check that your draft is still there</li>
* <li>Open Saved Messages chat (could be any other chat too)
* <li>Go to another app and share some text to DC
* <li>In DC select Saved Messages. Edit the shared text if you like. <em>Don't</em> hit the
* Send button.
* <li>Leave DC
* <li>Open DC again from the "Recent apps"
* <li>Check that your draft is still there
* </ul>
*/
@Test
public void testOpenAgainFromRecents() {
// Open a chat
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
onView(withId(R.id.list))
.perform(
RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
// Share some text to DC
Intent i = new Intent(Intent.ACTION_SEND);
i.putExtra(Intent.EXTRA_TEXT, "Veeery important draft");
i.setComponent(new ComponentName(getInstrumentation().getTargetContext().getApplicationContext(), ShareActivity.class));
i.setComponent(
new ComponentName(
getInstrumentation().getTargetContext().getApplicationContext(), ShareActivity.class));
activityRule.getScenario().onActivity(a -> a.startActivity(i));
// In DC, select the same chat you opened before
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
onView(withId(R.id.list))
.perform(
RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
// Leave DC and go back to the previous activity
pressBack();
// Here, we can't exactly replicate the "steps to reproduce". Previously, the other activity
// stayed open in the background, but since it doesn't anymore, we need to open it again:
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
onView(withId(R.id.list))
.perform(
RecyclerViewActions.actionOnItem(hasDescendant(withText("abc@example.org")), click()));
// Check that the draft is still there
// Util.sleep(2000); // Uncomment for debugging
onView(withHint(R.string.chat_input_placeholder)).check(matches(withText("Veeery important draft")));
onView(withHint(R.string.chat_input_placeholder))
.check(matches(withText("Veeery important draft")));
}
/**
* Regression test:
*
* If you save your contacts's emails in the contacts app of the phone, there are buttons to call
* them and also to write an email to them.
* <p>If you save your contacts's emails in the contacts app of the phone, there are buttons to
* call them and also to write an email to them.
*
* If you click the email button, ArcaneChat opened but instead of opening a chat with that contact,
* the chat list was show and "share with" was displayed at the top
* <p>If you click the email button, Delta Chat opened but instead of opening a chat with that
* contact, the chat list was show and "share with" was displayed at the top
*/
@Test
public void testOpenChatFromContacts() {
@@ -1,6 +1,5 @@
package com.b44t.messenger.uitests.online;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.replaceText;
@@ -11,13 +10,10 @@ import static androidx.test.espresso.matcher.ViewMatchers.withHint;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import android.text.TextUtils;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.b44t.messenger.TestUtils;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
@@ -30,14 +26,16 @@ import org.thoughtcrime.securesms.WelcomeActivity;
@LargeTest
public class OnboardingTest {
@Rule
public ActivityScenarioRule<WelcomeActivity> activityRule = TestUtils.getOnlineActivityRule(WelcomeActivity.class);
public ActivityScenarioRule<WelcomeActivity> activityRule =
TestUtils.getOnlineActivityRule(WelcomeActivity.class);
@Test
public void testAccountCreation() {
if (TextUtils.isEmpty(BuildConfig.TEST_ADDR) || TextUtils.isEmpty(BuildConfig.TEST_MAIL_PW)) {
throw new RuntimeException("You need to set TEST_ADDR and TEST_MAIL_PW; " +
"either in gradle.properties or via an environment variable. " +
"See README.md for more details.");
throw new RuntimeException(
"You need to set TEST_ADDR and TEST_MAIL_PW; "
+ "either in gradle.properties or via an environment variable. "
+ "See README.md for more details.");
}
onView(withText(R.string.scan_invitation_code)).check(matches(isClickable()));
onView(withText(R.string.import_backup_title)).check(matches(isClickable()));
@@ -0,0 +1,19 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="228"
android:viewportHeight="280">
<group
android:scaleX="0.35014287"
android:scaleY="0.43"
android:translateX="74.08372"
android:translateY="79.8">
<path
android:pathData="m10.03,234.14c0.3,-0.01 0.6,-0.02 0.9,-0.04 -0.07,-0.49 -0.14,-0.97 -0.2,-1.46 -0.15,-1.34 -0.26,-2.69 -0.25,-4.04 -0.02,-0.86 -0.05,-1.71 -0.07,-2.57 -0.09,-0.06 -0.18,-0.13 -0.27,-0.19 -0.02,-0.02 -0.04,-0.03 -0.07,-0.05zM44.87,232.95c11.71,-8.35 26.86,-14.79 46.21,-15.9 0,0 39.93,-0.27 47.91,-3.53 7.98,-3.26 68.68,-14.69 82.94,-98.43 14.26,-83.74 -1.06,-115.09 -1.06,-115.09 0,0 -21.14,55.68 -81.02,59.81 0,0 -14.5,1.03 -38.82,1.42 -24.32,0.39 -75.77,20.65 -90.55,85.62l-0.22,43.44c2.5,4.22 5.49,8.12 8.91,11.66 3.99,4.11 8.11,8.12 12.79,11.45 2.26,1.65 4.65,3.2 6.51,5.33 1.94,2.34 3.33,5 4.2,7.93 0.71,2.1 1.45,4.2 2.2,6.28z"
android:fillColor="@color/ic_launcher_background" />
<path
android:pathData="m217.97,45.86c-0.3,0.01 -0.6,0.02 -0.9,0.04 0.07,0.49 0.14,0.97 0.2,1.46 0.15,1.34 0.26,2.69 0.25,4.04 0.02,0.86 0.05,1.71 0.07,2.57 0.09,0.06 0.18,0.13 0.27,0.19 0.02,0.02 0.04,0.03 0.07,0.05zM183.13,47.05c-11.71,8.35 -26.86,14.79 -46.21,15.9 0,0 -39.93,0.27 -47.91,3.53 -7.98,3.26 -68.68,14.69 -82.94,98.43 -14.26,83.74 1.06,115.09 1.06,115.09 0,0 21.14,-55.68 81.02,-59.81 0,0 14.5,-1.03 38.82,-1.42 24.32,-0.39 75.77,-20.65 90.55,-85.62l0.22,-43.44c-2.5,-4.22 -5.49,-8.12 -8.91,-11.66 -3.99,-4.11 -8.11,-8.12 -12.79,-11.45 -2.26,-1.65 -4.65,-3.2 -6.51,-5.33 -1.94,-2.34 -3.33,-5 -4.2,-7.93 -0.71,-2.1 -1.45,-4.2 -2.2,-6.28z"
android:fillColor="@color/ic_launcher_background" />
</group>
</vector>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/white" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/white" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
+11 -9
View File
@@ -1,14 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<application>
<service
android:name=".connect.KeepAliveService"
android:foregroundServiceType="specialUse"
android:enabled="true" />
</application>
<application>
<service
android:name=".connect.KeepAliveService"
android:foregroundServiceType="specialUse"
android:enabled="true" />
</application>
</manifest>
@@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.notifications;
import android.content.Context;
import androidx.annotation.Nullable;
/*
@@ -10,6 +9,11 @@ import androidx.annotation.Nullable;
*/
public class FcmReceiveService {
public static void register(Context context) {}
public static void waitForRegisterFinished() {}
@Nullable public static String getToken() { return null; }
@Nullable
public static String getToken() {
return null;
}
}
+34 -26
View File
@@ -1,34 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- force compiling emojipicker on sdk<21 and firebase on sdk<19; runtime checks are required then -->
<uses-sdk tools:overrideLibrary="androidx.emoji2.emojipicker, com.google.firebase.messaging, com.google.android.gms.cloudmessaging"/>
<!-- force compiling emojipicker on sdk<21 and firebase on sdk<19; runtime checks are required then -->
<uses-sdk
tools:overrideLibrary="androidx.emoji2.emojipicker, com.google.firebase.messaging, com.google.android.gms.cloudmessaging" />
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
<meta-data
android:name="firebase_analytics_collection_deactivated"
android:value="true" />
<meta-data
android:name="google_analytics_adid_collection_enabled"
android:value="false" />
<meta-data
android:name="firebase_messaging_auto_init_enabled"
android:value="false" />
<application>
<service
android:name=".connect.KeepAliveService"
android:foregroundServiceType="dataSync"
android:enabled="true" />
<application>
<service
android:name=".connect.KeepAliveService"
android:foregroundServiceType="dataSync"
android:enabled="true" />
<service
android:name=".notifications.FcmReceiveService"
android:foregroundServiceType="dataSync"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service
android:name=".notifications.FcmReceiveService"
android:foregroundServiceType="dataSync"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<provider
android:name="com.google.firebase.provider.FirebaseInitProvider"
android:authorities="${applicationId}.firebaseinitprovider"
tools:node="remove">
</provider>
</application>
<provider
android:name="com.google.firebase.provider.FirebaseInitProvider"
android:authorities="${applicationId}.firebaseinitprovider"
tools:node="remove">
</provider>
</application>
</manifest>
@@ -1,20 +1,16 @@
package org.thoughtcrime.securesms.notifications;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.FirebaseApp;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.service.FetchForegroundService;
@@ -35,36 +31,38 @@ public class FcmReceiveService extends FirebaseMessagingService {
return;
}
Util.runOnAnyBackgroundThread(() -> {
final String rawToken;
Util.runOnAnyBackgroundThread(
() -> {
final String rawToken;
try {
synchronized (INIT_LOCK) {
if (!initialized) {
// manual init: read tokens from `./google-services.json`;
// automatic init disabled in AndroidManifest.xml to skip FCM code completely.
FirebaseApp.initializeApp(context);
try {
synchronized (INIT_LOCK) {
if (!initialized) {
// manual init: read tokens from `./google-services.json`;
// automatic init disabled in AndroidManifest.xml to skip FCM code completely.
FirebaseApp.initializeApp(context);
}
initialized = true;
}
rawToken = Tasks.await(FirebaseMessaging.getInstance().getToken());
} catch (Exception e) {
// we're here usually when FCM is not available and initializeApp() or getToken()
// failed.
Log.w(TAG, "cannot get FCM token for " + BuildConfig.APPLICATION_ID + ": " + e);
triedRegistering = true;
return;
}
if (TextUtils.isEmpty(rawToken)) {
Log.w(TAG, "got empty FCM token for " + BuildConfig.APPLICATION_ID);
triedRegistering = true;
return;
}
initialized = true;
}
rawToken = Tasks.await(FirebaseMessaging.getInstance().getToken());
} catch (Exception e) {
// we're here usually when FCM is not available and initializeApp() or getToken() failed.
Log.w(TAG, "cannot get FCM token for " + BuildConfig.APPLICATION_ID + ": " + e);
triedRegistering = true;
return;
}
if (TextUtils.isEmpty(rawToken)) {
Log.w(TAG, "got empty FCM token for " + BuildConfig.APPLICATION_ID);
triedRegistering = true;
return;
}
prefixedToken = addPrefix(rawToken);
Log.i(TAG, "FCM token: " + prefixedToken);
ApplicationContext.getDcAccounts().setPushDeviceToken(prefixedToken);
triedRegistering = true;
});
prefixedToken = addPrefix(rawToken);
Log.i(TAG, "FCM token: " + prefixedToken);
ApplicationContext.getDcAccounts().setPushDeviceToken(prefixedToken);
triedRegistering = true;
});
}
// wait a until FCM registration got a token or not.
@@ -98,7 +96,8 @@ public class FcmReceiveService extends FirebaseMessagingService {
// ForegroundServiceStartNotAllowedException.
// So, it's recommended to check the result of RemoteMessage.getPriority() and
// confirm it's PRIORITY_HIGH() before attempting to start a foreground service.
// source: https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start
// source:
// https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start
if (remoteMessage.getPriority() == RemoteMessage.PRIORITY_HIGH) {
FetchForegroundService.start(this);
} else {
+12 -2
View File
@@ -38,6 +38,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<!-- force compiling emojipicker on sdk<21; runtime checks are required then -->
<uses-sdk tools:overrideLibrary="androidx.emoji2.emojipicker"/>
@@ -54,7 +55,6 @@
android:requestLegacyExternalStorage="true"
tools:targetApi="TIRAMISU"
android:hasFragileUserData="true"
android:enableOnBackInvokedCallback="false"
>
<!-- android car support, see https://developer.android.com/training/auto/start/,
@@ -93,6 +93,7 @@
<intent-filter>
<data android:scheme="mailto"/>
<action android:name="android.intent.action.SENDTO"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
@@ -220,7 +221,7 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".WelcomeActivity"
android:launchMode="singleTask"
android:launchMode="singleTop"
android:theme="@style/TextSecure.LightNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
@@ -391,6 +392,15 @@
android:name=".service.FetchForegroundService"
android:foregroundServiceType="dataSync" />
<service
android:name=".service.AudioPlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
<receiver android:name=".notifications.MarkReadReceiver"
android:enabled="true"
android:exported="false">
File diff suppressed because one or more lines are too long
+38 -52
View File
@@ -1188,6 +1188,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
<li>
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
establishing end-to-end encryption between contacts and all members of a group chat.</p>
</li>
<li>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption and forward secrecy.</p>
</li>
<li>
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
@@ -1372,12 +1376,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
<p>Servers can therefore only see:</p>
<ul>
<li>the sender and receiver addresses</li>
<li>and the message size.</li>
<li>Sender and receiver addresses, randomly generated by default</li>
<li>Message size</li>
</ul>
<p>By default, the addresses are randomly generated.</p>
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
<h3 id="device-seizure">
@@ -1453,7 +1455,7 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
This means that if your private decryption key is leaked,
@@ -1464,12 +1466,9 @@ Otherwise, someone obtaining your decryption keys
is typically also able to get all your non-deleted messages
and doesn’t even need to decrypt any previously collected messages.</p>
<p>We designed a Forward Secrecy approach that withstood
initial examination from some cryptographers and implementation experts
but is pending a more formal write up
to ascertain it reliably works in federated messaging and with multi-device usage,
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will provide reliable deletion (forward secrecy) through automatic key rotation.
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="pqc">
@@ -1479,12 +1478,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
in collaboration with other OpenPGP implementers.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption to protect against quantum computer attacks.
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="how-can-i-manually-check-encryption-information">
@@ -1663,52 +1663,38 @@ ordered chronologically:</p>
<ul>
<li>
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
and implementation of verified groups and setup contact protocols
in 2017 and 2018 and also helped to integrate end-to-end Encryption
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
and released a first Desktop app beta version, and which moreover
moored our feature developments in UX research in human rights contexts,
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
The second 2019/2020 grant (~$300K) helped us to
release Delta/iOS versions, to convert our core library to Rust, and
to provide new features for all platforms.</p>
</li>
<li>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
</li>
<li>
<p>In 2021 we received further EU funding for two Next-Generation-Internet
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
</li>
<li>
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
This funding supported our long-running goals to make Delta Chat more usable
and compatible with a wide range of email servers world-wide, and more resilient and secure
in places often affected by internet censorship and shutdowns.</p>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
</li>
<li>
<p>2023-2024 we successfully completed the OTF-funded
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
allowing us to introduce guaranteed encryption,
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
and providing “instant onboarding” in all apps released from April 2024 on.</p>
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
and released a first Desktop app beta version, and which moreover
moored our feature developments in UX research in human rights contexts,
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
The second 2019/2020 grant (~$300K) helped us to
release Delta/iOS versions, to convert our core library to Rust, and
to provide new features for all platforms.</p>
</li>
<li>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
and implementation of verified groups and setup contact protocols
in 2017 and 2018 and also helped to integrate end-to-end Encryption
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>Sometimes we receive one-time donations from private individuals.
+72 -83
View File
@@ -1045,31 +1045,31 @@ für die <a href="https://chatmail.at/clients">Chatmail-Clients</a> umfasst, von
</h3>
<p>We would like to improve Delta Chat with your help,
which is why Delta Chat for Android asks whether you want
to send anonymous usage statistics.</p>
<p>Wir möchten Delta Chat mit deiner Hilfe verbessern.
Deshalb fragt Delta Chat für Android, ob du
anonyme Nutzungsstatistiken senden möchtest.</p>
<p>You can turn it on and off at
<strong>Settings → Advanced → Send statistics to Delta Chat’s developers</strong>.</p>
<p>Du kannst dies unter
<strong>Einstellungen → Erweitert → Statistik an Delta Chat Entwickler senden</strong> ein- und ausschalten.</p>
<p>When you turn it on,
weekly statistics will be automatically sent to a bot.</p>
<p>Wenn eingeschaltet,
werden wöchentlich Statistiken automatisch an einen Bot gesendet.</p>
<p>We are interested e.g. in statistics like:</p>
<p>Wir sind beispielsweise an folgenden Statistiken interessiert:</p>
<ul>
<li>
<p>How many contacts are introduced by personally scanning a QR code?</p>
<p>Wie viele Kontakte werden durch das persönliche Scannen eines QR-Codes hergestellt?</p>
</li>
<li>
<p>Which versions of Delta Chat are being used?</p>
<p>Welche Versionen von Delta Chat werden verwendet?</p>
</li>
<li>
<p>What errors occur for users?</p>
<p>Welche Fehler treten bei Benutzern auf?</p>
</li>
</ul>
<p>We will <em>not</em> collect any personally identifiable information about you.</p>
<p>Wir werden <em>keinerlei</em> personenbezogene Daten über dich sammeln.</p>
<h3 id="ich-bin-an-technischen-details-interessiert-gibt-es-hierzu-weitere-infos">
@@ -1107,6 +1107,10 @@ zum Austausch von Verschlüsselungsinformationen durch Scannen von QR-Codes oder
<li>
<p><a href="https://autocrypt.org">Autocrypt</a> wird verwendet, um automatisch eine Ende-zu-Ende-Verschlüsselung zwischen Kontakten und allen Mitgliedern einer Gruppe herzustellen.</p>
</li>
<li>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, dessen vollständige Implementierung für 2026 geplant ist,
wir post-quantum-resistente Verschlüsselung und Forward Secrecy einführen.</p>
</li>
<li>
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Teilen eines Kontakts im Chat</a>
ermöglicht es den Empfängern, eine Ende-zu-Ende-Verschlüsselung mit dem Kontakt zu verwenden.</p>
@@ -1280,15 +1284,13 @@ selbst wenn die Nachricht nicht Ende-zu-Ende-verschlüsselt war.</p>
speichern Delta-Chat-Apps keine Metadaten über Kontakte oder Gruppen auf Servern. Auch nicht in verschlüsselter Form.
Stattdessen werden alle Gruppen-Metadaten durchgängig verschlüsselt und ausschließlich auf den Endgeräten der Nutzer gespeichert.</p>
<p>Servers can therefore only see:</p>
<p>Server können daher nur das folgende sehen:</p>
<ul>
<li>the sender and receiver addresses</li>
<li>and the message size.</li>
<li>Absender- und Empfängeradressen, standardmäßig zufällig generiert</li>
<li>Größe der Nachricht</li>
</ul>
<p>By default, the addresses are randomly generated.</p>
<p>Alle anderen Metadaten zu Nachrichten, Kontakten und Gruppen befinden sich im Ende-zu-Ende-verschlüsselten Teil der Nachrichten.</p>
<h3 id="device-seizure">
@@ -1299,15 +1301,15 @@ Stattdessen werden alle Gruppen-Metadaten durchgängig verschlüsselt und aussch
</h3>
<p>Both for protecting against metadata-collecting servers
as well as against the threat of device seizure
we recommend to use a <a href="https://chatmail.at/relays">chatmail relay</a>
to create chat profiles using random addresses for transport.
Note that Delta Chat apps on all platforms support multiple profiles
so you can easily use situation-specific profiles next to your “main” profile
with the knowledge that all their data, along with all metadata, will be deleted.
Moreover, if a device is seized then chat contacts using short-lived profiles
can not be identified easily.</p>
<p>Sowohl zum Schutz vor Servern, die Metadaten sammeln,
als auch als Schutz bei Beschlagnahmung von Geräten
empfehlen wir die Verwendung eines <a href="https://chatmail.at/relays">Chatmail-Relays</a>,
um Chat-Profile mit zufälligen Adressen für den Transport zu erstellen.
Beachte, dass Delta-Chat-Apps mehrere Profile unterstützen,
sodass du neben deinem „Hauptprofil” ganz einfach situationsspezifische Profile verwenden kannst,
mit der Gewissheit, dass alle Daten sowie alle Metadaten gelöscht werden.
Darüber hinaus können Chat-Kontakte, die kurzlebige Profile verwenden,
im Falle einer Beschlagnahmung des Geräts nicht ohne Weiteres identifiziert werden.</p>
<h3 id="wer-sieht-meine-ip-adresse">
@@ -1317,7 +1319,7 @@ can not be identified easily.</p>
</h3>
<p>Das verwendete <a href="#relays">Rekay</a> muss Ihre IP-Adresse kennen,
<p>Das verwendete <a href="#relays">Relay</a> muss Ihre IP-Adresse kennen,
sowie manchmal auch die Geräte Ihrer Kontakte, wenn Sie einen <a href="#experiments">Anruf</a> tätigen
oder gemeinsam <a href="#webxdc">Apps</a> verwenden.</p>
@@ -1350,11 +1352,10 @@ um seine Serverinfrastruktur darüber im Unklaren zu lassen, wer eine Nachricht
Dies ist besonders wichtig, weil der Signal-Server die Handynummer jedes Kontos kennt,
die in der Regel mit einer Passidentität verbunden ist.</p>
<p>Even if <a href="https://chatmail.at/relays">chatmail relays</a>
do not ask for any private data (including no phone numbers),
it might still be worthwhile to protect relational metadata between addresses.
We don’t foresee bigger problems in using random throw-away addresses for sealed sending
but an implementation has not been agreed as a priority yet.</p>
<p>Auch wenn <a href="https://chatmail.at/relays">Chatmail-Relays</a>
keine privaten Daten (einschließlich Telefonnummern) abfragen,
könnte es dennoch sinnvoll sein, Metadaten zwischen Adressen zu schützen.
Wir sehen keine größeren Probleme bei der Verwendung von zufälligen Wegwerfadressen für aber eine Umsetzung wurde noch nicht als priorisiert.</p>
<h3 id="pfs">
@@ -1364,23 +1365,20 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>Nein, noch nicht.</p>
<p>Noch nicht, aber es kommt mit <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
This means that if your private decryption key is leaked,
and someone has collected your prior in-transit messages,
they will be able to decrypt and read them using the leaked decryption key.
Note that Forward Secrecy only increases security if you delete messages.
Otherwise, someone obtaining your decryption keys
is typically also able to get all your non-deleted messages
and doesn’t even need to decrypt any previously collected messages.</p>
<p>Delta Chat unterstützt derzeit keine Perfect Forward Secrecy (PFS).
Das bedeutet, dass, wenn Ihr privater Schlüssel offengelegt wird
und jemand Ihre früheren Nachrichten während der Übertragung gesammelt hat,
diese mit dem offengelegten Schlüssel entschlüsselt und gelesen werden können.
Beachten Sie, dass Forward Secrecy die Sicherheit nur erhöht, wenn du Nachrichten löschst.
Andernfalls kann jemand, der deinen Schlüssel erhält,
in der Regel auch alle deine nicht gelöschten Nachrichten abrufen
und muss zuvor gesammelte Nachrichten nicht einmal entschlüsseln.</p>
<p>We designed a Forward Secrecy approach that withstood
initial examination from some cryptographers and implementation experts
but is pending a more formal write up
to ascertain it reliably works in federated messaging and with multi-device usage,
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, dessen vollständige Implementierung für 2026 geplant ist,
wird durch automatische Schlüsselrotation eine zuverlässige Löschung (Forward Secrecy) gewährleisten.
Dieser Ansatz ist im Entwurf <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> festgelegt.</p>
<h3 id="pqc">
@@ -1390,11 +1388,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
</h3>
<p>Nein, noch nicht.</p>
<p>Noch nicht, aber es kommt mit <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat verwendet die Rust OpenPGP-Bibliothek <a href="https://github.com/rpgp/rpgp">rPGP</a>
die den neuesten <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP Entwurf</a> unterstützt.
Wir beabsichtigen, PQC-Unterstützung zum <a href="https://github.com/chatmail/core">chatmail core</a> hinzuzufügen, sobald der Entwurf bei der IETF in Zusammenarbeit mit anderen OpenPGP-Implementierern fertiggestellt ist.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, dessen vollständige Implementierung für 2026 geplant ist,
wird eine post-quantum-resistente Verschlüsselung zum Schutz vor Angriffen durch Quantencomputer bieten.
Delta Chat verwendet die Rust-OpenPGP-Bibliothek <a href="https://github.com/rpgp/rpgp">rPGP</a>,
die den neuesten <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Kryptografie OpenPGP Entfurf</a> unterstützt.
Die Implementierung ist in <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> festgelegt.</p>
<h3 id="wie-kann-ich-die-verschlüsselung-manuell-überprüfen">
@@ -1420,12 +1420,11 @@ ist die Verbindung sicher.</p>
<p>Nein.</p>
<p>Delta Chat generates secure OpenPGP keys according to the Autocrypt specification 1.1.
We do not recommend or offer users to perform manual key management.
We want to ensure that security audits can focus on a few proven cryptographic algorithms
instead of the full breadth of possible algorithms allowed with OpenPGP.
If you want to extract your OpenPGP key, there only is an expert method:
you need to look it up in the “keypairs” SQLite table of a profile backup tar-file.</p>
<p>Delta Chat generiert sichere OpenPGP-Schlüssel gemäß der Autocrypt-Spezifikation 1.1.
Wir bieten Benutzern keine manuelle Schlüsselverwaltung an, noch empfehlen diese.
Wir wollen sicherstellen, dass sich Sicherheitsüberprüfungen auf einige wenige bewährte kryptografische Algorithmen konzentrieren können,
anstatt auf die gesamte Bandbreite der mit OpenPGP zulässigen Algorithmen.
Wenn Sie Ihren OpenPGP-Schlüssel extrahieren möchten, gibt es nur eine Methode für Experten: Sie müssen ihn in der SQLite-Tabelle „keypairs” des Backups nachschlagen.</p>
<h3 id="security-audits">
@@ -1487,10 +1486,10 @@ Es wurden keine kritischen Probleme gefunden, aber zwei Probleme mit hohem Schwe
</h3>
<p>Some features require certain permissions,
e.g. you need to grant camera permission if you want to <a href="#howtoe2ee">scan an invite QR code</a>.</p>
<p>Einige Features erfordern bestimmte Berechtigungen.
So muss z.B. der Kamerazugriff gewährt werden, wenn du einen <a href="#howtoe2ee">QR-Code scannen</a> möchtest.</p>
<p>See <a href="https://delta.chat/en/gdpr#24-app-permissions">Privacy Policy</a> for a detailed overview.</p>
<p>Siehe <a href="https://delta.chat/de/gdpr#24-berechtigungen-der-app">Datenschutzhinweise</a> für eine detaillierte Übersicht.</p>
<h3 id="wo-können-meine-freunde-delta-chat-finden">
@@ -1534,29 +1533,6 @@ Wir nutzen vielmehr öffentliche Finanzierungsquellen, die bisher aus der EU und
<p>Konkret wurden die Delta-Chat-Entwicklungen bisher aus diesen Quellen finanziert:</p>
<ul>
<li>
<p>Das EU-Projekt <a href="https://nextleap.eu">NEXTLEAP</a> finanzierte 2017 und 2018 die Entwicklung und Implementierung von “Verifizierten Gruppen” und “Setup Kontakt” und half auch bei der Integration der Ende-zu-Ende-Verschlüsselung durch <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>Der <a href="https://opentechfund.org">Open Technology Fund</a> hat Delta Chat erstmals 2018/2019 bezuschusst; mit dieser Förderung (~$200K) wurden hauptsächlich die Android-App verbessert sowie das Release der Desktop-App in einer Betaversion ermöglicht. Basierend auf Nutzererfahrungen im Menschenrechtskontext wurden zudem verschiedene Funktionen entwickelt, siehe unseren Bericht <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
Die zweite Förderung 2019/2020 (~$300K) half uns bei der Erstellung der iOS-Version, unsere Kernbibliothek in die Programmiersprache “Rust” zu konvertieren und neue Funktionen für alle Plattformen bereitzustellen.</p>
</li>
<li>
<p>Die <a href="https://nlnet.nl/">NLnet-Stiftung</a> bewilligte 2019/2020 46K EUR für die Fertigstellung von Rust-/Python-Bindungs und die Einrichtung eines Chat-Bot-Ökosystems.</p>
</li>
<li>
<p>Im Jahr 2021 erhielten wir weitere EU-Mittel für zwei “Next-Generation-Internet”-Anträge, nämlich für <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - E-Mail-Provider-Portabilitätsverzeichnis</a> (~97K EUR) und <a href="https://nlnet.nl/project/EmailPorting/">AEAP - E-Mail-Adressportierung</a> (~90K EUR). Ziel sind bessere Unterstützung von Mehrfachkonten, verbesserten QR-Code-Kontakt- und -Gruppen-Setups sowie Netzwerkverbesserungen auf allen Plattformen.</p>
</li>
<li>
<p>Von Ende 2021 bis März 2023 erhielten wir eine <em>Internet-Freedom</em>-Finanzierung (500K USD) vom U.S. Bureau of Democracy, Human Rights and Labor (DRL). Diese Finanzierung unterstützte unsere langjährigen Ziele, Delta Chat benutzerfreundlicher und kompatibel mit einer breiten Palette von E-Mail-Servern weltweit zu machen, sowie widerstandsfähiger und sicherer an Orten, die häufig von Internetzensur und Abschaltungen betroffen sind.</p>
</li>
<li>
<p>2023-2024 schlossen wir erfolgreich das vom OTF finanzierte
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure-Chatmail-Projekt</a> ab.
Dieses fügt “Garantierte Verschlüsselung”,
das <a href="https://delta.chat/chatmail">Chatmail-Server-Netzwerk</a>
und „Instant Onboarding“ allen ab April 2024 veröffentlichten Anwendungen hinzu.</p>
</li>
<li>
<p>2023 und 2024 wurden wir in das Next-Generation-Internet-Programm (NGI)
für unsere Arbeit an <a href="https://nlnet.nl/project/WebXDC-Push/">Webxdc-PUSH</a> aufgenommen,
@@ -1567,6 +1543,19 @@ zusammen mit Kooperationspartnern, die an
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
Alle diese Projekte sind teilweise abgeschlossen oder sollen Anfang 2025 abgeschlossen werden.</p>
</li>
<li>
<p>Im Jahr 2021 erhielten wir weitere EU-Mittel für zwei “Next-Generation-Internet”-Anträge, nämlich für <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - E-Mail-Provider-Portabilitätsverzeichnis</a> (~97K EUR) und <a href="https://nlnet.nl/project/EmailPorting/">AEAP - E-Mail-Adressportierung</a> (~90K EUR). Ziel sind bessere Unterstützung von Mehrfachkonten, verbesserten QR-Code-Kontakt- und -Gruppen-Setups sowie Netzwerkverbesserungen auf allen Plattformen.</p>
</li>
<li>
<p>Die <a href="https://nlnet.nl/">NLnet-Stiftung</a> bewilligte 2019/2020 46K EUR für die Fertigstellung von Rust-/Python-Bindungs und die Einrichtung eines Chat-Bot-Ökosystems.</p>
</li>
<li>
<p>Der <a href="https://opentechfund.org">Open Technology Fund</a> hat Delta Chat erstmals 2018/2019 bezuschusst; mit dieser Förderung (~$200K) wurden hauptsächlich die Android-App verbessert sowie das Release der Desktop-App in einer Betaversion ermöglicht. Basierend auf Nutzererfahrungen im Menschenrechtskontext wurden zudem verschiedene Funktionen entwickelt, siehe unseren Bericht <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
Die zweite Förderung 2019/2020 (~$300K) half uns bei der Erstellung der iOS-Version, unsere Kernbibliothek in die Programmiersprache “Rust” zu konvertieren und neue Funktionen für alle Plattformen bereitzustellen.</p>
</li>
<li>
<p>Das EU-Projekt <a href="https://nextleap.eu">NEXTLEAP</a> finanzierte 2017 und 2018 die Entwicklung und Implementierung von “Verifizierten Gruppen” und “Setup Kontakt” und half auch bei der Integration der Ende-zu-Ende-Verschlüsselung durch <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>Manchmal erhalten wir einmalige Spenden von Privatpersonen, wofür wir sehr dankbar sind. Im Jahr 2021 hat uns zum Beispiel eine großzügige Privatperson 4000 EUR überwiesen mit dem Betreff “Weiter so!” 💜 Wir verwenden dieses Geld zur Finanzierung von Entwicklungstreffen oder zur Deckung von Ad-hoc-Ausgaben, die nicht ohne weiteres vorhersehbar sind oder nicht aus öffentlichen Fördermitteln erstattet werden können.
Der Erhalt von Spenden hilft uns auch, unabhängiger und langfristig lebensfähig zu werden, als Gemeinschaft.</p>
+38 -52
View File
@@ -1188,6 +1188,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
<li>
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
establishing end-to-end encryption between contacts and all members of a group chat.</p>
</li>
<li>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption and forward secrecy.</p>
</li>
<li>
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
@@ -1372,12 +1376,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
<p>Servers can therefore only see:</p>
<ul>
<li>the sender and receiver addresses</li>
<li>and the message size.</li>
<li>Sender and receiver addresses, randomly generated by default</li>
<li>Message size</li>
</ul>
<p>By default, the addresses are randomly generated.</p>
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
<h3 id="device-seizure">
@@ -1453,7 +1455,7 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
This means that if your private decryption key is leaked,
@@ -1464,12 +1466,9 @@ Otherwise, someone obtaining your decryption keys
is typically also able to get all your non-deleted messages
and doesn’t even need to decrypt any previously collected messages.</p>
<p>We designed a Forward Secrecy approach that withstood
initial examination from some cryptographers and implementation experts
but is pending a more formal write up
to ascertain it reliably works in federated messaging and with multi-device usage,
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will provide reliable deletion (forward secrecy) through automatic key rotation.
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="pqc">
@@ -1479,12 +1478,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
in collaboration with other OpenPGP implementers.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption to protect against quantum computer attacks.
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="how-can-i-manually-check-encryption-information">
@@ -1663,52 +1663,38 @@ ordered chronologically:</p>
<ul>
<li>
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
and implementation of verified groups and setup contact protocols
in 2017 and 2018 and also helped to integrate end-to-end Encryption
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
and released a first Desktop app beta version, and which moreover
moored our feature developments in UX research in human rights contexts,
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
The second 2019/2020 grant (~$300K) helped us to
release Delta/iOS versions, to convert our core library to Rust, and
to provide new features for all platforms.</p>
</li>
<li>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
</li>
<li>
<p>In 2021 we received further EU funding for two Next-Generation-Internet
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
</li>
<li>
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
This funding supported our long-running goals to make Delta Chat more usable
and compatible with a wide range of email servers world-wide, and more resilient and secure
in places often affected by internet censorship and shutdowns.</p>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
</li>
<li>
<p>2023-2024 we successfully completed the OTF-funded
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
allowing us to introduce guaranteed encryption,
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
and providing “instant onboarding” in all apps released from April 2024 on.</p>
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
and released a first Desktop app beta version, and which moreover
moored our feature developments in UX research in human rights contexts,
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
The second 2019/2020 grant (~$300K) helped us to
release Delta/iOS versions, to convert our core library to Rust, and
to provide new features for all platforms.</p>
</li>
<li>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
and implementation of verified groups and setup contact protocols
in 2017 and 2018 and also helped to integrate end-to-end Encryption
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>Sometimes we receive one-time donations from private individuals.
+38 -52
View File
@@ -1184,6 +1184,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
<li>
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
establishing end-to-end encryption between contacts and all members of a group chat.</p>
</li>
<li>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption and forward secrecy.</p>
</li>
<li>
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
@@ -1365,12 +1369,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
<p>Servers can therefore only see:</p>
<ul>
<li>the sender and receiver addresses</li>
<li>and the message size.</li>
<li>Sender and receiver addresses, randomly generated by default</li>
<li>Message size</li>
</ul>
<p>By default, the addresses are randomly generated.</p>
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
<h3 id="device-seizure">
@@ -1446,7 +1448,7 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
This means that if your private decryption key is leaked,
@@ -1457,12 +1459,9 @@ Otherwise, someone obtaining your decryption keys
is typically also able to get all your non-deleted messages
and doesn’t even need to decrypt any previously collected messages.</p>
<p>We designed a Forward Secrecy approach that withstood
initial examination from some cryptographers and implementation experts
but is pending a more formal write up
to ascertain it reliably works in federated messaging and with multi-device usage,
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will provide reliable deletion (forward secrecy) through automatic key rotation.
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="pqc">
@@ -1472,12 +1471,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
in collaboration with other OpenPGP implementers.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption to protect against quantum computer attacks.
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="how-can-i-manually-check-encryption-information">
@@ -1652,52 +1652,38 @@ ordered chronologically:</p>
<ul>
<li>
<p>El proyecto de la UE <a href="https://nextleap.eu">NEXTLEAP</a> financió la investigación
e implementación de grupos verificados y protocolos de contacto
en 2017 y 2018 y también ayudó a integrar el cifrado de extremo a extremo
a través de <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
and released a first Desktop app beta version, and which moreover
moored our feature developments in UX research in human rights contexts,
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
The second 2019/2020 grant (~$300K) helped us to
release Delta/iOS versions, to convert our core library to Rust, and
to provide new features for all platforms.</p>
</li>
<li>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
</li>
<li>
<p>In 2021 we received further EU funding for two Next-Generation-Internet
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
</li>
<li>
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
This funding supported our long-running goals to make Delta Chat more usable
and compatible with a wide range of email servers world-wide, and more resilient and secure
in places often affected by internet censorship and shutdowns.</p>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
</li>
<li>
<p>2023-2024 we successfully completed the OTF-funded
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
allowing us to introduce guaranteed encryption,
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
and providing “instant onboarding” in all apps released from April 2024 on.</p>
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
and released a first Desktop app beta version, and which moreover
moored our feature developments in UX research in human rights contexts,
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
The second 2019/2020 grant (~$300K) helped us to
release Delta/iOS versions, to convert our core library to Rust, and
to provide new features for all platforms.</p>
</li>
<li>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
<p>El proyecto de la UE <a href="https://nextleap.eu">NEXTLEAP</a> financió la investigación
e implementación de grupos verificados y protocolos de contacto
en 2017 y 2018 y también ayudó a integrar el cifrado de extremo a extremo
a través de <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>Sometimes we receive one-time donations from private individuals.
+35 -48
View File
@@ -1175,6 +1175,9 @@ to exchange encryption setup information through QR-code scanning or “invite l
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
establishing end-to-end encryption between contacts and all members of a group chat.</p>
</li>
<li>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, qui est prévu pour 2026, amènera un chiffrement avec résistance post-quantique ainsi que la confidentialité persistante (“forward secrecy”).</p>
</li>
<li>
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
chat</a>
@@ -1358,12 +1361,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
<p>Servers can therefore only see:</p>
<ul>
<li>the sender and receiver addresses</li>
<li>and the message size.</li>
<li>Sender and receiver addresses, randomly generated by default</li>
<li>Message size</li>
</ul>
<p>By default, the addresses are randomly generated.</p>
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
<h3 id="device-seizure">
@@ -1418,7 +1419,7 @@ For example, tapping a link exposes IP Addresses to unknown parties and is the b
</h3>
<p>No, not yet.</p>
<p>Non, pas encore.</p>
<p>The Signal messenger introduced <a href="https://signal.org/blog/sealed-sender/">“Sealed Sender” in 2018</a>
to keep their server infrastructure ignorant of who is sending a message to a set of recipients.
@@ -1439,7 +1440,7 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>No, not yet.</p>
<p>Pas encore mais cela arrive avec <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
This means that if your private decryption key is leaked,
@@ -1450,12 +1451,8 @@ Otherwise, someone obtaining your decryption keys
is typically also able to get all your non-deleted messages
and doesn’t even need to decrypt any previously collected messages.</p>
<p>We designed a Forward Secrecy approach that withstood
initial examination from some cryptographers and implementation experts
but is pending a more formal write up
to ascertain it reliably works in federated messaging and with multi-device usage,
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, prévu pour 2026, permettra la suppression fiable (forward secrecy) grâce à une rotation automatique des clefs.
Cette approche est détaillée dans le brouillon du <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">certificat Autocrypt v2 OpenPGP</a>.</p>
<h3 id="pqc">
@@ -1465,12 +1462,11 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
</h3>
<p>No, not yet.</p>
<p>Pas encore mais cela arrive avec <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
in collaboration with other OpenPGP implementers.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, prévu pour 2026, amènera un chiffrement avec résistance post-quantique pour protéger contre les attaques effectuées par des ordinateurs quantiques.
Delta Chat utilise la librairie Rust OpenPGP <a href="https://github.com/rpgp/rpgp">rPGP</a> qui supporte les dernières <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
L’implémentation est détaillée dans le brouillon du <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">certificat Autocrypt v2 OpenPGP</a>.</p>
<h3 id="how-can-i-manually-check-encryption-information">
@@ -1628,47 +1624,38 @@ ordered chronologically:</p>
<ul>
<li>
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
and implementation of verified groups and setup contact protocols
in 2017 and 2018 and also helped to integrate end-to-end Encryption
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>L’association <a href="https://opentechfund.org">Open Technology Fund</a> nous a octroyé une première subvention en 2018/2019 (de 200.000$ environ), grâce à laquelle nous avons pu apporter des améliorations majeures à l’application Android et publier une première version Beta de l’application de bureau. Elle nous a aussi permis d’ancrer notre recherche UX de développement de fonctionnalités dans des contextes de droits humains. <br />
À ce sujet, vous pouvez consulter notre rapport en anglais <a href="https://delta.chat/en/2019-07-19-uxreport">“Needfinding and UX report”</a>.
La seconde subvention de 2019/2020 (environ 300.000$) nous a permis de publier des version iOS de Delta Chat, de convertir notre bibliothèque principale en Rust et de créer de nouvelles fonctionnalités pour toutes les plateformes.</p>
</li>
<li>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
</li>
<li>
<p>In 2021 we received further EU funding for two Next-Generation-Internet
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
</li>
<li>
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
This funding supported our long-running goals to make Delta Chat more usable
and compatible with a wide range of email servers world-wide, and more resilient and secure
in places often affected by internet censorship and shutdowns.</p>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
</li>
<li>
<p>2023-2024 we successfully completed the OTF-funded
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
allowing us to introduce guaranteed encryption,
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
and providing “instant onboarding” in all apps released from April 2024 on.</p>
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
and released a first Desktop app beta version, and which moreover
moored our feature developments in UX research in human rights contexts,
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
The second 2019/2020 grant (~$300K) helped us to
release Delta/iOS versions, to convert our core library to Rust, and
to provide new features for all platforms.</p>
</li>
<li>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
and implementation of verified groups and setup contact protocols
in 2017 and 2018 and also helped to integrate end-to-end Encryption
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>Nous recevons parfois des dons ponctuels de la part de personnes privées.
+4
View File
@@ -6,6 +6,10 @@ a {
text-decoration: none;
}
a[href^='http']::after {
content: " ↗";
}
h2, h3, h4 {
margin-top: 2rem;
}
+38 -52
View File
@@ -1188,6 +1188,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
<li>
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
establishing end-to-end encryption between contacts and all members of a group chat.</p>
</li>
<li>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption and forward secrecy.</p>
</li>
<li>
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
@@ -1372,12 +1376,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
<p>Servers can therefore only see:</p>
<ul>
<li>the sender and receiver addresses</li>
<li>and the message size.</li>
<li>Sender and receiver addresses, randomly generated by default</li>
<li>Message size</li>
</ul>
<p>By default, the addresses are randomly generated.</p>
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
<h3 id="device-seizure">
@@ -1453,7 +1455,7 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
This means that if your private decryption key is leaked,
@@ -1464,12 +1466,9 @@ Otherwise, someone obtaining your decryption keys
is typically also able to get all your non-deleted messages
and doesn’t even need to decrypt any previously collected messages.</p>
<p>We designed a Forward Secrecy approach that withstood
initial examination from some cryptographers and implementation experts
but is pending a more formal write up
to ascertain it reliably works in federated messaging and with multi-device usage,
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will provide reliable deletion (forward secrecy) through automatic key rotation.
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="pqc">
@@ -1479,12 +1478,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
in collaboration with other OpenPGP implementers.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption to protect against quantum computer attacks.
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="how-can-i-manually-check-encryption-information">
@@ -1663,52 +1663,38 @@ ordered chronologically:</p>
<ul>
<li>
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
and implementation of verified groups and setup contact protocols
in 2017 and 2018 and also helped to integrate end-to-end Encryption
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
and released a first Desktop app beta version, and which moreover
moored our feature developments in UX research in human rights contexts,
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
The second 2019/2020 grant (~$300K) helped us to
release Delta/iOS versions, to convert our core library to Rust, and
to provide new features for all platforms.</p>
</li>
<li>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
</li>
<li>
<p>In 2021 we received further EU funding for two Next-Generation-Internet
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
</li>
<li>
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
This funding supported our long-running goals to make Delta Chat more usable
and compatible with a wide range of email servers world-wide, and more resilient and secure
in places often affected by internet censorship and shutdowns.</p>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
</li>
<li>
<p>2023-2024 we successfully completed the OTF-funded
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
allowing us to introduce guaranteed encryption,
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
and providing “instant onboarding” in all apps released from April 2024 on.</p>
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
and released a first Desktop app beta version, and which moreover
moored our feature developments in UX research in human rights contexts,
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
The second 2019/2020 grant (~$300K) helped us to
release Delta/iOS versions, to convert our core library to Rust, and
to provide new features for all platforms.</p>
</li>
<li>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
and implementation of verified groups and setup contact protocols
in 2017 and 2018 and also helped to integrate end-to-end Encryption
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>Terkadang kami menerima sumbangan satu kali dari perorangan.
+32 -42
View File
@@ -1173,6 +1173,10 @@ per scambiare informazioni sulla configurazione della crittografia tramite la sc
<li>
<p><a href="https://autocrypt.org">Autocrypt</a> viene utilizzato per stabilire
automaticamente la crittografia end-to-end tra i contatti e tutti i membri di una chat di gruppo.</p>
</li>
<li>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, la cui piena implementazione è prevista per il 2026,
introdurrà una crittografia post-quantistica resistente e una segretezza avanzata.</p>
</li>
<li>
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Condivisione di un contatto con una
@@ -1436,7 +1440,7 @@ ma un’implementazione non è stata ancora concordata come priorità.</p>
</h3>
<p>No, non ancora.</p>
<p>Non ancora, ma arriverà con <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat al momento non supporta la tecnologia Perfect Forward Secrecy (PFS).
Ciò significa che se la tua chiave di decrittazione privata viene divulgata
@@ -1447,12 +1451,9 @@ In caso contrario, chi ottiene le tue chiavi di decrittazione
in genere è in grado di ottenere anche tutti i tuoi messaggi non eliminati
e non ha nemmeno bisogno di decifrare i messaggi raccolti in precedenza.</p>
<p>Abbiamo progettato un approccio Forward Secrecy che ha superato
l’esame iniziale di alcuni crittografi ed esperti di implementazione
ma è in attesa di una stesura più formale
per accertarne l’affidabilità nella messaggistica federata e nell’utilizzo su più dispositivi,
prima di poter essere implementato in <a href="https://github.com/chatmail/core">chatmail core</a>,
che lo renderebbe disponibile in tutti i <a href="https://chatmail.at/clients">clients di chatmail</a>.</p>
<p><a href="https://autocrypt2.org">autocrypt v2</a>, la cui piena implementazione è prevista per il 2026,
garantirà un’eliminazione affidabile (segretezza in avanti) tramite rotazione automatica delle chiavi.
Questo approccio è specificato nella bozza dei <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">certificati OpenPGP di Autocrypt v2</a>.</p>
<h3 id="pqc">
@@ -1462,12 +1463,13 @@ che lo renderebbe disponibile in tutti i <a href="https://chatmail.at/clients">c
</h3>
<p>No, non ancora.</p>
<p>Non ancora, ma arriverà con <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat utilizza la libreria Rust OpenPGP <a href="https://github.com/rpgp/rpgp">rPGP</a>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, la cui piena implementazione è prevista per il 2026,
offrirà una crittografia post-quantistica resistente per proteggere dagli attacchi ai computer quantistici.
Delta Chat utilizza la libreria Rust OpenPGP <a href="https://github.com/rpgp/rpgp">rPGP</a>
che supporta l’ultima <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">bozza IETF Post-Quantum-Cryptography OpenPGP</a>.
Il nostro obiettivo è aggiungere il supporto PQC nel <a href="https://github.com/chatmail/core">core di chatmail</a> dopo che la bozza sarà stata finalizzata dall’IETF
in collaborazione con altri implementatori di OpenPGP.</p>
L’implementazione è specificata nella bozza dei <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Certificati OpenPGP Autocrypt v2</a>.</p>
<h3 id="come-posso-controllare-manualmente-le-informazioni-di-crittografia">
@@ -1647,10 +1649,21 @@ ordinate cronologicamente:</p>
<ul>
<li>
<p>Il progetto UE <a href="https://nextleap.eu">NEXTLEAP</a> ha finanziato la ricerca
e implementazione di gruppi verificati e impostazione di protocolli di contatto
nel 2017 e nel 2018 e ha anche contribuito a integrare la crittografia end-to-end
tramite <a href="https://autocrypt.org">Autocrypt</a>.</p>
<p>Nel 2023 e nel 2024 siamo stati accettati nel programma Next Generation Internet (NGI)
per il nostro lavoro in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
insieme ai partner di collaborazione che lavorano su
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> e
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
Tutti questi progetti sono parzialmente completati o saranno completati all’inizio del 2025.</p>
</li>
<li>
<p>Nel 2021 abbiamo ricevuto ulteriori finanziamenti dall’UE per due proposte di Next-Generation-Internet, ovvero per <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - directory di portabilità dei provider di posta elettronica</a> (~97.000 EUR) e <a href="https://nlnet.nl/project/EmailPorting/">AEAP - portabilità degli indirizzi email</a> (~90.000 EUR), che hanno portato a un migliore supporto multi-profilo, a un miglioramento delle impostazioni di contatto e di gruppo tramite codice QR e a numerosi miglioramenti di rete su tutte le piattaforme.</p>
</li>
<li>
<p>La <a href="https://nlnet.nl/">fondazione NLnet</a> ha concesso nel 2019/2020 46.000 EUR per
completando i collegamenti Rust/Python e avviando un ecosistema Chat-bot.</p>
</li>
<li>
<p>L’<a href="https://opentechfund.org">Open Technology Fund</a> ci ha dato una
@@ -1663,33 +1676,10 @@ rilasciare nelle versioni Delta/iOS, per convertire la nostra libreria principal
per fornire nuove funzionalità per tutte le piattaforme.</p>
</li>
<li>
<p>La <a href="https://nlnet.nl/">fondazione NLnet</a> ha concesso nel 2019/2020 46.000 EUR per
completando i collegamenti Rust/Python e avviando un ecosistema Chat-bot.</p>
</li>
<li>
<p>Nel 2021 abbiamo ricevuto ulteriori finanziamenti dall’UE per due proposte di Next-Generation-Internet, ovvero per <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - directory di portabilità dei provider di posta elettronica</a> (~97.000 EUR) e <a href="https://nlnet.nl/project/EmailPorting/">AEAP - portabilità degli indirizzi email</a> (~90.000 EUR), che hanno portato a un migliore supporto multi-profilo, a un miglioramento delle impostazioni di contatto e di gruppo tramite codice QR e a numerosi miglioramenti di rete su tutte le piattaforme.</p>
</li>
<li>
<p>Da fine 2021 a marzo 2023 abbiamo ricevuto un finanziamento <em>Internet Freedom</em> (500.000 USD) dall’Ufficio per la Democrazia, i Diritti Umani e il Lavoro (DRL) degli Stati Uniti.
Questo finanziamento ha supportato i nostri obiettivi a lungo termine: rendere Delta Chat più utilizzabile
e compatibile con un’ampia gamma di server email in tutto il mondo, e più resiliente e sicura
in luoghi spesso colpiti da censura e blocchi di Internet.</p>
</li>
<li>
<p>2023-2024 abbiamo completato con successo il progetto <a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Chatmail Sicuro</a> finanziato da OTF,
consentendoci di introdurre la crittografia garantita,
creando una <a href="https://delta.chat/chatmail">rete di server di chatmail</a>
e fornendo “inserimento immediato” in tutte le app rilasciate da aprile 2024 in poi.</p>
</li>
<li>
<p>Nel 2023 e nel 2024 siamo stati accettati nel programma Next Generation Internet (NGI)
per il nostro lavoro in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
insieme ai partner di collaborazione che lavorano su
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> e
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
Tutti questi progetti sono parzialmente completati o saranno completati all’inizio del 2025.</p>
<p>Il progetto UE <a href="https://nextleap.eu">NEXTLEAP</a> ha finanziato la ricerca
e implementazione di gruppi verificati e impostazione di protocolli di contatto
nel 2017 e nel 2018 e ha anche contribuito a integrare la crittografia end-to-end
tramite <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>A volte riceviamo donazioni una tantum da privati.
+38 -52
View File
@@ -1182,6 +1182,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
<li>
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
establishing end-to-end encryption between contacts and all members of a group chat.</p>
</li>
<li>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption and forward secrecy.</p>
</li>
<li>
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
@@ -1366,12 +1370,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
<p>Servers can therefore only see:</p>
<ul>
<li>the sender and receiver addresses</li>
<li>and the message size.</li>
<li>Sender and receiver addresses, randomly generated by default</li>
<li>Message size</li>
</ul>
<p>By default, the addresses are randomly generated.</p>
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
<h3 id="device-seizure">
@@ -1447,7 +1449,7 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
This means that if your private decryption key is leaked,
@@ -1458,12 +1460,9 @@ Otherwise, someone obtaining your decryption keys
is typically also able to get all your non-deleted messages
and doesn’t even need to decrypt any previously collected messages.</p>
<p>We designed a Forward Secrecy approach that withstood
initial examination from some cryptographers and implementation experts
but is pending a more formal write up
to ascertain it reliably works in federated messaging and with multi-device usage,
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will provide reliable deletion (forward secrecy) through automatic key rotation.
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="pqc">
@@ -1473,12 +1472,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
in collaboration with other OpenPGP implementers.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption to protect against quantum computer attacks.
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="how-can-i-manually-check-encryption-information">
@@ -1657,52 +1657,38 @@ ordered chronologically:</p>
<ul>
<li>
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
and implementation of verified groups and setup contact protocols
in 2017 and 2018 and also helped to integrate end-to-end Encryption
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p><a href="https://opentechfund.org">Open Technology Fund</a> heeft twee subsidies toegekend.
De eerste subsidie, voor 2018/2019, ter waarde van ong. $200,000, heeft enorm geholpen om de Android-app
te verbeteren en een bètaversie van de computerclient vrij te geven.
Verder hebben we onderzoek kunnen doen naar het uiterlijk in relatie tot mensenrechten -
bekijk onze conclusie hier: <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
De tweede subsidie, voor 2019/2020, ter waarde van ong. $300,000, loopt nog en ondersteunt ons bij het
vrijgeven van de iOS-client, het overzetten van de code van de kernbibliotheek naar Rust en
het implementeren van nieuwe functies op alle platformen.</p>
</li>
<li>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
</li>
<li>
<p>In 2021 we received further EU funding for two Next-Generation-Internet
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
</li>
<li>
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
This funding supported our long-running goals to make Delta Chat more usable
and compatible with a wide range of email servers world-wide, and more resilient and secure
in places often affected by internet censorship and shutdowns.</p>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
</li>
<li>
<p>2023-2024 we successfully completed the OTF-funded
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
allowing us to introduce guaranteed encryption,
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
and providing “instant onboarding” in all apps released from April 2024 on.</p>
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
and released a first Desktop app beta version, and which moreover
moored our feature developments in UX research in human rights contexts,
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
The second 2019/2020 grant (~$300K) helped us to
release Delta/iOS versions, to convert our core library to Rust, and
to provide new features for all platforms.</p>
</li>
<li>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
and implementation of verified groups and setup contact protocols
in 2017 and 2018 and also helped to integrate end-to-end Encryption
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>Soms ontvangen we eenmalige donaties van privépersonen, waar we
+25 -29
View File
@@ -105,8 +105,7 @@
</h2>
<p>Delta Chat is a reliable, decentralized and secure instant messaging app,
available for mobile and desktop platforms.</p>
<p>Delta Chat to niezawodna, zdecentralizowana i bezpieczna aplikacja do błyskawicznego przesyłania wiadomości, dostępna na platformy mobilne i stacjonarne.</p>
<ul>
<li>
@@ -1103,6 +1102,10 @@ weekly statistics will be automatically sent to a bot.</p>
<li>
<p><a href="https://autocrypt.org">Autocrypt</a> służy do automatycznego ustanawiania szyfrowania typu end-to-end między kontaktami a wszystkimi członkami czatu grupowego.</p>
</li>
<li>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption and forward secrecy.</p>
</li>
<li>
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Udostępnienie kontaktu na czacie</a> umożliwia odbiorcom korzystanie z szyfrowania typu end-to-end z tym kontaktem.</p>
</li>
@@ -1237,12 +1240,10 @@ even if the message was not end-to-end encrypted.</p>
<p>Servers can therefore only see:</p>
<ul>
<li>the sender and receiver addresses</li>
<li>and the message size.</li>
<li>Sender and receiver addresses, randomly generated by default</li>
<li>Message size</li>
</ul>
<p>By default, the addresses are randomly generated.</p>
<p>Wszystkie pozostałe metadane dotyczące wiadomości, kontaktów i grup znajdują się w zaszyfrowanej metodą end-to-end części wiadomości.</p>
<h3 id="device-seizure">
@@ -1315,11 +1316,13 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>Nie, jeszcze nie.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat obecnie nie obsługuje mechanizmu Perfect Forward Secrecy (PFS). Oznacza to, że jeśli twój prywatny klucz deszyfrujący zostanie ujawniony, a ktoś zdobędzie twoje wcześniejsze wiadomości w trakcie transmisji, będzie mógł je odszyfrować i odczytać za pomocą ujawnionego klucza deszyfrującego. Należy pamiętać, że mechanizm Forward Secrecy zwiększa bezpieczeństwo tylko w przypadku usuwania wiadomości. W przeciwnym razie osoba, która uzyska twoje klucze deszyfrujące, zazwyczaj będzie mogła uzyskać dostęp do wszystkich nieusuniętych wiadomości i nie będzie musiała odszyfrowywać żadnych wcześniej zebranych wiadomości.</p>
<p>Opracowaliśmy metodę Forward Secrecy, która przeszła wstępną analizę niektórych kryptografów i ekspertów ds. wdrożeń, ale oczekuje na bardziej formalne opracowanie, które potwierdzi jej niezawodne działanie w federacyjnym przesyłaniu wiadomości i w przypadku korzystania z wielu urządzeń, zanim zostanie zaimplementowana w <a href="https://github.com/chatmail/core">rdzeniu chatmail</a>, co uczyniłoby ją dostępną we wszystkich <a href="https://chatmail.at/clients">klientach chatmail</a>.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will provide reliable deletion (forward secrecy) through automatic key rotation.
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="pqc">
@@ -1329,9 +1332,13 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>Nie, jeszcze nie.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat korzysta z biblioteki Rust OpenPGP <a href="https://github.com/rpgp/rpgp">rPGP</a>, która obsługuje najnowszy <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">projekt OpenPGP IETF Post-Quantum-Cryptography</a>. Planujemy dodać obsługę PQC do <a href="https://github.com/chatmail/core">rdzenia chatmail</a> po sfinalizowaniu projektu w IETF we współpracy z innymi implementatorami OpenPGP.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption to protect against quantum computer attacks.
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="jak-mogę-ręcznie-sprawdzić-informacje-o-szyfrowaniu">
@@ -1464,31 +1471,20 @@ Raczej korzystamy z publicznych źródeł finansowania, jak dotąd pochodzących
<ul>
<li>
<p>Unijny projekt <a href="https://nextleap.eu">NEXTLEAP</a> sfinansował badania i wdrożenie zweryfikowanych grup i ustawień protokołów kontaktowych w latach 2017 i 2018, a także pomógł zintegrować szyfrowanie end-to-end poprzez <a href="https://autocrypt.org">Autocrypt</a>.</p>
<p>W latach 2023 i 2024 zostaliśmy przyjęci do programu Next Generation Internet (NGI) za naszą pracę w <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>, wraz z partnerami współpracującymi pracującymi nad <a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>, <a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>, <a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> i <a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>. Wszystkie te projekty są częściowo ukończone lub zostaną ukończone na początku 2025 r.</p>
</li>
<li>
<p>W 2021 r. otrzymaliśmy kolejne dofinansowanie z UE na dwie propozycje dotyczące Internetu nowej generacji, a mianowicie na <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD – katalog przenośności dostawcy poczty e-mail</a> ( ~97 tys. EUR) i <a href="https://nlnet.nl/project/EmailPorting/">AEAP – przenoszenie adresu e-mail</a> (~90 tys. EUR), co zaowocowało lepszą obsługą wielu kont, ulepszonymi kontaktami i ustawieniami grup za pomocą kodów QR oraz wieloma ulepszeniami sieciowymi na wszystkich platformach.</p>
</li>
<li>
<p><a href="https://nlnet.nl/">Fundacja NLnet</a> przekazała w latach 2019/2020 kwotę 46 tys. EUR na wykonanie wiązań Rust/Python i uruchomienie ekosystemu Chat-bot.</p>
</li>
<li>
<p><a href="https://opentechfund.org">Open Technology Fund</a> przyznał nam pierwszy grant w 2018/2019 (~200 000 $), dzięki któremu znacznie ulepszyliśmy aplikację na Androida i wydaliśmy pierwszą wersję beta aplikacji na komputery stacjonarne, a także ugruntował rozwój naszych funkcji w badaniach UX w kontekście praw człowieka, zobacz nasz końcowy raport <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX</a>.
Druga dotacja w 2019/2020 (~300 000 4) pomogła nam wydać wersje Delta/iOS, przekonwertować naszą podstawową bibliotekę na Rust i zapewnić nowe funkcje dla wszystkich platform.</p>
</li>
<li>
<p><a href="https://nlnet.nl/">Fundacja NLnet</a> przekazała w latach 2019/2020 kwotę 46 tys. EUR na wykonanie wiązań Rust/Python i uruchomienie ekosystemu Chat-bot.</p>
</li>
<li>
<p>In 2021 we received further EU funding for two Next-Generation-Internet
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
</li>
<li>
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
This funding supported our long-running goals to make Delta Chat more usable
and compatible with a wide range of email servers world-wide, and more resilient and secure
in places often affected by internet censorship and shutdowns.</p>
</li>
<li>
<p>W latach 2023-2024 pomyślnie ukończyliśmy finansowany przez OTF <a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">projekt Secure Chatmail</a>, co pozwoliło nam wprowadzić gwarantowane szyfrowanie, stworzyć <a href="https://delta.chat/chatmail">sieć serwerów chatmail</a> i zapewnić „natychmiastowe wdrażanie” we wszystkich aplikacjach wydanych od kwietnia 2024 r.</p>
</li>
<li>
<p>W latach 2023 i 2024 zostaliśmy przyjęci do programu Next Generation Internet (NGI) za naszą pracę w <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>, wraz z partnerami współpracującymi pracującymi nad <a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>, <a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>, <a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> i <a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>. Wszystkie te projekty są częściowo ukończone lub zostaną ukończone na początku 2025 r.</p>
<p>Unijny projekt <a href="https://nextleap.eu">NEXTLEAP</a> sfinansował badania i wdrożenie zweryfikowanych grup i ustawień protokołów kontaktowych w latach 2017 i 2018, a także pomógł zintegrować szyfrowanie end-to-end poprzez <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>Czasami otrzymujemy jednorazowe darowizny od osób prywatnych. Na przykład w 2021 roku pewna hojna osoba przekazała nam 4K EUR w formie przelewu bankowego tytułem “kontynuujcie dobry rozwój!”. 💜 Takie pieniądze przeznaczamy na finansowanie spotkań rozwojowych lub na doraźne wydatki, których nie da się łatwo przewidzieć lub zrefundować z publicznych dotacji. Otrzymywanie większej ilości darowizn pomaga nam również stać się bardziej niezależnymi i długoterminowo rentownymi jako społeczność współpracowników.</p>
+253 -268
View File
@@ -2,36 +2,36 @@
<html lang="pt"><head><meta charset="UTF-8" /><meta name="viewport" content="initial-scale=1.0" /><link rel="stylesheet" href="../help.css" /></head><body><ul id="top">
<li><a href="#o-que-é-o-delta-chat">O que é o Delta Chat</a>
<ul>
<li><a href="#howtoe2ee">How can I find people to chat with?</a></li>
<li><a href="#why-is-a-chat-marked-as-request">Why is a chat marked as “Request”?</a></li>
<li><a href="#how-can-i-put-two-of-my-friends-in-contact-with-each-other">How can I put two of my friends in contact with each other?</a></li>
<li><a href="#howtoe2ee">Como posso encontrar pessoas para conversar?</a></li>
<li><a href="#por-que-um-chat-está-marcado-como-solicitação">Por que um chat está marcado como “Solicitação”?</a></li>
<li><a href="#como-posso-colocar-dois-dos-meus-amigos-em-contato-um-com-o-outro">Como posso colocar dois dos meus amigos em contato um com o outro?</a></li>
<li><a href="#dá-para-mandar-imagens-vídeos-e-outros-anexos-pelo-delta-chat">Dá para mandar imagens, vídeos e outros anexos pelo Delta Chat?</a></li>
<li><a href="#multiple-accounts">What are profiles? How can I switch between them?</a></li>
<li><a href="#multiple-accounts">O que são perfis? Como posso alternar entre eles?</a></li>
<li><a href="#quem-consegue-ver-a-imagem-do-meu-perfil">Quem consegue ver a imagem do meu perfil?</a></li>
<li><a href="#signature">Can I set a Bio/Status with Delta Chat?</a></li>
<li><a href="#what-do-pinning-muting-and-archiving-mean">What do Pinning, Muting and Archiving mean?</a></li>
<li><a href="#save">How do “Saved Messages” work?</a></li>
<li><a href="#what-does-the-green-dot-mean">What does the green dot mean?</a></li>
<li><a href="#signature">Posso definir uma Bio/Assinatura com o Delta Chat?</a></li>
<li><a href="#o-que-significa-fixar-silenciar-e-arquivar">O que significa Fixar, Silenciar e Arquivar?</a></li>
<li><a href="#save">Como funcionam as “Mensagens salvas”?</a></li>
<li><a href="#o-que-significa-o-ponto-verde">O que significa o ponto verde?</a></li>
<li><a href="#o-que-significam-os-carrapatos-mostrados-ao-lado-das-mensagens-de-saída">O que significam os carrapatos mostrados ao lado das mensagens de saída?</a></li>
<li><a href="#edit">Correct typos and delete messages after sending</a></li>
<li><a href="#ephemeralmsgs">How do disappearing messages work?</a></li>
<li><a href="#delold">What happens if I turn on “Delete Messages from Device”?</a></li>
<li><a href="#remove-account">How can I delete my chat profile?</a></li>
<li><a href="#edit">Corrigir erros de digitação e excluir mensagens após o envio</a></li>
<li><a href="#ephemeralmsgs">Como funcionam as mensagens efêmeras?</a></li>
<li><a href="#delold">O que acontece se eu ativar a opção “Apagar mensagens do dispositivo”?</a></li>
<li><a href="#remove-account">Como posso excluir minha conta?</a></li>
</ul>
</li>
<li><a href="#groups">Groups</a>
<li><a href="#groups">Grupos</a>
<ul>
<li><a href="#criação-de-um-grupo">Criação de um grupo</a></li>
<li><a href="#addmembers">Add and remove members</a></li>
<li><a href="#addmembers">Adicionar e remover membros</a></li>
<li><a href="#deletei-minha-própria-conta-por-acidente">Deletei minha própria conta por acidente.</a></li>
<li><a href="#não-quero-mais-receber-as-mensagens-de-um-grupo">Não quero mais receber as mensagens de um grupo.</a></li>
<li><a href="#cloning-a-group">Cloning a group</a></li>
<li><a href="#how-many-members-can-participate-in-a-single-group">How many members can participate in a single group?</a></li>
<li><a href="#clonagem-de-grupos">Clonagem de grupos</a></li>
<li><a href="#quantos-membros-podem-participar-em-um-único-grupo">Quantos membros podem participar em um único grupo?</a></li>
</ul>
</li>
<li><a href="#webxdc">In-chat apps</a>
<li><a href="#webxdc">Aplicativos embutidos</a>
<ul>
<li><a href="#where-can-i-get-in-chat-apps">Where can I get in-chat apps?</a></li>
<li><a href="#onde-posso-obter-aplicativos-embutidos">Onde posso obter aplicativos embutidos?</a></li>
<li><a href="#how-private-are-in-chat-apps">How private are in-chat apps?</a></li>
<li><a href="#create-xdc">How can I create my own in-chat apps?</a></li>
</ul>
@@ -105,118 +105,118 @@
</h2>
<p>Delta Chat is a reliable, decentralized and secure instant messaging app,
available for mobile and desktop platforms.</p>
<p>Delta Chat é um aplicativo de mensagens instantâneas confiável, descentralizado e seguro,
disponível para desktop e plataformas móveis.</p>
<ul>
<li>
<p>Instant creation of <strong>private chat profiles</strong>
with secure and interoperable <a href="https://chatmail.at/relays">chatmail relays</a>
that offer instant message delivery, and Push Notifications for iOS and Android devices.</p>
<p>Criação instantânea de **perfis de chat privados **
com <a href="https://chatmail.at/relays">relays chatmail</a> seguros e interoperáveis
que oferecem entrega de mensagens instantâneas e Notificações Push para dispositivos iOS e Android.</p>
</li>
<li>
<p>Pervasive <a href="#multiple-accounts">multi-profile</a> and
<a href="#multiclient">multi-device</a> support on all platforms
and between different <a href="https://chatmail.at/clients">chatmail apps</a>.</p>
<p>Suporte a <a href="#multiple-accounts">vários perfis</a> e
a <a href="#multiclient">vários dispositivos</a> em todas as plataformas
e entre diferentes <a href="https://chatmail.at/clients">aplicativos de chatmail</a>.</p>
</li>
<li>
<p>Interactive <a href="#webxdc">in-chat apps</a> for gaming and collaboration</p>
<p><a href="#webxdc">Aplicativos interativos embutidos</a> para jogos e colaboração</p>
</li>
<li>
<p><a href="#security-audits">Audited end-to-end encryption</a>
safe against network and server attacks.</p>
<p><a href="#security-audits">Criptografia de ponta a ponta auditada</a>
segura contra ataques à rede e ao servidor.</p>
</li>
<li>
<p>Free and Open Source software, both app and server side,
built on <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Internet Standards</a>.</p>
<p>Software gratuito e de código aberto, tanto no aplicativo quanto no servidor,
desenvolvido com base nos <a href="https://github.com/chatmail/core/blob/main/standards.md#standards-used-in-delta-chat">Padrões da Internet</a>.</p>
</li>
</ul>
<h3 id="howtoe2ee">
How can I find people to chat with? <a href="#howtoe2ee" class="anchor"></a>
Como posso encontrar pessoas para conversar? <a href="#howtoe2ee" class="anchor"></a>
</h3>
<p>First, note that Delta Chat is a private messenger.
There is no public discovery, <em>you</em> decide about your contacts.</p>
<p>Primeiro, observe que o Delta Chat é um mensageiro privado.
Não há descoberta pública, <em>você</em> decide sobre seus contatos.</p>
<ul>
<li>
<p>If you are <strong>face to face</strong> with your friend or family,
tap the <strong>QR Code</strong> icon <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" />
on the main screen.<br />
Ask your chat partner to <strong>scan</strong> the QR image
with their Delta Chat app.</p>
<p>Se você estiver <strong>cara a cara</strong> com seu amigo ou parente,
toque no ícone de <strong>QR Code</strong> <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" />
na tela principal.<br />
Peça ao seu parceiro para <strong>escanear</strong> o QR code
com o aplicativo Delta Chat dele.</p>
</li>
<li>
<p>For a <strong>remote</strong> contact setup,
from the same screen,
click “Copy” or “Share” and send the <strong>invite link</strong>
through another private chat.</p>
<p>Para criar um contato <strong>remotamente</strong>,
na mesma tela,
clique em “Copiar” ou “Compartilhar” e envie o <strong>link do convite</strong>
por meio de outro mensageiro privado.</p>
</li>
</ul>
<p>Now wait while connection gets established.</p>
<p>Agora, aguarde enquanto a conexão se estabelece.</p>
<ul>
<li>
<p>If both sides are online, they will soon see a chat
and can start messaging securely.</p>
<p>Se ambos os lados estiverem on-line, vocês logo verão um chat
e poderão começar a enviar mensagens com segurança.</p>
</li>
<li>
<p>If one side is offline or in bad network,
the ability to chat is delayed until connectivity is restored.</p>
<p>Se um lado estiver off-line ou em uma rede ruim,
a capacidade de acessar o chat será atrasada até que a conectividade seja restaurada.</p>
</li>
</ul>
<p>Congratulations!
You now will automatically use <a href="#e2ee">end-to-end encryption</a> with this contact.
If you add each other to <a href="#groups">groups</a>, end-to-end encryption will be established among all members.</p>
<p>Parabéns!
Agora você usará automaticamente a <a href="#e2ee">criptografia de ponta a ponta</a> com esse contato.
Se vocês se adicionarem a <a href="#groups">grupos</a>, a criptografia de ponta a ponta será estabelecida entre todos os membros.</p>
<h3 id="why-is-a-chat-marked-as-request">
<h3 id="por-que-um-chat-está-marcado-como-solicitação">
Why is a chat marked as “Request”? <a href="#why-is-a-chat-marked-as-request" class="anchor"></a>
Por que um chat está marcado como “Solicitação”? <a href="#por-que-um-chat-está-marcado-como-solicitação" class="anchor"></a>
</h3>
<p>As being a private messenger,
only friends and family you <a href="#howtoe2ee">share your QR code or invite link with</a> can write to you.</p>
<p>Por ser um mensageiro privado,
somente amigos e familiares com quem você <a href="#howtoe2ee">compartilha seu QR code ou link de convite</a> podem escrever para você.</p>
<p>Your friends may share your contact with other friends,
this appears as <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Request</b></p>
<p>Seus amigos podem compartilhar seu contato com outros amigos,
isso aparece como <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Solicitação</b></p>
<ul>
<li>
<p>You need to <strong>accept</strong> the request before you can reply.</p>
<p>Você precisa <strong>aceitar</strong> a solicitação antes de poder responder.</p>
</li>
<li>
<p>You can also <strong>delete</strong> it if you don’t want to chat with them for now.</p>
<p>Você também pode <strong>excluir</strong> a solicitação se não quiser conversar com a pessoa por enquanto.</p>
</li>
<li>
<p>If you delete a request, future messages from that contact will still appear
as message request, so you can change your mind. If you really don’t want to
receive messages from this person, consider <strong>blocking</strong> them.</p>
<p>Se você excluir uma solicitação, as mensagens futuras desse contato ainda aparecerão
como solicitação de mensagem, assim você pode mudar de ideia. Se você realmente não quiser
receber mensagens dessa pessoa, considere <strong>bloqueá-la</strong>.</p>
</li>
</ul>
<h3 id="how-can-i-put-two-of-my-friends-in-contact-with-each-other">
<h3 id="como-posso-colocar-dois-dos-meus-amigos-em-contato-um-com-o-outro">
How can I put two of my friends in contact with each other? <a href="#how-can-i-put-two-of-my-friends-in-contact-with-each-other" class="anchor"></a>
Como posso colocar dois dos meus amigos em contato um com o outro? <a href="#como-posso-colocar-dois-dos-meus-amigos-em-contato-um-com-o-outro" class="anchor"></a>
</h3>
<p>Attach the first contact to the chat of the second using <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment Button → Contact</strong>.
You can also add a little introduction message.</p>
<p>Anexe o primeiro contato ao chat do segundo clicando em <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Botão de Anexo → Contato</strong>.
Você também pode adicionar uma pequena mensagem de apresentação.</p>
<p>The second contact will receive a <strong>card</strong> then
and can tap it to start chatting with the first contact.</p>
<p>O segundo contato receberá um <strong>cartão</strong>
e poderá tocar nele para começar a conversar com o primeiro contato.</p>
<h3 id="dá-para-mandar-imagens-vídeos-e-outros-anexos-pelo-delta-chat">
@@ -226,31 +226,31 @@ and can tap it to start chatting with the first contact.</p>
</h3>
<p>Sim. Images, videos, files, voice messages etc. can be sent using the <img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Attachment-</strong>
or <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Voice Message</strong> buttons</p>
<p>Sim. Imagens, vídeos, arquivos, mensagens de voz etc. podem ser enviados usando os botões de<img style="vertical-align:middle; width:1.0em; margin:1px" src="../paperclip.png" alt="Paperclip" /> <strong>Anexo-</strong>
ou <img style="vertical-align:middle; width:0.8em; margin:1px" src="../mic.png" alt="Microphone" /> <strong>Mensagem de Voz</strong></p>
<p>For performance, images are optimized and sent at a smaller size by default, but you can send it as a “file” to preserve the original.</p>
<p>Para fins de desempenho, as imagens são otimizadas e enviadas em um tamanho menor por padrão, mas você pode enviá-las como um “arquivo” para preservar o original.</p>
<h3 id="multiple-accounts">
What are profiles? How can I switch between them? <a href="#multiple-accounts" class="anchor"></a>
O que são perfis? Como posso alternar entre eles? <a href="#multiple-accounts" class="anchor"></a>
</h3>
<p>A profile is <strong>a name, a picture</strong> and some additional information for encrypting messages.
A profile lives on your device(s) only
and uses the server only to relay messages.</p>
<p>Um perfil é <strong>um nome, uma foto</strong> e algumas informações adicionais para criptografar mensagens.
Um perfil reside apenas em seu(s) dispositivo(s)
e usa o servidor somente para transportar mensagens.</p>
<p>On first installation of Delta Chat a first profile is created.</p>
<p>Na primeira instalação do Delta Chat, é criado um primeiro perfil.</p>
<p>Later, you can tap your profile image in the upper left corner to <strong>Add Profiles</strong>
or to <strong>Switch Profiles</strong>.</p>
<p>Depois, você pode tocar em sua imagem de perfil no canto superior esquerdo para <strong>Adicionar Conta</strong>
ou para <strong>Mudar de Conta</strong>.</p>
<p>You may want to use separate profiles for political, family or work related activities.</p>
<p>Talvez você queira usar perfis separados para atividades políticas, familiares ou atividades do trabalho.</p>
<p>You may also wish to learn <a href="#multiclient">how to use the same profile on multiple devices</a>.</p>
<p>Talvez você também queira aprender <a href="#multiclient">como usar a mesma conta em vários dispositivos</a>.</p>
<h3 id="quem-consegue-ver-a-imagem-do-meu-perfil">
@@ -267,96 +267,96 @@ or to <strong>Switch Profiles</strong>.</p>
<h3 id="signature">
Can I set a Bio/Status with Delta Chat? <a href="#signature" class="anchor"></a>
Posso definir uma Bio/Assinatura com o Delta Chat? <a href="#signature" class="anchor"></a>
</h3>
<p>Yes,
you can do so under <strong>Settings → Profile → Bio</strong>.
Once you sent a message to a contact,
they will see it when they view your contact details.</p>
<p>Sim,
você pode fazer isso em <strong>Configurações → Conta → Assinatura</strong>.
Depois de enviar uma mensagem para um contato,
ele verá sua assinatura quando visualizar seus detalhes de contato.</p>
<h3 id="what-do-pinning-muting-and-archiving-mean">
<h3 id="o-que-significa-fixar-silenciar-e-arquivar">
What do Pinning, Muting and Archiving mean? <a href="#what-do-pinning-muting-and-archiving-mean" class="anchor"></a>
O que significa Fixar, Silenciar e Arquivar? <a href="#o-que-significa-fixar-silenciar-e-arquivar" class="anchor"></a>
</h3>
<p>Use these tools to organize your chats and keep everything in its place:</p>
<p>Use essas ferramentas para organizar seus chats e manter tudo em seu devido lugar:</p>
<ul>
<li>
<p><strong>Pinned chats</strong> always stay atop of the chat list. You can use them to access your most loved chats quickly or temporarily to not forget about things.</p>
<p>Os <strong>chats fixados</strong> sempre ficam no topo da lista de conversas. Você pode usá-los para acessar seus chats mais queridos rapidamente ou temporariamente para não se esquecer de nada.</p>
</li>
<li>
<p><strong>Mute chats</strong> if you do not want to get notifications for them. Muted chats stay in place and you can also pin a muted chat.</p>
<p><strong>Silencie os chats</strong> se não quiser receber notificações deles. Os bate-papos silenciados permanecem no lugar e você também pode fixar um chat silenciado.</p>
</li>
<li>
<p><strong>Archive chats</strong> if you do not want to see them in your chat list any longer.
They remain accessible above the chat list or via search
and are marked by <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Archived</b></p>
<p><strong>Arquive chats</strong> se não quiser mais vê-los em sua lista de conversas.
Eles permanecem acessíveis acima da lista conversa ou por meio de pesquisa
e são marcados como <b style="border: 1px solid currentColor; padding: 0 3px; font-size:90%">Arquivado</b></p>
</li>
<li>
<p>When an archived chat gets a new message, unless muted, it will <strong>pop out of the archive</strong> and back into your chat list.
<strong>Muted chats stay archived</strong> until you unarchive them manually.</p>
<p>Quando um chat arquivado receber uma nova mensagem, a menos que esteja silenciado, ele <strong>sairá do arquivo</strong> e voltará à sua lista de conversas.
<strong>Os chats silenciados permanecem arquivados</strong> até que você os desarquive manualmente.</p>
</li>
</ul>
<p>To use the functions, long tap or right click a chat in the chat list.</p>
<p>Para usar as funções, toque e segure ou clique com o botão direito do mouse em um conversa na lista conversas.</p>
<h3 id="save">
How do “Saved Messages” work? <a href="#save" class="anchor"></a>
Como funcionam as “Mensagens salvas”? <a href="#save" class="anchor"></a>
</h3>
<p><strong>Saved Messages</strong> is a chat that you can use to easily remember and find messages.</p>
<p><strong>Saved Messages</strong> é um chat que você pode usar para lembrar e localizar facilmente as mensagens.</p>
<ul>
<li>
<p>In any chat, long tap or right click a message and select <strong>Save</strong></p>
<p>Em qualquer chat, dê um toque longo ou clique com o botão direito do mouse em uma mensagem e selecione <strong>Salvar</strong></p>
</li>
<li>
<p>Saved messages are marked by the symbol
<img style="vertical-align:middle; width:1.2em; margin:1px" src="../saved-icon.png" alt="Saved icon" />
next to the timestamp</p>
<p>As mensagens salvas são marcadas com o símbolo
<img style="vertical-align:middle; width:1.2em; margin:1px" src="../saved-icon.png" alt="Saved icon" />
ao lado do registro de data e hora</p>
</li>
<li>
<p>Later, open the “Saved Messages” chat - and you will see the saved messages there.
By tapping <img style="vertical-align:middle; width:1.2em; margin:1px" src="../go-to-original.png" alt="Arrow-right icon" />,
you can go back to the original message in the original chat</p>
<p>Depois, abra o chat de “Mensagens salvas” - e você verá as mensagens salvas lá.
Ao tocar em <img style="vertical-align:middle; width:1.2em; margin:1px" src="../go-to-original.png" alt="Arrow-right icon" />,
você pode voltar para a mensagem original no chat original</p>
</li>
<li>
<p>Finally, you can also use “Saved Messages” to take <strong>personal notes</strong> - open the chat, type something, add a photo or a voice message etc.</p>
<p>Por fim, você também pode usar “Mensagens Salvas” para fazer <strong>notas pessoais</strong> - abra o chat, digite algo, adicione uma foto ou uma mensagem de voz etc.</p>
</li>
<li>
<p>As “Saved Message” are synced, they can become very handy for transferring data between devices</p>
<p>Como as “Mensagens salvas” são sincronizadas, elas podem se tornar muito úteis para a transferência de dados entre dispositivos</p>
</li>
</ul>
<p>Messages stay saved even if they are edited or deleted -
may it be by <a href="#edit">sender</a>, by <a href="#delold">device cleanup</a> or by <a href="#ephemeralmsgs">disappearing messages of other chats</a>.</p>
<p>As mensagens permanecem salvas mesmo que sejam editadas ou excluídas -
seja pelo <a href="#edit">remetente</a>, por <a href="#delold">limpeza do dispositivo</a> ou por <a href="#ephemeralmsgs">mensagens temporárias de outros chats</a>.</p>
<h3 id="what-does-the-green-dot-mean">
<h3 id="o-que-significa-o-ponto-verde">
What does the green dot mean? <a href="#what-does-the-green-dot-mean" class="anchor"></a>
O que significa o ponto verde? <a href="#o-que-significa-o-ponto-verde" class="anchor"></a>
</h3>
<p>You can sometimes see a <strong>green dot</strong> <img style="vertical-align:middle; width:1.2em; margin:1px" src="../green-dot.png" alt="" />
next to the avatar of a contact.
It means they were <strong>recently seen by you</strong> in the last 10 minutes,
e.g. because they messaged you or sent a read receipt.</p>
<p>Às vezes, você pode ver um <strong>ponto verde</strong> <img style="vertical-align:middle; width:1.2em; margin:1px" src="../green-dot.png" alt="" />
ao lado do avatar de um contato.
Isso significa que ele foi <strong>visto recentemente por você</strong> nos últimos 10 minutos,
por exemplo, porque ele enviou uma mensagem para você ou uma confirmação de leitura.</p>
<p>So this is not a real time online status
and others will as well not always see that you are “online”.</p>
<p>Portanto, essa não é uma checagem de status on-line em tempo real
e outras pessoas também nem sempre verão que você está “on-line”.</p>
<h3 id="o-que-significam-os-carrapatos-mostrados-ao-lado-das-mensagens-de-saída">
@@ -368,132 +368,131 @@ and others will as well not always see that you are “online”.</p>
<ul>
<li>
<p><strong>One tick</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick1.png" alt="" />
means that the message was sent successfully to the <a href="#relays">relay</a>.</p>
<p><strong>Um tique</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick1.png" alt="" />
significa que a mensagem foi enviada com sucesso para o <a href="#relays">relay</a>.</p>
</li>
<li>
<p><strong>Two ticks</strong> <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick2.png" alt="" />
indicate your contact has read the message.</p>
<p>Dois ticks** <img style="vertical-align:middle; width:1.5em; margin:1px" src="../tick2.png" alt="" />
indicam que seu contato leu a mensagem.</p>
</li>
</ul>
<p>In <a href="#groups">groups</a> the second tick means that at least one member has reported back having read the message.</p>
<p>Em <a href="#groups">grupos</a>, o segundo tique significa que pelo menos um membro leu a mensagem.</p>
<p>You will only get the second tick if both you and one of the recipients who read the message
has <strong>Settings → Chats → Read Receipts</strong> enabled.</p>
<p>Você só receberá o segundo tique se tanto você quanto um dos destinatários que leu a mensagem
tiverem <strong>Configurações → Conversas → Confirmação de leitura</strong> ativada.</p>
<h3 id="edit">
Correct typos and delete messages after sending <a href="#edit" class="anchor"></a>
Corrigir erros de digitação e excluir mensagens após o envio <a href="#edit" class="anchor"></a>
</h3>
<ul>
<li>
<p>You can edit the text of your messages after sending.
For that, long tap or right click the message and select <strong>Edit</strong>
or <img style="vertical-align:middle; width:1.2em; margin:1px" src="../edit-icon.png" alt="Edit icon" />.</p>
<p>Você pode editar o texto de suas mensagens após o envio.
Para isso, toque longamente ou clique com o botão direito do mouse na mensagem e selecione <strong>Editar Mensagem</strong>
ou <img style="vertical-align:middle; width:1.2em; margin:1px" src="../edit-icon.png" alt="Edit icon" />.</p>
</li>
<li>
<p>If you have sent a message accidentally,
from the same menu, select <strong>Delete</strong> and then <strong>Delete for Everyone</strong>.</p>
<p>Se você tiver enviado uma mensagem acidentalmente,
no mesmo menu, selecione <strong>Apagar</strong> e, em seguida, <strong>Apagar para Toda a Gente</strong>.</p>
</li>
</ul>
<p>While edited messages will have the word “Edited” next to the timestamp,
deleted messages will be removed without a marker in the chat.
Notifications are not sent and there is no time limit.</p>
<p>Enquanto as mensagens editadas terão a palavra “Editado” ao lado do registro de data e hora,
as mensagens excluídas serão removidas sem um marcador no chat.
Não são enviadas notificações e não há limite de tempo.</p>
<p>Note, that the original message may still be received by chat members
who could have already replied, forwarded, saved, screenshotted or otherwise copied the message.</p>
<p>Observe que a mensagem original ainda pode ser recebida pelos membros do conversa
que podem ter respondido, encaminhado, salvo, capturado a tela ou copiado a mensagem.</p>
<h3 id="ephemeralmsgs">
How do disappearing messages work? <a href="#ephemeralmsgs" class="anchor"></a>
Como funcionam as mensagens efêmeras? <a href="#ephemeralmsgs" class="anchor"></a>
</h3>
<p>You can turn on “disappearing messages”
in the settings of a chat,
at the top right of the chat window,
by selecting a time span
between 5 minutes and 1 year.</p>
<p>Você pode ativar a opção “mensagens temporárias”
nas configurações de um conversa,
no canto superior direito da janela conversa,
selecionando um intervalo de tempo
entre 5 minutos e 1 ano.</p>
<p>Until the setting is turned off again,
each chat member’s Delta Chat app takes care
of deleting the messages
after the selected time span.
The time span begins
when the receiver first sees the message in Delta Chat.
The messages are deleted both,
on the servers,
and in the apps itself.</p>
<p>Até que a configuração seja desativada novamente,
o aplicativo Delta Chat de cada membro do conversa se encarrega de
de excluir as mensagens
após o período de tempo selecionado.
O intervalo de tempo começa
quando o destinatário vê a mensagem pela primeira vez no Delta Chat.
As mensagens são excluídas tanto
nos servidores
como nos próprios aplicativos.</p>
<p>Note that you can rely on disappearing messages
only as long as you trust your chat partners;
malicious chat partners can take photos,
or otherwise save, copy or forward messages before deletion.</p>
<p>Observe que você pode confiar no desaparecimento das mensagens
somente se você confiar em seus parceiros do conversa;
parceiros mal-intencionados de conversa podem tirar fotos,
ou salvar, copiar ou encaminhar mensagens antes de excluí-las.</p>
<p>Apart from that,
if one chat partner uninstalls Delta Chat,
the (anyway encrypted) messages may take longer to get deleted from their server.</p>
<p>Além disso,
se um parceiro de conversa desinstalar o Delta Chat,
as mensagens (de qualquer forma criptografadas) podem demorar mais para serem excluídas do servidor deles.</p>
<h3 id="delold">
What happens if I turn on “Delete Messages from Device”? <a href="#delold" class="anchor"></a>
O que acontece se eu ativar a opção “Apagar mensagens do dispositivo”? <a href="#delold" class="anchor"></a>
</h3>
<p>If you want to save storage on your device, you can choose to delete old
messages automatically.</p>
<p>Se quiser economizar espaço de armazenamento no dispositivo, você pode optar por excluir mensagens antigas automaticamente.</p>
<p>To turn it on, go to <strong>Settings → Chats → Delete Message from Device</strong>.
You can set a timeframe between “after an hour” and “after a year”;
this way, <em>all</em> messages will be deleted from your device as soon as they are
older than that.</p>
<p>Para ativá-la, vá para <strong>Configurações → Conversas → Apagar mensagens do dispositivo</strong>.
Você pode definir um período de tempo entre “após uma hora” e “após um ano”;
Dessa forma, <em>todas</em> as mensagens serão excluídas de seu dispositivo assim que forem
mais antigas do que isso.</p>
<h3 id="remove-account">
How can I delete my chat profile? <a href="#remove-account" class="anchor"></a>
Como posso excluir minha conta? <a href="#remove-account" class="anchor"></a>
</h3>
<p>If you are using more than one chat profile,
you can remove single ones in the top profile switcher menu (on Android and iOS),
or in the sidebar with a right click (in the Desktop app).
Chat profiles are only removed on the device where deletion was triggered.
Chat profiles on other devices will continue to fully function.</p>
<p>Se estiver usando mais de uma conta,
você pode removê-las individualmente no menu superior de mudar de conta (no Android e no iOS),
ou na barra lateral com um clique no botão direito do mouse (no aplicativo para desktop).
As contas são removidas somente no dispositivo em que a exclusão foi acionada.
Contas em outros dispositivos continuarão funcionando plenamente.</p>
<p>If you use a single default chat profile you can simply uninstall the app.
This will still automatically trigger deletion of all associated address data on the chatmail server.
For more info, please refer to <a href="https://nine.testrun.org/info.html#account-deletion">nine.testrun.org address-deletion</a>
or the respective page from your chosen <a href="https://chatmail.at/relays">3rd party chatmail server</a>.</p>
<p>Se você usar uma conta única padrão, poderá simplesmente desinstalar o aplicativo.
Isso ainda acionará automaticamente a exclusão de todos os dados de endereço associados no servidor de chatmail.
Para obter mais informações, consulte <a href="https://nine.testrun.org/info.html#account-deletion">nine.testrun.org address-deletion</a>
ou a respectiva página do <a href="https://chatmail.at/relays">servidor de chatmail de terceiros</a> que você escolheu.</p>
<h2 id="groups">
Groups <a href="#groups" class="anchor"></a>
Grupos <a href="#groups" class="anchor"></a>
</h2>
<p>Groups let several people chat together privately with <strong>equal rights</strong>.</p>
<p>Os grupos permitem que várias pessoas conversem em particular com <strong>direitos iguais</strong>.</p>
<p>Anyone can
change the group name or avatar,
<a href="#addmembers">add or remove members</a>,
set <a href="#ephemeralmsgs">disappearing messages</a>,
and <a href="#edit">delete their own messages</a> from all member’s devices.</p>
<p>Qualquer pessoa pode
alterar o nome do grupo ou o avatar,
<a href="#addmembers">adicionar ou remover membros</a>,
definir <a href="#ephemeralmsgs">mensagens temporárias</a>,
e <a href="#edit">excluir suas próprias mensagens</a> dos dispositivos de todos os membros.</p>
<p>Because all members have the same rights, groups work best among <strong>trusted friends and family</strong>.</p>
<p>Como todos os membros têm os mesmos direitos, os grupos funcionam melhor entre <strong>amigos e familiares de confiança</strong>.</p>
<h3 id="criação-de-um-grupo">
@@ -518,33 +517,33 @@ and <a href="#edit">delete their own messages</a> from all member’s devices.</
<h3 id="addmembers">
Add and remove members <a href="#addmembers" class="anchor"></a>
Adicionar e remover membros <a href="#addmembers" class="anchor"></a>
</h3>
<p>All group members have the <strong>same rights</strong>.
For this reason, everyone can delete any member or add new ones.</p>
<p>Todos os membros do grupo têm os <strong>mesmos direitos</strong>.
Por este motivo, todos podem remover qualquer membro ou adicionar novos membros.</p>
<ul>
<li>
<p>To <strong>add or delete members</strong>, tap the group name in the chat and select the member to add or remove.</p>
<p>Para <strong>adicionar ou remover membros</strong>, toque no nome do grupo dentro do chat e selecione o membro a adicionar ou a remover.</p>
</li>
<li>
<p>If the member is not yet in your contact list, but <strong>face to face</strong> with you,
from the same screen, show a <strong>QR code</strong>.<br />
Ask your chat partner to <strong>scan</strong> the QR image with their Delta Chat app by tapping
<img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> on the main screen.</p>
<p>Se o membro ainda não estiver na sua lista de contactos, mas estiver <strong>cara a cara</strong> com você,
na mesma tela, moste um <strong>QR code</strong>.<br />
Peça ao seu parceiro de conversa para <strong>escanear</strong> o QR code com o Delta Chat dele, tocando em
<img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> na tela inicial.</p>
</li>
<li>
<p>For a <strong>remote</strong> member addition,
click “Copy” or “Share” and send the <strong>invite link</strong>
through another private chat to the new member.</p>
<p>Para adicionar um membro <strong>remotamente</strong>,
clique em “Copiar” ou “Compartilhar” e envie o link do <strong>convite</strong>
por meio de outro mensageiro privado para o novo membro.</p>
</li>
</ul>
<p>QR code and invite link can be used to add several members.
However, since groups are <a href="#groups">meant for trusted people</a>, avoid sharing them publicly.</p>
<p>O QR code e o link de convite podem ser usados para adicionar vários membros.
No entanto, como os grupos são <a href="#groups">destinados a pessoas de confiança</a>, evite compartilhá-los publicamente.</p>
<h3 id="deletei-minha-própria-conta-por-acidente">
@@ -571,60 +570,60 @@ Se você quiser entrar mais tarde no grupo novamente, peça a outra pessoa do gr
<p>Uma alternativa é “silenciar” um grupo. Fazendo isso, você receberá todas as mensagens e ainda poderá escrever, mas não será receberá mais notificações d enovas mensagens.</p>
<h3 id="cloning-a-group">
<h3 id="clonagem-de-grupos">
Cloning a group <a href="#cloning-a-group" class="anchor"></a>
Clonagem de grupos <a href="#clonagem-de-grupos" class="anchor"></a>
</h3>
<p>You can duplicate a group to start a separate discussion
or to exclude members without them noticing.</p>
<p>Você pode duplicar um grupo para iniciar uma discussão separada
ou para excluir membros sem que eles percebam.</p>
<ul>
<li>
<p>Open the group profile and tap <strong>Clone Chat</strong> (Android/iOS),
or right-click the group in the chat list (Desktop).</p>
<p>Abra o perfil do grupo e toque em <strong>Clonar conversa</strong> (Android/iOS),
ou clique com o botão direito do mouse no grupo na lista de conversa (desktop).</p>
</li>
<li>
<p>Set a new name, choose an avatar, and adjust the member list if needed.</p>
<p>Defina um novo nome, escolha um avatar e ajuste a lista de membros, se necessário.</p>
</li>
</ul>
<p>The new group is <strong>fully independent</strong> from the original,
which continues to work as before.</p>
<p>O novo grupo é <strong>totalmente independente</strong> do original,
que continua funcionando como antes.</p>
<h3 id="how-many-members-can-participate-in-a-single-group">
<h3 id="quantos-membros-podem-participar-em-um-único-grupo">
How many members can participate in a single group? <a href="#how-many-members-can-participate-in-a-single-group" class="anchor"></a>
Quantos membros podem participar em um único grupo? <a href="#quantos-membros-podem-participar-em-um-único-grupo" class="anchor"></a>
</h3>
<p>There is no strict technical limit,
but more than 150 is not recommended.</p>
<p>Não há um limite técnico rigoroso,
mas não é recomendável mais de 150.</p>
<p>As groups get larger, they can become socially unstable and may need a hierarchy -
where Delta Chat is a private messenger for chatting with <a href="#groups">equal rights</a>.
See <a href="https://en.wikipedia.org/wiki/Dunbar%27s_number">Dunbar’s number</a> for more insights.</p>
<p>À medida que os grupos se tornam maiores, eles podem se tornar socialmente instáveis e podem precisar de uma hierarquia -
mas o Delta Chat é um mensageiro privado para conversar com <a href="#groups">direitos iguais</a>.
Consulte o <a href="https://pt.wikipedia.org/wiki/N%C3%BAmero_de_Dunbar">Número de Dunbar</a> para obter mais informações.</p>
<h2 id="webxdc">
In-chat apps <a href="#webxdc" class="anchor"></a>
Aplicativos embutidos <a href="#webxdc" class="anchor"></a>
</h2>
<p>You can send apps to a chat - games, editors, polls and other tools.
This makes Delta Chat a truly extensible messenger.</p>
<p>Você pode enviar aplicativos para um chat - jogos, editores, enquetes e outras ferramentas.
Isso torna o Delta Chat um mensageiro realmente extensível.</p>
<h3 id="where-can-i-get-in-chat-apps">
<h3 id="onde-posso-obter-aplicativos-embutidos">
Where can I get in-chat apps? <a href="#where-can-i-get-in-chat-apps" class="anchor"></a>
Onde posso obter aplicativos embutidos? <a href="#onde-posso-obter-aplicativos-embutidos" class="anchor"></a>
</h3>
@@ -1184,6 +1183,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
<li>
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
establishing end-to-end encryption between contacts and all members of a group chat.</p>
</li>
<li>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption and forward secrecy.</p>
</li>
<li>
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
@@ -1368,12 +1371,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
<p>Servers can therefore only see:</p>
<ul>
<li>the sender and receiver addresses</li>
<li>and the message size.</li>
<li>Sender and receiver addresses, randomly generated by default</li>
<li>Message size</li>
</ul>
<p>By default, the addresses are randomly generated.</p>
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
<h3 id="device-seizure">
@@ -1449,7 +1450,7 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
This means that if your private decryption key is leaked,
@@ -1460,12 +1461,9 @@ Otherwise, someone obtaining your decryption keys
is typically also able to get all your non-deleted messages
and doesn’t even need to decrypt any previously collected messages.</p>
<p>We designed a Forward Secrecy approach that withstood
initial examination from some cryptographers and implementation experts
but is pending a more formal write up
to ascertain it reliably works in federated messaging and with multi-device usage,
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will provide reliable deletion (forward secrecy) through automatic key rotation.
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="pqc">
@@ -1475,12 +1473,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
in collaboration with other OpenPGP implementers.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption to protect against quantum computer attacks.
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="how-can-i-manually-check-encryption-information">
@@ -1659,52 +1658,38 @@ ordered chronologically:</p>
<ul>
<li>
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
and implementation of verified groups and setup contact protocols
in 2017 and 2018 and also helped to integrate end-to-end Encryption
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
and released a first Desktop app beta version, and which moreover
moored our feature developments in UX research in human rights contexts,
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
The second 2019/2020 grant (~$300K) helped us to
release Delta/iOS versions, to convert our core library to Rust, and
to provide new features for all platforms.</p>
</li>
<li>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
</li>
<li>
<p>In 2021 we received further EU funding for two Next-Generation-Internet
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
</li>
<li>
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
This funding supported our long-running goals to make Delta Chat more usable
and compatible with a wide range of email servers world-wide, and more resilient and secure
in places often affected by internet censorship and shutdowns.</p>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
</li>
<li>
<p>2023-2024 we successfully completed the OTF-funded
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
allowing us to introduce guaranteed encryption,
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
and providing “instant onboarding” in all apps released from April 2024 on.</p>
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
and released a first Desktop app beta version, and which moreover
moored our feature developments in UX research in human rights contexts,
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
The second 2019/2020 grant (~$300K) helped us to
release Delta/iOS versions, to convert our core library to Rust, and
to provide new features for all platforms.</p>
</li>
<li>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
and implementation of verified groups and setup contact protocols
in 2017 and 2018 and also helped to integrate end-to-end Encryption
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>Sometimes we receive one-time donations from private individuals.
+37 -51
View File
@@ -1181,6 +1181,10 @@ Chatmail использует INBOX по умолчанию для ретран
<li>
<p><a href="https://autocrypt.org">Autocrypt</a> используется для автоматической
настройки сквозного шифрования между контактами и всеми членами группового чата.</p>
</li>
<li>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, полное внедрение которого запланировано на 2026 год,
обеспечит поддержку постквантового шифрования и прямой секретности.</p>
</li>
<li>
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Обмен контактом в
@@ -1365,12 +1369,10 @@ Delta Chat вместо этого использует реализацию Ope
<p>Таким образом, серверы могут видеть только:</p>
<ul>
<li>адрес отправителя и получателя</li>
<li>а также размер сообщения.</li>
<li>Адреса отправителя и получателя, по умолчанию генерируются случайным образом</li>
<li>Размер сообщения</li>
</ul>
<p>По умолчанию адреса генерируются случайным образом.</p>
<p>Все прочие метаданные сообщений, контактов и групп содержатся в части сообщений, защищённой сквозным шифрованием.</p>
<h3 id="device-seizure">
@@ -1427,7 +1429,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 +1450,7 @@ Delta Chat вместо этого использует реализацию Ope
</h3>
<p>Нет, еще нет.</p>
<p>Пока нет, но это будет реализовано в <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>На данный момент, Delta Chat не поддерживает Perfect Forward Secrecy (PFS) (Совершенную прямую секретность).
Это означает, что если ваш приватный ключ дешифрования будет скомпрометирован,
@@ -1459,12 +1461,9 @@ Delta Chat вместо этого использует реализацию Ope
также может получить все ваши не удалённые сообщения
и ему даже не нужно расшифровывать какие-либо ранее собранные сообщения.</p>
<p>Мы разработали подход к Forward Secrecy (Прямой секретности), который прошёл
первичную проверку некоторыми криптографами и экспертами по реализации
но требует более формального описания
чтобы убедиться, что он надёжно работает в федеративном обмене сообщениями и при использовании нескольких устройств,
прежде чем он может быть внедрён в <a href="https://github.com/chatmail/core">ядро chatmail</a>,
что сделает его доступным во всех <a href="https://chatmail.at/clients">клиентах clients</a>.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, полное внедрение которого запланировано на 2026 год,
обеспечит надёжное удаление (прямую секретность) за счёт автоматической ротации ключей.
Этот подход описан в черновике спецификации <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a>.</p>
<h3 id="pqc">
@@ -1474,12 +1473,13 @@ Delta Chat вместо этого использует реализацию Ope
</h3>
<p>Нет, еще нет.</p>
<p>Пока нет, но эта возможность появится в <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat использует библиотеку OpenPGP на Rust <a href="https://github.com/rpgp/rpgp">rPGP</a>,
которая поддерживает последний <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">черновик IETF Post-Quantum-Cryptography OpenPGP</a>.
Мы планируем добавить поддержку PQC в <a href="https://github.com/chatmail/core">ядро chatmail</a> после того, как черновик будет окончательно утвержден в IETF
в сотрудничестве с другими разработчиками OpenPGP.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, полное внедрение которого запланировано на 2026 год,
обеспечит поддержку постквантового шифрования для защиты от атак с использованием квантовых компьютеров.
Delta Chat использует Rust-библиотеку OpenPGP <a href="https://github.com/rpgp/rpgp">rPGP</a>
которая поддерживает актуальный черновик IETF <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP</a>.
Особенности реализации описаны в черновике спецификации <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a>.</p>
<h3 id="как-можно-вручную-проверить-информацию-о-шифровании">
@@ -1658,10 +1658,22 @@ Google Play Store, F-Droid, Huawei App Gallery, iOS и macOS App Store, Microsof
<ul>
<li>
<p>Проект ЕС <a href="https://nextleap.eu">NEXTLEAP</a> финансировал исследование
и внедрение проверенных групп и настройку протоколов контактов
в 2017 и 2018 годах, а также помог интегрировать сквозное шифрование
через <a href="https://autocrypt.org">Autocrypt</a>.</p>
<p>В 2023 и 2024 годах мы были приняты в программу Next Generation Internet (NGI)
за нашу работу над <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
в сотрудничестве с партнерами, работающими над
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> и
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
Все эти проекты частично завершены или будут завершены в начале 2025 года.</p>
</li>
<li>
<p>В 2021 г. мы получили дополнительное финансирование из ЕС для двух Next-Generation-Internet
целей, а именно для <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - e-mail provider portability directory</a> (~97 тыс. евро) и <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90 тыс. евро). Это привело к улучшению поддержки нескольких профилей, улучшению настройки контактов и групп с помощью QR-кода и многим улучшениям в сетевом взаимодействии на всех платформах.</p>
</li>
<li>
<p>Фонд <a href="https://nlnet.nl/">NLnet Foundation</a> выделил в 2019/2020 году 46 тысяч евро на
доработку связки Rust/Python и создание экосистемы чат-ботов..</p>
</li>
<li>
<p>Фонд <a href="https://opentechfund.org">Open Technology Fund</a> предоставил нам
@@ -1674,36 +1686,10 @@ Google Play Store, F-Droid, Huawei App Gallery, iOS и macOS App Store, Microsof
предоставить новые функции для всех платформ.</p>
</li>
<li>
<p><a href="https://nlnet.nl/">Фонд NLnet</a> выделил в 2019/2020 году 46 тыс. евро на
завершение привязки Rust/Python и создание экосистемы чат-ботов.</p>
</li>
<li>
<p>В 2021 г. мы получили дополнительное финансирование из ЕС для двух Next-Generation-Internet
целей, а именно для <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - e-mail provider portability directory</a> (~97 тыс. евро) и <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90 тыс. евро). Это привело к улучшению поддержки нескольких профилей, улучшению настройки контактов и групп с помощью QR-кода и многим улучшениям в сетевом взаимодействии на всех платформах.</p>
</li>
<li>
<p>С конца 2021 года по март 2023 года мы получили финансирование в размере ($500 тыс.) от
U.S. Bureau of Democracy, Human Rights and Labor (DRL) для поддержки <em>свободы интернета</em>.
Это финансирование поддержало наши долгосрочные цели, сделать Delta Chat более удобным для использования
и совместимым с широким спектром электронных почтовых серверов по всему миру, а также более устойчивым
и безопасным в местах, часто подвергающихся интернет-цензуре и отключениям.</p>
</li>
<li>
<p>2023-2024 мы завершили проект финансируемый OTF
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
что позволило нам внедрить гарантированное шифрование,
создать сеть серверов <a href="https://delta.chat/chatmail">chatmail</a>
и обеспечить “немедленную регистрацию” во всех приложениях, выпущенных с апреля 2024 года.</p>
</li>
<li>
<p>В 2023 и 2024 годах мы были приняты в программу Next Generation Internet (NGI)
за нашу работу над <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
в сотрудничестве с партнерами, работающими над
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> и
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
Все эти проекты частично завершены или будут завершены в начале 2025 года.</p>
<p>Проект ЕС <a href="https://nextleap.eu">NEXTLEAP</a> финансировал исследование
и внедрение проверенных групп и настройку протоколов контактов
в 2017 и 2018 годах, а также помог интегрировать сквозное шифрование
через <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>Иногда мы получаем разовые пожертвования от физических лиц.
+38 -52
View File
@@ -1188,6 +1188,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
<li>
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
establishing end-to-end encryption between contacts and all members of a group chat.</p>
</li>
<li>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption and forward secrecy.</p>
</li>
<li>
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
@@ -1372,12 +1376,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
<p>Servers can therefore only see:</p>
<ul>
<li>the sender and receiver addresses</li>
<li>and the message size.</li>
<li>Sender and receiver addresses, randomly generated by default</li>
<li>Message size</li>
</ul>
<p>By default, the addresses are randomly generated.</p>
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
<h3 id="device-seizure">
@@ -1453,7 +1455,7 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
This means that if your private decryption key is leaked,
@@ -1464,12 +1466,9 @@ Otherwise, someone obtaining your decryption keys
is typically also able to get all your non-deleted messages
and doesn’t even need to decrypt any previously collected messages.</p>
<p>We designed a Forward Secrecy approach that withstood
initial examination from some cryptographers and implementation experts
but is pending a more formal write up
to ascertain it reliably works in federated messaging and with multi-device usage,
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will provide reliable deletion (forward secrecy) through automatic key rotation.
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="pqc">
@@ -1479,12 +1478,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
in collaboration with other OpenPGP implementers.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption to protect against quantum computer attacks.
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="how-can-i-manually-check-encryption-information">
@@ -1663,52 +1663,38 @@ ordered chronologically:</p>
<ul>
<li>
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
and implementation of verified groups and setup contact protocols
in 2017 and 2018 and also helped to integrate end-to-end Encryption
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
and released a first Desktop app beta version, and which moreover
moored our feature developments in UX research in human rights contexts,
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
The second 2019/2020 grant (~$300K) helped us to
release Delta/iOS versions, to convert our core library to Rust, and
to provide new features for all platforms.</p>
</li>
<li>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
</li>
<li>
<p>In 2021 we received further EU funding for two Next-Generation-Internet
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
</li>
<li>
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
This funding supported our long-running goals to make Delta Chat more usable
and compatible with a wide range of email servers world-wide, and more resilient and secure
in places often affected by internet censorship and shutdowns.</p>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
</li>
<li>
<p>2023-2024 we successfully completed the OTF-funded
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
allowing us to introduce guaranteed encryption,
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
and providing “instant onboarding” in all apps released from April 2024 on.</p>
<p>The <a href="https://opentechfund.org">Open Technology Fund</a> gave us a
first 2018/2019 grant (~$200K) during which we majorly improved the Android app
and released a first Desktop app beta version, and which moreover
moored our feature developments in UX research in human rights contexts,
see our concluding <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
The second 2019/2020 grant (~$300K) helped us to
release Delta/iOS versions, to convert our core library to Rust, and
to provide new features for all platforms.</p>
</li>
<li>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
and implementation of verified groups and setup contact protocols
in 2017 and 2018 and also helped to integrate end-to-end Encryption
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>Sometimes we receive one-time donations from private individuals.
+39 -50
View File
@@ -1190,6 +1190,10 @@ to exchange encryption setup information through QR-code scanning or “invite l
<li>
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
establishing end-to-end encryption between contacts and all members of a group chat.</p>
</li>
<li>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption and forward secrecy.</p>
</li>
<li>
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Sharing a contact to a
@@ -1374,12 +1378,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
<p>Servers can therefore only see:</p>
<ul>
<li>the sender and receiver addresses</li>
<li>and the message size.</li>
<li>Sender and receiver addresses, randomly generated by default</li>
<li>Message size</li>
</ul>
<p>By default, the addresses are randomly generated.</p>
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
<h3 id="device-seizure">
@@ -1455,7 +1457,7 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
This means that if your private decryption key is leaked,
@@ -1466,12 +1468,9 @@ Otherwise, someone obtaining your decryption keys
is typically also able to get all your non-deleted messages
and doesn’t even need to decrypt any previously collected messages.</p>
<p>We designed a Forward Secrecy approach that withstood
initial examination from some cryptographers and implementation experts
but is pending a more formal write up
to ascertain it reliably works in federated messaging and with multi-device usage,
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will provide reliable deletion (forward secrecy) through automatic key rotation.
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="pqc">
@@ -1481,12 +1480,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
in collaboration with other OpenPGP implementers.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption to protect against quantum computer attacks.
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="how-can-i-manually-check-encryption-information">
@@ -1665,10 +1665,25 @@ ordered chronologically:</p>
<ul>
<li>
<p>The <a href="https://nextleap.eu">NEXTLEAP</a> EU project funded the research
and implementation of verified groups and setup contact protocols
in 2017 and 2018 and also helped to integrate end-to-end Encryption
through <a href="https://autocrypt.org">Autocrypt</a>.</p>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
</li>
<li>
<p>Më 2021-n morëm financime të mëtejshme nga BE për dy propozime që shtrihen në
“Internetin e Brezit Tjetër”, konkretisht për <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - e-mail provider portability directory</a> (~97K euro) dhe <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K euro) që sollën mbulim më të mirë për përdorues me shumë
llogari, përmirësim të gjërave për kontakte me kod QR dhe grupe, si dhe mjaft
përmirësime në punën në rrjet për krejt platformat.</p>
</li>
<li>
<p><a href="https://nlnet.nl/">Fondacioni NLnet</a> dhuroi 46K euro gjatë 2019/2020 për
plotësimin e <em>Rust/Python bindings</em> dhe për t’i dhënë udhë një ekosistemi
Chat-bot.</p>
</li>
<li>
<p><a href="https://opentechfund.org">Open Technology Fund</a> na dha grantin e parë
@@ -1681,36 +1696,10 @@ versione Delta/iOS, për të shndërruar bibliotekën tonë bazë në Rust, si d
për të sjellë veçori të reja për krejt platformat.</p>
</li>
<li>
<p>The <a href="https://nlnet.nl/">NLnet foundation</a> granted in 2019/2020 EUR 46K for
completing Rust/Python bindings and instigating a Chat-bot eco-system.</p>
</li>
<li>
<p>In 2021 we received further EU funding for two Next-Generation-Internet
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
</li>
<li>
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
This funding supported our long-running goals to make Delta Chat more usable
and compatible with a wide range of email servers world-wide, and more resilient and secure
in places often affected by internet censorship and shutdowns.</p>
</li>
<li>
<p>2023-2024 we successfully completed the OTF-funded
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail project</a>,
allowing us to introduce guaranteed encryption,
creating a <a href="https://delta.chat/chatmail">chatmail server network</a>
and providing “instant onboarding” in all apps released from April 2024 on.</p>
</li>
<li>
<p>In 2023 and 2024 we got accepted in the Next Generation Internet (NGI)
program for our work in <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>,
along with collaboration partners working on
<a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>,
<a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>,
<a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> and
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>.
All of these projects are partially completed or to be completed in early 2025.</p>
<p>Projekti <a href="https://nextleap.eu">NEXTLEAP</a> i BE-së financoi kërkimin
për dhe sendërtimin e grupeve të verifikuara dhe protokolleve të
ujdisjes së kontakteve më 2017-n dhe 2018-n dhe ndihmoi gjithashtu
të integrohet Fshehtëzim Skaj-më-Skaj përmes <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>Ndonjëherë marrim dhurime unike nga individë privatë.
+25 -33
View File
@@ -140,17 +140,10 @@
<ul>
<li>
<p>If you are <strong>face to face</strong> with your friend or family,
tap the <strong>QR Code</strong> icon <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" />
on the main screen.<br />
Ask your chat partner to <strong>scan</strong> the QR image
with their Delta Chat app.</p>
<p>Якщо ви перебуваєте <strong>віч-на-віч</strong> зі своїм другом або родиною, торкніться піктограми <strong>QR-код</strong> на головному екрані <img style="vertical-align:middle; height:1.3em; margin:1px" src="../qr-icon.png" /> на головному екрані. Попросіть вашого партнера по чату <strong>сканувати</strong> QR-зображення за допомогою їхнього застосунку Delta Chat.</p>
</li>
<li>
<p>For a <strong>remote</strong> contact setup,
from the same screen,
click “Copy” or “Share” and send the <strong>invite link</strong>
through another private chat.</p>
<p>Для <strong>віддаленого</strong> налаштування контакту, на тому ж самому екрані, натисніть “Копіювати” або “Поділитися” і відправте <strong>запрошувальне посилання</strong> через інший приватний чат.</p>
</li>
</ul>
@@ -1095,6 +1088,10 @@ to send anonymous usage statistics.</p>
<li>
<p><a href="https://autocrypt.org">Autocrypt</a> використовується для автоматичного встановлення наскрізного шифрування між контактами і всіма учасниками групового чату.</p>
</li>
<li>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption and forward secrecy.</p>
</li>
<li>
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">Поширення контакту в чаті</a> дозволяє отримувачам використовувати наскрізне шифрування з контактом.</p>
</li>
@@ -1298,11 +1295,13 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>Ні, поки ще ні.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat наразі не підтримує ідеальну пряму секретність (Perfect Forward Secrecy, PFS). Це означає, що якщо ваш приватний ключ для розшифрування буде скомпрометовано, а хтось заздалегідь зібрав ваші повідомлення під час передачі, він зможе розшифрувати та прочитати їх, використовуючи зламаний ключ. Зверніть увагу, що пряма секретність підвищує рівень безпеки лише в тому разі, якщо ви видаляєте повідомлення. Інакше, якщо хтось отримує доступ до ваших ключів розшифрування, він зазвичай також має доступ до всіх ваших невидалених повідомлень і навіть не потребує розшифровувати заздалегідь перехоплені дані.</p>
<p>Ми розробили підхід Forward Secrecy, який витримав початкову експертизу від деяких криптографів та експертів з реалізації але чекає на більш офіційний звіт щоб переконатися, що він надійно працює в об’єднаних системах обміну повідомленнями та при використанні декількох пристроїв, перш ніж його можна буде реалізувати в <a href="https://github.com/chatmail/core">ядрі чату</a>, що зробить його доступним у всіх <a href="https://chatmail.at/clients">клієнтах чату</a>.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will provide reliable deletion (forward secrecy) through automatic key rotation.
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="pqc">
@@ -1312,9 +1311,13 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>Ні, поки ще ні.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat використовує бібліотеку Rust OpenPGP <a href="https://github.com/rpgp/rpgp">rPGP</a> яка підтримує останню версію <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>. Ми плануємо додати підтримку PQC у <a href="https://github.com/chatmail/core">chatmail core</a> після того, як проект буде завершено у IETF у співпраці з іншими розробниками OpenPGP.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption to protect against quantum computer attacks.
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="як-я-можу-вручну-перевірити-інформацію-про-шифрування">
@@ -1445,7 +1448,14 @@ Google Play Store, F-Droid, Huawei App Gallery, iOS and macOS App Store, Microso
<ul>
<li>
<p>Проект ЄС <a href="https://nextleap.eu">NEXTLEAP</a> фінансував дослідження та впровадження верифікованих груп і протоколів встановлення контактів у 2017 та 2018 роках, а також допоміг інтегрувати наскрізне шифрування через <a href="https://autocrypt.org">Autocrypt</a>.</p>
<p>У 2023 та 2024 роках нас прийняли до програми Next Generation Internet (NGI) за нашу роботу над <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>, а також у співпраці з партнерами, які працюють над <a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>, <a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>, <a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> та <a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>. Усі ці проєкти частково завершені або будуть завершені на початку 2025 року.</p>
</li>
<li>
<p>У 2021 році ми отримали подальше фінансування від ЄС на дві пропозиції щодо Інтернету наступного покоління а саме на <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - каталог перенесення провайдерів електронної пошти</a> (~97 тис. євро) та <a href="https://nlnet.nl/project/EmailPorting/">AEAP - перенесення адрес електронної пошти</a> (~90 тис. євро), що дозволило нам покращити багатопрофільну підтримку, вдосконалити налаштування контактів та груп за допомогою QR-коду та багато інших мережевих покращень на всіх платформах.</p>
</li>
<li>
<p>Фонд <a href="https://nlnet.nl/">NLnet</a> виділив у 2019/2020 роках 46 тисяч євро на
завершення прив’язок Rust/Python та запуск екосистеми чат-ботів.</p>
</li>
<li>
<p><a href="https://opentechfund.org">Open Technology Fund</a> надав нам два гранти.
@@ -1458,25 +1468,7 @@ Google Play Store, F-Droid, Huawei App Gallery, iOS and macOS App Store, Microso
і додати нові функції для всіх платформ.</p>
</li>
<li>
<p>Фонд <a href="https://nlnet.nl/">NLnet</a> виділив у 2019/2020 роках 46 тисяч євро на
завершення прив’язок Rust/Python та запуск екосистеми чат-ботів.</p>
</li>
<li>
<p>In 2021 we received further EU funding for two Next-Generation-Internet
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
</li>
<li>
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
This funding supported our long-running goals to make Delta Chat more usable
and compatible with a wide range of email servers world-wide, and more resilient and secure
in places often affected by internet censorship and shutdowns.</p>
</li>
<li>
<p>У 2023-2024 роках ми успішно завершили проєкт <a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">Secure Chatmail</a>, що фінансувався OTF, що дозволило нам запровадити гарантоване шифрування, створити <a href="https://delta.chat/chatmail">мережу серверів chatmail</a> та забезпечити “миттєву реєстрацію” у всіх застосунках, випущених з квітня 2024 року.</p>
</li>
<li>
<p>У 2023 та 2024 роках нас прийняли до програми Next Generation Internet (NGI) за нашу роботу над <a href="https://nlnet.nl/project/WebXDC-Push/">webxdc PUSH</a>, а також у співпраці з партнерами, які працюють над <a href="https://nlnet.nl/project/Webxdc-Evolve/">webxdc evolve</a>, <a href="https://nlnet.nl/project/WebXDC-XMPP/">webxdc XMPP</a>, <a href="https://nlnet.nl/project/DeltaTouch/">DeltaTouch</a> та <a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a>. Усі ці проєкти частково завершені або будуть завершені на початку 2025 року.</p>
<p>Проект ЄС <a href="https://nextleap.eu">NEXTLEAP</a> фінансував дослідження та впровадження верифікованих груп і протоколів встановлення контактів у 2017 та 2018 роках, а також допоміг інтегрувати наскрізне шифрування через <a href="https://autocrypt.org">Autocrypt</a>.</p>
</li>
<li>
<p>Іноді ми отримуємо одноразові пожертви від приватних осіб. Наприклад, у 2021 році щедра приватна особа перерахував нам 4 тис. євро з повідомленням «так тримати!». 💜 Ми використовуємо такі пожертви для фінансування зборів на розвиток або для тимчасових витрат, які важко передбачити або відшкодувати за рахунок грантів державного фінансування. Отримання більшої кількості пожертв також допомагає нам стати більш незалежними та довгостроково життєздатними як спільнота контриб’юторів.</p>
+28 -43
View File
@@ -1161,6 +1161,10 @@ weekly statistics will be automatically sent to a bot.</p>
<li>
<p><a href="https://autocrypt.org">Autocrypt</a> is used for automatically
用于在联系人和群聊的所有成员之间自动建立端到端加密。</p>
</li>
<li>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption and forward secrecy.</p>
</li>
<li>
<p><a href="https://github.com/chatmail/core/blob/main/spec.md#attaching-a-contact-to-a-message">将联系人分享到聊天中
@@ -1342,12 +1346,10 @@ Instead, all group metadata is end-to-end encrypted and stored on end-user devic
<p>Servers can therefore only see:</p>
<ul>
<li>the sender and receiver addresses</li>
<li>and the message size.</li>
<li>Sender and receiver addresses, randomly generated by default</li>
<li>Message size</li>
</ul>
<p>By default, the addresses are randomly generated.</p>
<p>All other message, contact and group metadata resides in the end-to-end encrypted part of messages.</p>
<h3 id="device-seizure">
@@ -1423,7 +1425,7 @@ but an implementation has not been agreed as a priority yet.</p>
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat today doesn’t support Perfect Forward Secrecy (PFS).
This means that if your private decryption key is leaked,
@@ -1434,12 +1436,9 @@ Otherwise, someone obtaining your decryption keys
is typically also able to get all your non-deleted messages
and doesn’t even need to decrypt any previously collected messages.</p>
<p>We designed a Forward Secrecy approach that withstood
initial examination from some cryptographers and implementation experts
but is pending a more formal write up
to ascertain it reliably works in federated messaging and with multi-device usage,
before it could be implemented in <a href="https://github.com/chatmail/core">chatmail core</a>,
which would make it available in all <a href="https://chatmail.at/clients">chatmail clients</a>.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will provide reliable deletion (forward secrecy) through automatic key rotation.
This approach is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="pqc">
@@ -1449,12 +1448,13 @@ which would make it available in all <a href="https://chatmail.at/clients">chatm
</h3>
<p>No, not yet.</p>
<p>Not yet, but it’s coming with <a href="https://autocrypt2.org">Autocrypt v2</a>.</p>
<p>Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
We aim to add PQC support in <a href="https://github.com/chatmail/core">chatmail core</a> after the draft is finalized at the IETF
in collaboration with other OpenPGP implementers.</p>
<p><a href="https://autocrypt2.org">Autocrypt v2</a>, scheduled for full implementation in 2026,
will bring post-quantum resistant encryption to protect against quantum computer attacks.
Delta Chat uses the Rust OpenPGP library <a href="https://github.com/rpgp/rpgp">rPGP</a>
which supports the latest <a href="https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/">IETF Post-Quantum-Cryptography OpenPGP draft</a>.
The implementation is specified in the <a href="https://datatracker.ietf.org/doc/draft-autocrypt-openpgp-v2-cert/">Autocrypt v2 OpenPGP Certificates</a> draft.</p>
<h3 id="how-can-i-manually-check-encryption-information">
@@ -1623,33 +1623,6 @@ Google Play Store, F-Droid, Huawei App Gallery, iOS and macOS App Store, Microso
按时间顺序排列:</p>
<ul>
<li>
<p><a href="https://nextleap.eu">NEXTLEAP</a>欧盟项目资助了以下研究和实施工作:在 2017 年和 2018 年实施的验证组和设置联系协议和通过 <a href="https://autocrypt.org">Autocrypt</a>整合了端到端加密。</p>
</li>
<li>
<p>在<a href="https://opentechfund.org">开放技术基金</a> 2018/2019 年提供的第一笔赠款(约 20 万美元)期间,我们显著改善了安卓应用,发布了第一个桌面测试版,并根据人权方面的用户体验研究进行了功能开发,请参阅我们的结论<a href="https://delta.chat/en/2019-07-19-uxreport">《需求发现与用户体验报告》</a>。2019/2020 年的第二笔赠款(约 30 万美元)对发布 Delta/iOS 版本,将核心库转换到 Rust ,以及为所有平台开发新功能提供了帮助。</p>
</li>
<li>
<p><a href="https://nlnet.nl/">NLnet 基金会</a> 2019/2020 年拨款 4.6 万欧元,用于完成 Rust/Python 绑定并建立聊天机器人生态系统。</p>
</li>
<li>
<p>In 2021 we received further EU funding for two Next-Generation-Internet
proposals, namely for <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - email provider portability directory</a> (~97K EUR) and <a href="https://nlnet.nl/project/EmailPorting/">AEAP - email address porting</a> (~90K EUR) which resulted in better multi-profile support, improved QR-code contact and group setups and many networking improvements on all platforms.</p>
</li>
<li>
<p>From End 2021 till March 2023 we received <em>Internet Freedom</em> funding (500K USD) from the
U.S. Bureau of Democracy, Human Rights and Labor (DRL).
This funding supported our long-running goals to make Delta Chat more usable
and compatible with a wide range of email servers world-wide, and more resilient and secure
in places often affected by internet censorship and shutdowns.</p>
</li>
<li>
<p>2023-2024 年,我们成功完成了 OTF 资助的
<a href="https://www.opentech.fund/projects-we-support/supported-projects/secure-chat-mail-with-delta-chat/">安全 Chatmail 项目</a>,
使我们能够引入保证的加密,
创建一个 <a href="https://delta.chat/chatmail">Chatmail 服务器网络</a>,
并在 2024 年 4 月起发布的所有应用中提供“即时入职”。</p>
</li>
<li>
<p>在 2023 年和 2024 年,我们的 <a href="https://nlnet.nl/project/WebXDC-Push/">WebXDC PUSH</a> 工作已在下一代互联网 (NGI) 中获得认可,
并与致力于
@@ -1659,6 +1632,18 @@ in places often affected by internet censorship and shutdowns.</p>
<a href="https://nlnet.nl/project/DeltaTauri/">DeltaTauri</a> 的合作伙伴合作。
所有这些项目都已部分完成或将在 2025 年初完成。</p>
</li>
<li>
<p>在 2021 年,我们从两项下一代互联网提案收到了欧盟的进一步资助,即 <a href="https://dapsi.ngi.eu/hall-of-fame/eppd/">EPPD - 电子邮件提供商可移植性目录</a>(约 9.7 万欧元)和 <a href="https://nlnet.nl/project/EmailPorting/">AEAP - 电子邮件地址移植</a>(约 9 万欧元)。这带来了更好的多账户支持,改进的二维码联系人和群组设置,和所有平台上的多处网络改进。</p>
</li>
<li>
<p><a href="https://nlnet.nl/">NLnet 基金会</a> 2019/2020 年拨款 4.6 万欧元,用于完成 Rust/Python 绑定并建立聊天机器人生态系统。</p>
</li>
<li>
<p>在<a href="https://opentechfund.org">开放技术基金</a> 2018/2019 年提供的第一笔赠款(约 20 万美元)期间,我们显著改善了安卓应用,发布了第一个桌面测试版,并根据人权方面的用户体验研究进行了功能开发,请参阅我们的结论<a href="https://delta.chat/en/2019-07-19-uxreport">《需求发现与用户体验报告》</a>。2019/2020 年的第二笔赠款(约 30 万美元)对发布 Delta/iOS 版本,将核心库转换到 Rust ,以及为所有平台开发新功能提供了帮助。</p>
</li>
<li>
<p><a href="https://nextleap.eu">NEXTLEAP</a>欧盟项目资助了以下研究和实施工作:在 2017 年和 2018 年实施的验证组和设置联系协议和通过 <a href="https://autocrypt.org">Autocrypt</a>整合了端到端加密。</p>
</li>
<li>
<p>我们有时会收到个人的一次性捐款。 例如,2021 年,一位慷慨的人士以 “继续保持良好的发展态势!”为主题通过银行向我们捐赠了4千欧元💜。 我们用这些钱来资助发展活动或支付不容易预测或从公共基金中报销的临时费用。收到更多的捐款也有助于我们作为一个贡献者社区变得更加独立和长期可持续。</p>
+72 -11
View File
@@ -53,6 +53,11 @@ public class Rpc {
return transport.callForResult(new TypeReference<Event>(){}, "get_next_event");
}
/** Waits for at least one event and return a batch of events. */
public java.util.List<Event> getNextEventBatch() throws RpcException {
return transport.callForResult(new TypeReference<java.util.List<Event>>(){}, "get_next_event_batch");
}
public Integer addAccount() throws RpcException {
return transport.callForResult(new TypeReference<Integer>(){}, "add_account");
}
@@ -211,11 +216,11 @@ public class Rpc {
}
/**
* Set configuration values from a QR code. (technically from the URI that is stored in the qrcode)
* Before this function is called, `checkQr()` should confirm the type of the
* QR code is `account` or `webrtcInstance`.
* Set configuration values from a QR code (technically from the URI stored in it).
* Before this function is called, `check_qr()` should be used to get the QR code type.
* <p>
* Internally, the function will call dc_set_config() with the appropriate keys,
* "DCACCOUNT:" and "DCLOGIN:" QR codes configure the account, but I/O mustn't be started for
* such QR codes, consider using [`Self::add_transport_from_qr`] which also restarts I/O.
*/
public void setConfigFromQr(Integer accountId, String qrContent) throws RpcException {
transport.call("set_config_from_qr", mapper.valueToTree(accountId), mapper.valueToTree(qrContent));
@@ -234,6 +239,11 @@ public class Rpc {
return transport.callForResult(new TypeReference<java.util.Map<String, String>>(){}, "batch_get_config", mapper.valueToTree(accountId), mapper.valueToTree(keys));
}
/** Returns all `ui.*` config keys that were set by the UI. */
public java.util.List<String> getAllUiConfigKeys(Integer accountId) throws RpcException {
return transport.callForResult(new TypeReference<java.util.List<String>>(){}, "get_all_ui_config_keys", mapper.valueToTree(accountId));
}
public void setStockStrings(java.util.Map<String, String> strings) throws RpcException {
transport.call("set_stock_strings", mapper.valueToTree(strings));
}
@@ -449,11 +459,11 @@ public class Rpc {
* Delete a chat.
* <p>
* Messages are deleted from the device and the chat database entry is deleted.
* After that, the event #DC_EVENT_MSGS_CHANGED is posted.
* After that, a `MsgsChanged` event is emitted.
* Messages are deleted from the server in background.
* <p>
* Things that are _not done_ implicitly:
* <p>
* - Messages are **not deleted from the server**.
* - The chat or the contact is **not blocked**, so new messages from the user/the group may appear as a contact request
* and the user may create the chat again.
* - **Groups are not left** - this would
@@ -677,7 +687,8 @@ public class Rpc {
* Set group name.
* <p>
* If the group is already _promoted_ (any message was sent to the group),
* all group members are informed by a special status message that is sent automatically by this function.
* or if this is a brodacast channel,
* all members are informed by a special status message that is sent automatically by this function.
* <p>
* Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
*/
@@ -685,11 +696,37 @@ public class Rpc {
transport.call("set_chat_name", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(newName));
}
/**
* Set group or broadcast channel description.
* <p>
* If the group is already _promoted_ (any message was sent to the group),
* or if this is a brodacast channel,
* all members are informed by a special status message that is sent automatically by this function.
* <p>
* Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
* <p>
* See also [`Self::get_chat_description`] / `getChatDescription()`.
*/
public void setChatDescription(Integer accountId, Integer chatId, String description) throws RpcException {
transport.call("set_chat_description", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(description));
}
/**
* Load the chat description from the database.
* <p>
* UIs show this in the profile page of the chat,
* it is settable by [`Self::set_chat_description`] / `setChatDescription()`.
*/
public String getChatDescription(Integer accountId, Integer chatId) throws RpcException {
return transport.callForResult(new TypeReference<String>(){}, "get_chat_description", mapper.valueToTree(accountId), mapper.valueToTree(chatId));
}
/**
* Set group profile image.
* <p>
* If the group is already _promoted_ (any message was sent to the group),
* all group members are informed by a special status message that is sent automatically by this function.
* or if this is a brodacast channel,
* all members are informed by a special status message that is sent automatically by this function.
* <p>
* Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
* <p>
@@ -733,11 +770,26 @@ public class Rpc {
return transport.callForResult(new TypeReference<Integer>(){}, "add_device_message", mapper.valueToTree(accountId), mapper.valueToTree(label), mapper.valueToTree(msg));
}
/**
* Mark all messages in all chats as _noticed_.
* Skips messages from blocked contacts, but does not skip messages in muted chats.
* <p>
* _Noticed_ messages are no longer _fresh_ and do not count as being unseen
* but are still waiting for being marked as "seen" using markseen_msgs()
* (read receipts aren't sent for noticed messages).
* <p>
* Calling this function usually results in the event #DC_EVENT_MSGS_NOTICED.
* See also markseen_msgs().
*/
public void marknoticedAllChats(Integer accountId) throws RpcException {
transport.call("marknoticed_all_chats", mapper.valueToTree(accountId));
}
/**
* Mark all messages in a chat as _noticed_.
* _Noticed_ messages are no longer _fresh_ and do not count as being unseen
* but are still waiting for being marked as "seen" using markseen_msgs()
* (IMAP/MDNs is not done for noticed messages).
* (read receipts aren't sent for noticed messages).
* <p>
* Calling this function usually results in the event #DC_EVENT_MSGS_NOTICED.
* See also markseen_msgs().
@@ -893,6 +945,15 @@ public class Rpc {
return transport.callForResult(new TypeReference<MessageInfo>(){}, "get_message_info_object", mapper.valueToTree(accountId), mapper.valueToTree(messageId));
}
/**
* Returns count of read receipts on message.
* <p>
* This view count is meant as a feedback measure for the channel owner only.
*/
public Integer getMessageReadReceiptCount(Integer accountId, Integer messageId) throws RpcException {
return transport.callForResult(new TypeReference<Integer>(){}, "get_message_read_receipt_count", mapper.valueToTree(accountId), mapper.valueToTree(messageId));
}
/** Returns contacts that sent read receipts and the time of reading. */
public java.util.List<MessageReadReceipt> getMessageReadReceipts(Integer accountId, Integer messageId) throws RpcException {
return transport.callForResult(new TypeReference<java.util.List<MessageReadReceipt>>(){}, "get_message_read_receipts", mapper.valueToTree(accountId), mapper.valueToTree(messageId));
@@ -1286,8 +1347,8 @@ public class Rpc {
}
/** Starts an outgoing call. */
public Integer placeOutgoingCall(Integer accountId, Integer chatId, String placeCallInfo) throws RpcException {
return transport.callForResult(new TypeReference<Integer>(){}, "place_outgoing_call", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(placeCallInfo));
public Integer placeOutgoingCall(Integer accountId, Integer chatId, String placeCallInfo, Boolean hasVideo) throws RpcException {
return transport.callForResult(new TypeReference<Integer>(){}, "place_outgoing_call", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(placeCallInfo), mapper.valueToTree(hasVideo));
}
/** Accepts an incoming call. */
@@ -2,7 +2,7 @@
package chat.delta.rpc.types;
/**
* cheaper version of fullchat, omits: - contacts - contact_ids - fresh_message_counter - ephemeral_timer - self_in_group - was_seen_recently - can_send
* cheaper version of fullchat, omits: - contact_ids - fresh_message_counter - ephemeral_timer - self_in_group - was_seen_recently - can_send
* <p>
* used when you only need the basic metadata of a chat like type, name, profile picture
*/
@@ -2,7 +2,7 @@
package chat.delta.rpc.types;
public class CallInfo {
/** True if SDP offer has a video. */
/** True if the call is started as a video call. */
public Boolean hasVideo;
/**
* SDP offer.
@@ -38,7 +38,7 @@ public class Contact {
* <p>
* UI should display the information in the contact's profile as follows:
* <p>
* - If `verifierId` != 0, display text "Introduced by ..." with the name and address of the contact formatted by `name_and_addr`/`nameAndAddr`. Prefix the text by a green checkmark.
* - If `verifierId` != 0, display text "Introduced by ..." with the name of the contact. Prefix the text by a green checkmark.
* <p>
* - If `verifierId` == 0 and `isVerified` != 0, display "Introduced" prefixed by a green checkmark.
* <p>
@@ -412,7 +412,9 @@ public abstract class EventType {
/**
* One or more transports has changed.
* <p>
* This event is used for tests to detect when transport synchronization messages arrives. UIs don't need to use it, it is unlikely that user modifies transports on multiple devices simultaneously.
* UI should update the list.
* <p>
* This event is emitted when transport synchronization messages arrives, but not when the UI modifies the transport list by itself.
*/
public static class TransportsModified extends EventType {
}
@@ -7,7 +7,6 @@ public class FullChat {
public ChatType chatType;
public String color;
public java.util.List<Integer> contactIds;
public java.util.List<Contact> contacts;
public Integer ephemeralTimer;
public Integer freshMessageCounter;
public Integer id;
@@ -12,6 +12,7 @@ public class Message {
public String error;
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
public String file;
/** The size of the file in bytes, if applicable. If message is a pre-message, then this is the size of the file to be downloaded. */
public Integer fileBytes;
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
public String fileMime;
@@ -22,6 +22,7 @@ public abstract class MessageLoadResult {
public String error;
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
public String file;
/** The size of the file in bytes, if applicable. If message is a pre-message, then this is the size of the file to be downloaded. */
public Integer fileBytes;
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET)
public String fileMime;
@@ -4,6 +4,7 @@ package chat.delta.rpc.types;
public enum SystemMessageType {
Unknown,
GroupNameChanged,
GroupDescriptionChanged,
GroupImageChanged,
MemberAddedToGroup,
MemberRemovedFromGroup,
@@ -2,63 +2,92 @@ package com.b44t.messenger;
public class DcAccounts {
public DcAccounts(String dir) {
accountsCPtr = createAccountsCPtr(dir);
public DcAccounts(String dir, DcEventChannel channel) {
accountsCPtr = createAccountsCPtr(dir, channel);
if (accountsCPtr == 0) throw new RuntimeException("createAccountsCPtr() returned null pointer");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
unref();
}
public void unref() {
if (accountsCPtr != 0) {
unrefAccountsCPtr();
accountsCPtr = 0;
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
unref();
public DcEventEmitter getEventEmitter() {
return new DcEventEmitter(getEventEmitterCPtr());
}
public DcJsonrpcInstance getJsonrpcInstance() {
return new DcJsonrpcInstance(getJsonrpcInstanceCPtr());
}
public void startIo() {
for (int accountId : getAll()) {
DcContext acc = getAccount(accountId);
if (acc.isEnabled()) {
acc.startIo();
}
}
}
;
public void unref() {
if (accountsCPtr != 0) {
unrefAccountsCPtr();
accountsCPtr = 0;
}
}
public DcEventEmitter getEventEmitter () { return new DcEventEmitter(getEventEmitterCPtr()); }
public DcJsonrpcInstance getJsonrpcInstance () { return new DcJsonrpcInstance(getJsonrpcInstanceCPtr()); }
public void startIo () {
for (int accountId : getAll()) {
DcContext acc = getAccount(accountId);
if (acc.isEnabled()) {
acc.startIo();
}
}
};
public native void startIo2 ();
public native void stopIo ();
public native void maybeNetwork ();
public native void setPushDeviceToken (String token);
public native boolean backgroundFetch (int timeoutSeconds);
public native void stopBackgroundFetch ();
public native int migrateAccount (String dbfile);
public native boolean removeAccount (int accountId);
public native int[] getAll ();
public DcContext getAccount (int accountId) { return new DcContext(getAccountCPtr(accountId)); }
public DcContext getSelectedAccount () { return new DcContext(getSelectedAccountCPtr()); }
public native boolean selectAccount (int accountId);
// working with raw c-data
private long accountsCPtr; // CAVE: the name is referenced in the JNI
private native long createAccountsCPtr (String dir);
private native void unrefAccountsCPtr ();
private native long getEventEmitterCPtr ();
private native long getJsonrpcInstanceCPtr ();
private native long getAccountCPtr (int accountId);
private native long getSelectedAccountCPtr ();
public boolean isAllChatmail() {
for (int accountId : getAll()) {
DcContext dcContext = getAccount(accountId);
if (!dcContext.isChatmail()) {
return false;
}
}
return true;
public native void startIo2();
public native void stopIo();
public native void maybeNetwork();
public native void setPushDeviceToken(String token);
public native boolean backgroundFetch(int timeoutSeconds);
public native void stopBackgroundFetch();
public native int migrateAccount(String dbfile);
public native boolean removeAccount(int accountId);
public native int[] getAll();
public DcContext getAccount(int accountId) {
return new DcContext(getAccountCPtr(accountId));
}
public DcContext getSelectedAccount() {
return new DcContext(getSelectedAccountCPtr());
}
public native boolean selectAccount(int accountId);
// working with raw c-data
private long accountsCPtr; // CAVE: the name is referenced in the JNI
private native long createAccountsCPtr(String dir, DcEventChannel channel);
private native void unrefAccountsCPtr();
private native long getEventEmitterCPtr();
private native long getJsonrpcInstanceCPtr();
private native long getAccountCPtr(int accountId);
private native long getSelectedAccountCPtr();
public boolean isAllChatmail() {
for (int accountId : getAll()) {
DcContext dcContext = getAccount(accountId);
if (!dcContext.isChatmail()) {
return false;
}
}
return true;
}
}
@@ -2,31 +2,35 @@ package com.b44t.messenger;
public class DcBackupProvider {
public DcBackupProvider(long backupProviderCPtr) {
this.backupProviderCPtr = backupProviderCPtr;
public DcBackupProvider(long backupProviderCPtr) {
this.backupProviderCPtr = backupProviderCPtr;
}
public boolean isOk() {
return backupProviderCPtr != 0;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
unref();
}
public void unref() {
if (backupProviderCPtr != 0) {
unrefBackupProviderCPtr();
backupProviderCPtr = 0;
}
}
public boolean isOk() {
return backupProviderCPtr != 0;
}
public native String getQr();
@Override protected void finalize() throws Throwable {
super.finalize();
unref();
}
public native String getQrSvg();
public void unref() {
if (backupProviderCPtr != 0) {
unrefBackupProviderCPtr();
backupProviderCPtr = 0;
}
}
public native void waitForReceiver();
public native String getQr ();
public native String getQrSvg ();
public native void waitForReceiver ();
// working with raw c-data
private long backupProviderCPtr; // CAVE: the name is referenced in the JNI
// working with raw c-data
private long backupProviderCPtr; // CAVE: the name is referenced in the JNI
private native void unrefBackupProviderCPtr();
private native void unrefBackupProviderCPtr();
}
+92 -59
View File
@@ -1,76 +1,109 @@
package com.b44t.messenger;
import org.thoughtcrime.securesms.util.Util;
public class DcChat {
public static final int DC_CHAT_TYPE_UNDEFINED = 0;
public static final int DC_CHAT_TYPE_SINGLE = 100;
public static final int DC_CHAT_TYPE_GROUP = 120;
public static final int DC_CHAT_TYPE_MAILINGLIST = 140;
public static final int DC_CHAT_TYPE_OUT_BROADCAST = 160;
public static final int DC_CHAT_TYPE_IN_BROADCAST = 165;
public static final int DC_CHAT_TYPE_UNDEFINED = 0;
public static final int DC_CHAT_TYPE_SINGLE = 100;
public static final int DC_CHAT_TYPE_GROUP = 120;
public static final int DC_CHAT_TYPE_MAILINGLIST = 140;
public static final int DC_CHAT_TYPE_OUT_BROADCAST = 160;
public static final int DC_CHAT_TYPE_IN_BROADCAST = 165;
public static final int DC_CHAT_NO_CHAT = 0;
public final static int DC_CHAT_ID_ARCHIVED_LINK = 6;
public final static int DC_CHAT_ID_ALLDONE_HINT = 7;
public final static int DC_CHAT_ID_LAST_SPECIAL = 9;
public static final int DC_CHAT_NO_CHAT = 0;
public static final int DC_CHAT_ID_ARCHIVED_LINK = 6;
public static final int DC_CHAT_ID_ALLDONE_HINT = 7;
public static final int DC_CHAT_ID_LAST_SPECIAL = 9;
public final static int DC_CHAT_VISIBILITY_NORMAL = 0;
public final static int DC_CHAT_VISIBILITY_ARCHIVED = 1;
public final static int DC_CHAT_VISIBILITY_PINNED = 2;
public static final int DC_CHAT_VISIBILITY_NORMAL = 0;
public static final int DC_CHAT_VISIBILITY_ARCHIVED = 1;
public static final int DC_CHAT_VISIBILITY_PINNED = 2;
private int accountId;
private int accountId;
public DcChat(int accountId, long chatCPtr) {
this.accountId = accountId;
this.chatCPtr = chatCPtr;
public DcChat(int accountId, long chatCPtr) {
this.accountId = accountId;
this.chatCPtr = chatCPtr;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
unrefChatCPtr();
chatCPtr = 0;
}
public int getAccountId() {
return accountId;
}
public native int getId();
public native int getType();
public native int getVisibility();
public native String getName();
public native String getMailinglistAddr();
public native String getProfileImage();
public native int getColor();
public native boolean isEncrypted();
public native boolean isUnpromoted();
public native boolean isSelfTalk();
public native boolean isDeviceTalk();
public native boolean canSend();
public native boolean isSendingLocations();
public native boolean isMuted();
public native boolean isContactRequest();
// aliases and higher-level tools
public boolean isMultiUser() {
int type = getType();
return type != DC_CHAT_TYPE_SINGLE;
}
public boolean shallLeaveBeforeDelete(DcContext dcContext) {
if (isInBroadcast()) {
final int[] members = dcContext.getChatContacts(getId());
return Util.contains(members, DcContact.DC_CONTACT_ID_SELF);
} else if (isMultiUser() && isEncrypted() && canSend() && !isOutBroadcast()) {
return true;
}
return false;
}
@Override protected void finalize() throws Throwable {
super.finalize();
unrefChatCPtr();
chatCPtr = 0;
}
public boolean isMailingList() {
return getType() == DC_CHAT_TYPE_MAILINGLIST;
}
public int getAccountId () { return accountId; }
public native int getId ();
public native int getType ();
public native int getVisibility ();
public native String getName ();
public native String getMailinglistAddr();
public native String getProfileImage ();
public native int getColor ();
public native boolean isEncrypted ();
public native boolean isUnpromoted ();
public native boolean isSelfTalk ();
public native boolean isDeviceTalk ();
public native boolean canSend ();
public native boolean isSendingLocations();
public native boolean isMuted ();
public native boolean isContactRequest ();
public boolean isInBroadcast() {
return getType() == DC_CHAT_TYPE_IN_BROADCAST;
}
public boolean isOutBroadcast() {
return getType() == DC_CHAT_TYPE_OUT_BROADCAST;
}
// aliases and higher-level tools
// working with raw c-data
public boolean isMultiUser() {
int type = getType();
return type != DC_CHAT_TYPE_SINGLE;
}
private long chatCPtr; // CAVE: the name is referenced in the JNI
public boolean isMailingList() {
return getType() == DC_CHAT_TYPE_MAILINGLIST;
}
public boolean isInBroadcast() {
return getType() == DC_CHAT_TYPE_IN_BROADCAST;
}
public boolean isOutBroadcast() {
return getType() == DC_CHAT_TYPE_OUT_BROADCAST;
}
// working with raw c-data
private long chatCPtr; // CAVE: the name is referenced in the JNI
private native void unrefChatCPtr();
public long getChatCPtr () { return chatCPtr; }
private native void unrefChatCPtr();
public long getChatCPtr() {
return chatCPtr;
}
}
@@ -2,45 +2,64 @@ package com.b44t.messenger;
public class DcChatlist {
private int accountId;
private int accountId;
public DcChatlist(int accountId, long chatlistCPtr) {
this.accountId = accountId;
this.chatlistCPtr = chatlistCPtr;
}
public DcChatlist(int accountId, long chatlistCPtr) {
this.accountId = accountId;
this.chatlistCPtr = chatlistCPtr;
}
@Override protected void finalize() throws Throwable {
super.finalize();
unrefChatlistCPtr();
chatlistCPtr = 0;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
unrefChatlistCPtr();
chatlistCPtr = 0;
}
public int getAccountId() { return accountId; }
public native int getCnt ();
public native int getChatId (int index);
public DcChat getChat (int index) { return new DcChat(accountId, getChatCPtr(index)); }
public native int getMsgId (int index);
public DcMsg getMsg (int index) { return new DcMsg(getMsgCPtr(index)); }
public DcLot getSummary(int index, DcChat chat) { return new DcLot(getSummaryCPtr(index, chat==null? 0 : chat.getChatCPtr())); }
public int getAccountId() {
return accountId;
}
public class Item {
public DcLot summary;
public int msgId;
public int chatId;
}
public native int getCnt();
public Item getItem(int index) {
Item item = new Item();
item.summary = getSummary(index, null);
item.msgId = getMsgId(index);
item.chatId = getChatId(index);
return item;
}
public native int getChatId(int index);
// working with raw c-data
private long chatlistCPtr; // CAVE: the name is referenced in the JNI
private native void unrefChatlistCPtr();
private native long getChatCPtr (int index);
private native long getMsgCPtr (int index);
private native long getSummaryCPtr (int index, long chatCPtr);
public DcChat getChat(int index) {
return new DcChat(accountId, getChatCPtr(index));
}
public native int getMsgId(int index);
public DcMsg getMsg(int index) {
return new DcMsg(getMsgCPtr(index));
}
public DcLot getSummary(int index, DcChat chat) {
return new DcLot(getSummaryCPtr(index, chat == null ? 0 : chat.getChatCPtr()));
}
public class Item {
public DcLot summary;
public int msgId;
public int chatId;
}
public Item getItem(int index) {
Item item = new Item();
item.summary = getSummary(index, null);
item.msgId = getMsgId(index);
item.chatId = getChatId(index);
return item;
}
// working with raw c-data
private long chatlistCPtr; // CAVE: the name is referenced in the JNI
private native void unrefChatlistCPtr();
private native long getChatCPtr(int index);
private native long getMsgCPtr(int index);
private native long getSummaryCPtr(int index, long chatCPtr);
}
+68 -53
View File
@@ -2,67 +2,82 @@ package com.b44t.messenger;
public class DcContact {
public final static int DC_CONTACT_ID_SELF = 1;
public final static int DC_CONTACT_ID_INFO = 2;
public final static int DC_CONTACT_ID_DEVICE = 5;
public final static int DC_CONTACT_ID_LAST_SPECIAL = 9;
public final static int DC_CONTACT_ID_NEW_CLASSIC_CONTACT= -1; // used by the UI, not valid to the core
public final static int DC_CONTACT_ID_NEW_GROUP = -2; // - " -
public final static int DC_CONTACT_ID_ADD_MEMBER = -3; // - " -
public final static int DC_CONTACT_ID_QR_INVITE = -4; // - " -
public final static int DC_CONTACT_ID_NEW_BROADCAST = -5; // - " -
public final static int DC_CONTACT_ID_ADD_ACCOUNT = -6; // - " -
public final static int DC_CONTACT_ID_NEW_UNENCRYPTED_GROUP = -7; // - " -
public static final int DC_CONTACT_ID_SELF = 1;
public static final int DC_CONTACT_ID_INFO = 2;
public static final int DC_CONTACT_ID_DEVICE = 5;
public static final int DC_CONTACT_ID_LAST_SPECIAL = 9;
public static final int DC_CONTACT_ID_NEW_CLASSIC_CONTACT =
-1; // used by the UI, not valid to the core
public static final int DC_CONTACT_ID_NEW_GROUP = -2; // - " -
public static final int DC_CONTACT_ID_ADD_MEMBER = -3; // - " -
public static final int DC_CONTACT_ID_QR_INVITE = -4; // - " -
public static final int DC_CONTACT_ID_NEW_BROADCAST = -5; // - " -
public static final int DC_CONTACT_ID_ADD_ACCOUNT = -6; // - " -
public static final int DC_CONTACT_ID_NEW_UNENCRYPTED_GROUP = -7; // - " -
public DcContact(long contactCPtr) {
this.contactCPtr = contactCPtr;
public DcContact(long contactCPtr) {
this.contactCPtr = contactCPtr;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
unrefContactCPtr();
contactCPtr = 0;
}
@Override
public boolean equals(Object other) {
if (other == null || !(other instanceof DcContact)) {
return false;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
unrefContactCPtr();
contactCPtr = 0;
}
DcContact that = (DcContact) other;
return this.getId() == that.getId();
}
@Override
public int hashCode() {
return this.getId();
}
@Override
public boolean equals(Object other) {
if (other == null || !(other instanceof DcContact)) {
return false;
}
@Override
public String toString() {
return getAddr();
}
DcContact that = (DcContact) other;
return this.getId()==that.getId();
}
public native int getId();
@Override
public int hashCode() {
return this.getId();
}
public native String getName();
@Override
public String toString() {
return getAddr();
}
public native String getAuthName();
public native int getId ();
public native String getName ();
public native String getAuthName ();
public native String getDisplayName ();
public native String getAddr ();
public native String getProfileImage();
public native int getColor ();
public native String getStatus ();
public native long getLastSeen ();
public native boolean wasSeenRecently();
public native boolean isBlocked ();
public native boolean isVerified ();
public native boolean isKeyContact ();
public native int getVerifierId ();
public native boolean isBot ();
public native String getDisplayName();
// working with raw c-data
private long contactCPtr; // CAVE: the name is referenced in the JNI
private native void unrefContactCPtr();
public native String getAddr();
public native String getProfileImage();
public native int getColor();
public native String getStatus();
public native long getLastSeen();
public native boolean wasSeenRecently();
public native boolean isBlocked();
public native boolean isVerified();
public native boolean isKeyContact();
public native int getVerifierId();
public native boolean isBot();
// working with raw c-data
private long contactCPtr; // CAVE: the name is referenced in the JNI
private native void unrefContactCPtr();
}
+376 -249
View File
@@ -2,288 +2,415 @@ package com.b44t.messenger;
public class DcContext {
public final static int DC_EVENT_INFO = 100;
public final static int DC_EVENT_WARNING = 300;
public final static int DC_EVENT_ERROR = 400;
public final static int DC_EVENT_ERROR_SELF_NOT_IN_GROUP = 410;
public final static int DC_EVENT_MSGS_CHANGED = 2000;
public final static int DC_EVENT_REACTIONS_CHANGED = 2001;
public final static int DC_EVENT_INCOMING_REACTION = 2002;
public final static int DC_EVENT_INCOMING_WEBXDC_NOTIFY = 2003;
public final static int DC_EVENT_INCOMING_MSG = 2005;
public final static int DC_EVENT_MSGS_NOTICED = 2008;
public final static int DC_EVENT_MSG_DELIVERED = 2010;
public final static int DC_EVENT_MSG_FAILED = 2012;
public final static int DC_EVENT_MSG_READ = 2015;
public final static int DC_EVENT_CHAT_MODIFIED = 2020;
public final static int DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED = 2021;
public final static int DC_EVENT_CHAT_DELETED = 2023;
public final static int DC_EVENT_CONTACTS_CHANGED = 2030;
public final static int DC_EVENT_LOCATION_CHANGED = 2035;
public final static int DC_EVENT_CONFIGURE_PROGRESS = 2041;
public final static int DC_EVENT_IMEX_PROGRESS = 2051;
public final static int DC_EVENT_IMEX_FILE_WRITTEN = 2052;
public final static int DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060;
public final static int DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061;
public final static int DC_EVENT_CONNECTIVITY_CHANGED = 2100;
public final static int DC_EVENT_SELFAVATAR_CHANGED = 2110;
public final static int DC_EVENT_WEBXDC_STATUS_UPDATE = 2120;
public final static int DC_EVENT_WEBXDC_INSTANCE_DELETED = 2121;
public final static int DC_EVENT_WEBXDC_REALTIME_DATA = 2150;
public final static int DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE = 2200;
public final static int DC_EVENT_INCOMING_CALL = 2550;
public final static int DC_EVENT_INCOMING_CALL_ACCEPTED = 2560;
public final static int DC_EVENT_OUTGOING_CALL_ACCEPTED = 2570;
public final static int DC_EVENT_CALL_ENDED = 2580;
public final static int DC_EVENT_TRANSPORTS_MODIFIED = 2600;
public static final int DC_EVENT_INFO = 100;
public static final int DC_EVENT_WARNING = 300;
public static final int DC_EVENT_ERROR = 400;
public static final int DC_EVENT_ERROR_SELF_NOT_IN_GROUP = 410;
public static final int DC_EVENT_MSGS_CHANGED = 2000;
public static final int DC_EVENT_REACTIONS_CHANGED = 2001;
public static final int DC_EVENT_INCOMING_REACTION = 2002;
public static final int DC_EVENT_INCOMING_WEBXDC_NOTIFY = 2003;
public static final int DC_EVENT_INCOMING_MSG = 2005;
public static final int DC_EVENT_MSGS_NOTICED = 2008;
public static final int DC_EVENT_MSG_DELIVERED = 2010;
public static final int DC_EVENT_MSG_FAILED = 2012;
public static final int DC_EVENT_MSG_READ = 2015;
public static final int DC_EVENT_MSG_DELETED = 2016;
public static final int DC_EVENT_CHAT_MODIFIED = 2020;
public static final int DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED = 2021;
public static final int DC_EVENT_CHAT_DELETED = 2023;
public static final int DC_EVENT_CONTACTS_CHANGED = 2030;
public static final int DC_EVENT_LOCATION_CHANGED = 2035;
public static final int DC_EVENT_CONFIGURE_PROGRESS = 2041;
public static final int DC_EVENT_IMEX_PROGRESS = 2051;
public static final int DC_EVENT_IMEX_FILE_WRITTEN = 2052;
public static final int DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060;
public static final int DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061;
public static final int DC_EVENT_CONNECTIVITY_CHANGED = 2100;
public static final int DC_EVENT_SELFAVATAR_CHANGED = 2110;
public static final int DC_EVENT_WEBXDC_STATUS_UPDATE = 2120;
public static final int DC_EVENT_WEBXDC_INSTANCE_DELETED = 2121;
public static final int DC_EVENT_WEBXDC_REALTIME_DATA = 2150;
public static final int DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE = 2200;
public static final int DC_EVENT_INCOMING_CALL = 2550;
public static final int DC_EVENT_INCOMING_CALL_ACCEPTED = 2560;
public static final int DC_EVENT_OUTGOING_CALL_ACCEPTED = 2570;
public static final int DC_EVENT_CALL_ENDED = 2580;
public static final int DC_EVENT_TRANSPORTS_MODIFIED = 2600;
public final static int DC_IMEX_EXPORT_SELF_KEYS = 1;
public final static int DC_IMEX_IMPORT_SELF_KEYS = 2;
public final static int DC_IMEX_EXPORT_BACKUP = 11;
public final static int DC_IMEX_IMPORT_BACKUP = 12;
public static final int DC_IMEX_EXPORT_SELF_KEYS = 1;
public static final int DC_IMEX_IMPORT_SELF_KEYS = 2;
public static final int DC_IMEX_EXPORT_BACKUP = 11;
public static final int DC_IMEX_IMPORT_BACKUP = 12;
public final static int DC_GCL_VERIFIED_ONLY = 1;
public final static int DC_GCL_ADD_SELF = 2;
public final static int DC_GCL_ADDRESS = 0x04;
public final static int DC_GCL_ARCHIVED_ONLY = 0x01;
public final static int DC_GCL_NO_SPECIALS = 0x02;
public final static int DC_GCL_ADD_ALLDONE_HINT = 0x04;
public final static int DC_GCL_FOR_FORWARDING = 0x08;
public static final int DC_GCL_VERIFIED_ONLY = 1;
public static final int DC_GCL_ADD_SELF = 2;
public static final int DC_GCL_ADDRESS = 0x04;
public static final int DC_GCL_ARCHIVED_ONLY = 0x01;
public static final int DC_GCL_NO_SPECIALS = 0x02;
public static final int DC_GCL_ADD_ALLDONE_HINT = 0x04;
public static final int DC_GCL_FOR_FORWARDING = 0x08;
public final static int DC_GCM_ADDDAYMARKER = 0x01;
public static final int DC_GCM_ADDDAYMARKER = 0x01;
public final static int DC_QR_ASK_VERIFYCONTACT = 200;
public final static int DC_QR_ASK_VERIFYGROUP = 202;
public final static int DC_QR_ASK_JOIN_BROADCAST= 204;
public final static int DC_QR_FPR_OK = 210;
public final static int DC_QR_FPR_MISMATCH = 220;
public final static int DC_QR_FPR_WITHOUT_ADDR = 230;
public final static int DC_QR_ACCOUNT = 250;
public final static int DC_QR_BACKUP2 = 252;
public final static int DC_QR_BACKUP_TOO_NEW = 255;
public final static int DC_QR_WEBRTC = 260;
public final static int DC_QR_PROXY = 271;
public final static int DC_QR_ADDR = 320;
public final static int DC_QR_TEXT = 330;
public final static int DC_QR_URL = 332;
public final static int DC_QR_ERROR = 400;
public final static int DC_QR_WITHDRAW_VERIFYCONTACT = 500;
public final static int DC_QR_WITHDRAW_VERIFYGROUP = 502;
public final static int DC_QR_WITHDRAW_JOINBROADCAST = 504;
public final static int DC_QR_REVIVE_VERIFYCONTACT = 510;
public final static int DC_QR_REVIVE_VERIFYGROUP = 512;
public final static int DC_QR_REVIVE_JOINBROADCAST = 514;
public final static int DC_QR_LOGIN = 520;
public static final int DC_QR_ASK_VERIFYCONTACT = 200;
public static final int DC_QR_ASK_VERIFYGROUP = 202;
public static final int DC_QR_ASK_JOIN_BROADCAST = 204;
public static final int DC_QR_FPR_OK = 210;
public static final int DC_QR_FPR_MISMATCH = 220;
public static final int DC_QR_FPR_WITHOUT_ADDR = 230;
public static final int DC_QR_ACCOUNT = 250;
public static final int DC_QR_BACKUP2 = 252;
public static final int DC_QR_BACKUP_TOO_NEW = 255;
public static final int DC_QR_WEBRTC = 260;
public static final int DC_QR_PROXY = 271;
public static final int DC_QR_ADDR = 320;
public static final int DC_QR_TEXT = 330;
public static final int DC_QR_URL = 332;
public static final int DC_QR_ERROR = 400;
public static final int DC_QR_WITHDRAW_VERIFYCONTACT = 500;
public static final int DC_QR_WITHDRAW_VERIFYGROUP = 502;
public static final int DC_QR_WITHDRAW_JOINBROADCAST = 504;
public static final int DC_QR_REVIVE_VERIFYCONTACT = 510;
public static final int DC_QR_REVIVE_VERIFYGROUP = 512;
public static final int DC_QR_REVIVE_JOINBROADCAST = 514;
public static final int DC_QR_LOGIN = 520;
public final static int DC_SOCKET_AUTO = 0;
public final static int DC_SOCKET_SSL = 1;
public final static int DC_SOCKET_STARTTLS = 2;
public final static int DC_SOCKET_PLAIN = 3;
public static final int DC_SOCKET_AUTO = 0;
public static final int DC_SOCKET_SSL = 1;
public static final int DC_SOCKET_STARTTLS = 2;
public static final int DC_SOCKET_PLAIN = 3;
public final static int DC_SHOW_EMAILS_OFF = 0;
public final static int DC_SHOW_EMAILS_ACCEPTED_CONTACTS = 1;
public final static int DC_SHOW_EMAILS_ALL = 2;
public static final int DC_SHOW_EMAILS_OFF = 0;
public static final int DC_SHOW_EMAILS_ACCEPTED_CONTACTS = 1;
public static final int DC_SHOW_EMAILS_ALL = 2;
public final static int DC_MEDIA_QUALITY_BALANCED = 0;
public final static int DC_MEDIA_QUALITY_WORSE = 1;
public static final int DC_MEDIA_QUALITY_BALANCED = 0;
public static final int DC_MEDIA_QUALITY_WORSE = 1;
public final static int DC_CONNECTIVITY_NOT_CONNECTED = 1000;
public final static int DC_CONNECTIVITY_CONNECTING = 2000;
public final static int DC_CONNECTIVITY_WORKING = 3000;
public final static int DC_CONNECTIVITY_CONNECTED = 4000;
public static final int DC_CONNECTIVITY_NOT_CONNECTED = 1000;
public static final int DC_CONNECTIVITY_CONNECTING = 2000;
public static final int DC_CONNECTIVITY_WORKING = 3000;
public static final int DC_CONNECTIVITY_CONNECTED = 4000;
private static final String CONFIG_ACCOUNT_ENABLED = "ui.enabled";
private static final String CONFIG_MUTE_MENTIONS_IF_MUTED = "ui.mute_mentions_if_muted";
private static final String CONFIG_ACCOUNT_ENABLED = "ui.enabled";
private static final String CONFIG_MUTE_MENTIONS_IF_MUTED = "ui.mute_mentions_if_muted";
// when using DcAccounts, use Rpc.addAccount() instead
public DcContext(String osName, String dbfile) {
contextCPtr = createContextCPtr(osName, dbfile);
// when using DcAccounts, use Rpc.addAccount() instead
public DcContext(String osName, String dbfile) {
contextCPtr = createContextCPtr(osName, dbfile);
}
public DcContext(long contextCPtr) {
this.contextCPtr = contextCPtr;
}
public boolean isOk() {
return contextCPtr != 0;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
if (contextCPtr != 0) {
unrefContextCPtr();
contextCPtr = 0;
}
}
public DcContext(long contextCPtr) {
this.contextCPtr = contextCPtr;
public native int getAccountId();
// when using DcAccounts, use DcAccounts.getEventEmitter() instead
public DcEventEmitter getEventEmitter() {
return new DcEventEmitter(getEventEmitterCPtr());
}
public native void setStockTranslation(int stockId, String translation);
public native String getBlobdir();
public native String getLastError();
public native void stopOngoingProcess();
public native int isConfigured();
public native boolean open(String passphrase);
public native boolean isOpen();
// when using DcAccounts, use DcAccounts.startIo() instead
public native void startIo();
// when using DcAccounts, use DcAccounts.stopIo() instead
public native void stopIo();
// when using DcAccounts, use DcAccounts.maybeNetwork() instead
public native void maybeNetwork();
public native void setConfig(String key, String value);
public void setConfigInt(String key, int value) {
setConfig(key, Integer.toString(value));
}
public native boolean setConfigFromQr(String qr);
public native String getConfig(String key);
public int getConfigInt(String key) {
return getConfigInt(key, 0);
}
public int getConfigInt(String key, int defValue) {
try {
return Integer.parseInt(getConfig(key));
} catch (Exception e) {
}
return defValue;
}
public boolean isOk() {
return contextCPtr != 0;
}
public native String getInfo();
@Override
protected void finalize() throws Throwable {
super.finalize();
if (contextCPtr != 0) {
unrefContextCPtr();
contextCPtr = 0;
}
}
public native int getConnectivity();
public native int getAccountId ();
public native String getConnectivityHtml();
// when using DcAccounts, use DcAccounts.getEventEmitter() instead
public DcEventEmitter getEventEmitter () { return new DcEventEmitter(getEventEmitterCPtr()); }
public native String initiateKeyTransfer();
public native void setStockTranslation (int stockId, String translation);
public native String getBlobdir ();
public native String getLastError ();
public native void stopOngoingProcess ();
public native int isConfigured ();
public native boolean open (String passphrase);
public native boolean isOpen ();
public native void imex(int what, String dir);
// when using DcAccounts, use DcAccounts.startIo() instead
public native void startIo ();
public native String imexHasBackup(String dir);
// when using DcAccounts, use DcAccounts.stopIo() instead
public native void stopIo ();
public DcBackupProvider newBackupProvider() {
return new DcBackupProvider(newBackupProviderCPtr());
}
// when using DcAccounts, use DcAccounts.maybeNetwork() instead
public native void maybeNetwork ();
public native boolean receiveBackup(String qr);
public native void setConfig (String key, String value);
public void setConfigInt (String key, int value) { setConfig(key, Integer.toString(value)); }
public native boolean setConfigFromQr (String qr);
public native String getConfig (String key);
public int getConfigInt (String key) { return getConfigInt(key, 0); }
public int getConfigInt (String key, int defValue) { try{return Integer.parseInt(getConfig(key));} catch(Exception e) {} return defValue; }
public native String getInfo ();
public native int getConnectivity ();
public native String getConnectivityHtml ();
public native String initiateKeyTransfer ();
public native void imex (int what, String dir);
public native String imexHasBackup (String dir);
public DcBackupProvider newBackupProvider () { return new DcBackupProvider(newBackupProviderCPtr()); }
public native boolean receiveBackup (String qr);
public native boolean mayBeValidAddr (String addr);
public native int lookupContactIdByAddr(String addr);
public native int[] getContacts (int flags, String query);
public native int[] getBlockedContacts ();
public DcContact getContact (int contact_id) { return new DcContact(getContactCPtr(contact_id)); }
public native int createContact (String name, String addr);
public native void blockContact (int id, int block);
public native String getContactEncrInfo (int contact_id);
public native boolean deleteContact (int id);
public native int addAddressBook (String adrbook);
public DcChatlist getChatlist (int listflags, String query, int queryId) { return new DcChatlist(getAccountId(), getChatlistCPtr(listflags, query, queryId)); }
public DcChat getChat (int chat_id) { return new DcChat(getAccountId(), getChatCPtr(chat_id)); }
public native String getChatEncrInfo (int chat_id);
public native void markseenMsgs (int msg_ids[]);
public native void marknoticedChat (int chat_id);
public native void setChatVisibility (int chat_id, int visibility);
public native int getChatIdByContactId (int contact_id);
public native int createChatByContactId(int contact_id);
public native int createGroupChat (String name);
public native int createBroadcastList ();
public native boolean isContactInChat (int chat_id, int contact_id);
public native int addContactToChat (int chat_id, int contact_id);
public native int removeContactFromChat(int chat_id, int contact_id);
public native void setDraft (int chat_id, DcMsg msg/*null=delete*/);
public DcMsg getDraft (int chat_id) { return new DcMsg(getDraftCPtr(chat_id)); }
public native int setChatName (int chat_id, String name);
public native int setChatProfileImage (int chat_id, String name);
public native int[] getChatMsgs (int chat_id, int flags, int marker1before);
public native int[] searchMsgs (int chat_id, String query);
public native int[] getFreshMsgs ();
public native int[] getChatMedia (int chat_id, int type1, int type2, int type3);
public native int[] getChatContacts (int chat_id);
public native int getChatEphemeralTimer (int chat_id);
public native boolean setChatEphemeralTimer (int chat_id, int timer);
public native boolean setChatMuteDuration (int chat_id, long duration);
public native void deleteChat (int chat_id);
public native void blockChat (int chat_id);
public native void acceptChat (int chat_id);
public DcMsg getMsg (int msg_id) { return new DcMsg(getMsgCPtr(msg_id)); }
public native void sendEditRequest (int msg_id, String text);
public native String getMsgInfo (int id);
public native String getMsgHtml (int msg_id);
public native void downloadFullMsg (int msg_id);
public native int getFreshMsgCount (int chat_id);
public native int estimateDeletionCount(boolean from_server, long seconds);
public native void deleteMsgs (int msg_ids[]);
public native void sendDeleteRequest (int msg_ids[]);
public native void forwardMsgs (int msg_ids[], int chat_id);
public native void saveMsgs (int msg_ids[]);
public native boolean resendMsgs (int msg_ids[]);
public native int sendMsg (int chat_id, DcMsg msg);
public native int sendTextMsg (int chat_id, String text);
public native boolean sendWebxdcStatusUpdate(int msg_id, String payload);
public native String getWebxdcStatusUpdates(int msg_id, int last_known_serial);
public native void setWebxdcIntegration (String file);
public native int initWebxdcIntegration(int chat_id);
public native int addDeviceMsg (String label, DcMsg msg);
public native boolean wasDeviceMsgEverAdded(String label);
public DcLot checkQr (String qr) { return new DcLot(checkQrCPtr(qr)); }
public native String getSecurejoinQr (int chat_id);
public native String getSecurejoinQrSvg (int chat_id);
public native String createQrSvg (String payload);
public native int joinSecurejoin (String qr);
public native void sendLocationsToChat (int chat_id, int seconds);
public native boolean isSendingLocationsToChat(int chat_id);
public DcProvider getProviderFromEmailWithDns (String email) { long cptr = getProviderFromEmailWithDnsCPtr(email); return cptr!=0 ? new DcProvider(cptr) : null; }
public native boolean mayBeValidAddr(String addr);
public boolean isEnabled() {
return !"0".equals(getConfig(CONFIG_ACCOUNT_ENABLED));
}
public native int lookupContactIdByAddr(String addr);
public void setEnabled(boolean enabled) {
setConfigInt(CONFIG_ACCOUNT_ENABLED, enabled? 1 : 0);
if (enabled) {
startIo();
} else {
stopIo();
}
}
public native int[] getContacts(int flags, String query);
public boolean isMentionsEnabled() {
return getConfigInt(CONFIG_MUTE_MENTIONS_IF_MUTED) != 1;
}
public native int[] getBlockedContacts();
public void setMentionsEnabled(boolean enabled) {
setConfigInt(CONFIG_MUTE_MENTIONS_IF_MUTED, enabled? 0 : 1);
}
public DcContact getContact(int contact_id) {
return new DcContact(getContactCPtr(contact_id));
}
public String getName() {
String displayname = getConfig("displayname");
if (displayname.isEmpty()) {
displayname = getContact(DcContact.DC_CONTACT_ID_SELF).getAddr();
}
return displayname;
}
public native int createContact(String name, String addr);
public boolean isChatmail() {
return getConfigInt("is_chatmail") == 1;
}
public native void blockContact(int id, int block);
public boolean isMuted() {
return getConfigInt("is_muted") == 1;
}
public native String getContactEncrInfo(int contact_id);
public void setMuted(boolean muted) {
setConfigInt("is_muted", muted? 1 : 0);
}
public native boolean deleteContact(int id);
public void restartIo() {
if (!isEnabled()) return;
stopIo();
public native int addAddressBook(String adrbook);
public DcChatlist getChatlist(int listflags, String query, int queryId) {
return new DcChatlist(getAccountId(), getChatlistCPtr(listflags, query, queryId));
}
public DcChat getChat(int chat_id) {
return new DcChat(getAccountId(), getChatCPtr(chat_id));
}
public native String getChatEncrInfo(int chat_id);
public native void markseenMsgs(int msg_ids[]);
public native void marknoticedChat(int chat_id);
public native void setChatVisibility(int chat_id, int visibility);
public native int getChatIdByContactId(int contact_id);
public native int createChatByContactId(int contact_id);
public native int createGroupChat(String name);
public native int createBroadcastList();
public native boolean isContactInChat(int chat_id, int contact_id);
public native int addContactToChat(int chat_id, int contact_id);
public native int removeContactFromChat(int chat_id, int contact_id);
public native void setDraft(int chat_id, DcMsg msg /*null=delete*/);
public DcMsg getDraft(int chat_id) {
return new DcMsg(getDraftCPtr(chat_id));
}
public native int setChatName(int chat_id, String name);
public native int setChatProfileImage(int chat_id, String name);
public native int[] getChatMsgs(int chat_id, int flags, int marker1before);
public native int[] searchMsgs(int chat_id, String query);
public native int[] getFreshMsgs();
public native int[] getChatMedia(int chat_id, int type1, int type2, int type3);
public native int[] getChatContacts(int chat_id);
public native int getChatEphemeralTimer(int chat_id);
public native boolean setChatEphemeralTimer(int chat_id, int timer);
public native boolean setChatMuteDuration(int chat_id, long duration);
public native void deleteChat(int chat_id);
public native void blockChat(int chat_id);
public native void acceptChat(int chat_id);
public DcMsg getMsg(int msg_id) {
return new DcMsg(getMsgCPtr(msg_id));
}
public native void sendEditRequest(int msg_id, String text);
public native String getMsgInfo(int id);
public native String getMsgHtml(int msg_id);
public native void downloadFullMsg(int msg_id);
public native int getFreshMsgCount(int chat_id);
public native int estimateDeletionCount(boolean from_server, long seconds);
public native void deleteMsgs(int msg_ids[]);
public native void sendDeleteRequest(int msg_ids[]);
public native void forwardMsgs(int msg_ids[], int chat_id);
public native void saveMsgs(int msg_ids[]);
public native boolean resendMsgs(int msg_ids[]);
public native int sendMsg(int chat_id, DcMsg msg);
public native int sendTextMsg(int chat_id, String text);
public native boolean sendWebxdcStatusUpdate(int msg_id, String payload);
public native String getWebxdcStatusUpdates(int msg_id, int last_known_serial);
public native void setWebxdcIntegration(String file);
public native int initWebxdcIntegration(int chat_id);
public native int addDeviceMsg(String label, DcMsg msg);
public native boolean wasDeviceMsgEverAdded(String label);
public DcLot checkQr(String qr) {
return new DcLot(checkQrCPtr(qr));
}
public native String getSecurejoinQr(int chat_id);
public native String getSecurejoinQrSvg(int chat_id);
public native String createQrSvg(String payload);
public native int joinSecurejoin(String qr);
public native void sendLocationsToChat(int chat_id, int seconds);
public native boolean isSendingLocationsToChat(int chat_id);
public DcProvider getProviderFromEmailWithDns(String email) {
long cptr = getProviderFromEmailWithDnsCPtr(email);
return cptr != 0 ? new DcProvider(cptr) : null;
}
public boolean isEnabled() {
return !"0".equals(getConfig(CONFIG_ACCOUNT_ENABLED));
}
public void setEnabled(boolean enabled) {
setConfigInt(CONFIG_ACCOUNT_ENABLED, enabled ? 1 : 0);
if (enabled) {
startIo();
} else {
stopIo();
}
}
/**
* @return true if at least one chat has location streaming enabled
*/
public native boolean setLocation (float latitude, float longitude, float accuracy);
public boolean isMentionsEnabled() {
return getConfigInt(CONFIG_MUTE_MENTIONS_IF_MUTED) != 1;
}
// working with raw c-data
private long contextCPtr; // CAVE: the name is referenced in the JNI
private native long createContextCPtr(String osName, String dbfile);
private native void unrefContextCPtr ();
private native long getEventEmitterCPtr();
public native long createMsgCPtr (int viewtype);
private native long getChatlistCPtr (int listflags, String query, int queryId);
private native long getChatCPtr (int chat_id);
private native long getMsgCPtr (int id);
private native long getDraftCPtr (int id);
private native long getContactCPtr (int id);
private native long checkQrCPtr (String qr);
private native long getProviderFromEmailWithDnsCPtr (String addr);
private native long newBackupProviderCPtr();
public void setMentionsEnabled(boolean enabled) {
setConfigInt(CONFIG_MUTE_MENTIONS_IF_MUTED, enabled ? 0 : 1);
}
public String getName() {
String displayname = getConfig("displayname");
if (displayname.isEmpty()) {
displayname = getConfig("addr");
}
return displayname;
}
public boolean isChatmail() {
return getConfigInt("is_chatmail") == 1;
}
public boolean isMuted() {
return getConfigInt("is_muted") == 1;
}
public void setMuted(boolean muted) {
setConfigInt("is_muted", muted ? 1 : 0);
}
public void restartIo() {
if (!isEnabled()) return;
stopIo();
startIo();
}
/**
* @return true if at least one chat has location streaming enabled
*/
public native boolean setLocation(float latitude, float longitude, float accuracy);
// working with raw c-data
private long contextCPtr; // CAVE: the name is referenced in the JNI
private native long createContextCPtr(String osName, String dbfile);
private native void unrefContextCPtr();
private native long getEventEmitterCPtr();
public native long createMsgCPtr(int viewtype);
private native long getChatlistCPtr(int listflags, String query, int queryId);
private native long getChatCPtr(int chat_id);
private native long getMsgCPtr(int id);
private native long getDraftCPtr(int id);
private native long getContactCPtr(int id);
private native long checkQrCPtr(String qr);
private native long getProviderFromEmailWithDnsCPtr(String addr);
private native long newBackupProviderCPtr();
}
+24 -17
View File
@@ -2,24 +2,31 @@ package com.b44t.messenger;
public class DcEvent {
public DcEvent(long eventCPtr) {
this.eventCPtr = eventCPtr;
}
public DcEvent(long eventCPtr) {
this.eventCPtr = eventCPtr;
}
@Override protected void finalize() throws Throwable {
super.finalize();
unrefEventCPtr();
eventCPtr = 0;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
unrefEventCPtr();
eventCPtr = 0;
}
public native int getId ();
public native int getData1Int ();
public native int getData2Int ();
public native String getData2Str ();
public native byte[] getData2Blob();
public native int getAccountId();
public native int getId();
// working with raw c-data
private long eventCPtr; // CAVE: the name is referenced in the JNI
private native void unrefEventCPtr();
public native int getData1Int();
public native int getData2Int();
public native String getData2Str();
public native byte[] getData2Blob();
public native int getAccountId();
// working with raw c-data
private long eventCPtr; // CAVE: the name is referenced in the JNI
private native void unrefEventCPtr();
}
@@ -0,0 +1,30 @@
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();
}
@@ -2,23 +2,26 @@ package com.b44t.messenger;
public class DcEventEmitter {
public DcEventEmitter(long eventEmitterCPtr) {
this.eventEmitterCPtr = eventEmitterCPtr;
}
public DcEventEmitter(long eventEmitterCPtr) {
this.eventEmitterCPtr = eventEmitterCPtr;
}
@Override protected void finalize() throws Throwable {
super.finalize();
unrefEventEmitterCPtr();
eventEmitterCPtr = 0;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
unrefEventEmitterCPtr();
eventEmitterCPtr = 0;
}
public DcEvent getNextEvent () {
long eventCPtr = getNextEventCPtr();
return eventCPtr == 0 ? null : new DcEvent(eventCPtr);
}
public DcEvent getNextEvent() {
long eventCPtr = getNextEventCPtr();
return eventCPtr == 0 ? null : new DcEvent(eventCPtr);
}
// working with raw c-data
private long eventEmitterCPtr; // CAVE: the name is referenced in the JNI
private native long getNextEventCPtr ();
private native void unrefEventEmitterCPtr();
// working with raw c-data
private long eventEmitterCPtr; // CAVE: the name is referenced in the JNI
private native long getNextEventCPtr();
private native void unrefEventEmitterCPtr();
}
@@ -2,20 +2,23 @@ package com.b44t.messenger;
public class DcJsonrpcInstance {
public DcJsonrpcInstance(long jsonrpcInstanceCPtr) {
this.jsonrpcInstanceCPtr = jsonrpcInstanceCPtr;
}
public DcJsonrpcInstance(long jsonrpcInstanceCPtr) {
this.jsonrpcInstanceCPtr = jsonrpcInstanceCPtr;
}
@Override protected void finalize() throws Throwable {
super.finalize();
unrefJsonrpcInstanceCPtr();
jsonrpcInstanceCPtr = 0;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
unrefJsonrpcInstanceCPtr();
jsonrpcInstanceCPtr = 0;
}
public native void request(String request);
public native String getNextResponse();
public native void request(String request);
// working with raw c-data
private long jsonrpcInstanceCPtr; // CAVE: the name is referenced in the JNI
private native void unrefJsonrpcInstanceCPtr();
public native String getNextResponse();
// working with raw c-data
private long jsonrpcInstanceCPtr; // CAVE: the name is referenced in the JNI
private native void unrefJsonrpcInstanceCPtr();
}
+27 -20
View File
@@ -2,28 +2,35 @@ package com.b44t.messenger;
public class DcLot {
public final static int DC_TEXT1_DRAFT = 1;
public final static int DC_TEXT1_USERNAME = 2;
public final static int DC_TEXT1_SELF = 3;
public static final int DC_TEXT1_DRAFT = 1;
public static final int DC_TEXT1_USERNAME = 2;
public static final int DC_TEXT1_SELF = 3;
public DcLot(long lotCPtr) {
this.lotCPtr = lotCPtr;
}
public DcLot(long lotCPtr) {
this.lotCPtr = lotCPtr;
}
@Override protected void finalize() throws Throwable {
super.finalize();
unrefLotCPtr();
lotCPtr = 0;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
unrefLotCPtr();
lotCPtr = 0;
}
public native String getText1 ();
public native int getText1Meaning();
public native String getText2 ();
public native long getTimestamp ();
public native int getState ();
public native int getId ();
public native String getText1();
// working with raw c-data
private long lotCPtr; // CAVE: the name is referenced in the JNI
private native void unrefLotCPtr();
public native int getText1Meaning();
public native String getText2();
public native long getTimestamp();
public native int getState();
public native int getId();
// working with raw c-data
private long lotCPtr; // CAVE: the name is referenced in the JNI
private native void unrefLotCPtr();
}
@@ -1,14 +1,14 @@
package com.b44t.messenger;
/**
* Contains a list of media entries, their respective positions and ability to move through it.
*/
/** Contains a list of media entries, their respective positions and ability to move through it. */
public class DcMediaGalleryElement {
final int[] mediaMsgs;
int position;
final DcContext context;
public DcMediaGalleryElement(int[] mediaMsgs, int position, DcContext context, boolean leftIsRecent) {
public DcMediaGalleryElement(
int[] mediaMsgs, int position, DcContext context, boolean leftIsRecent) {
this.mediaMsgs = mediaMsgs;
this.position = position;
this.context = context;
@@ -33,7 +33,7 @@ public class DcMediaGalleryElement {
}
public void moveToPosition(int newPosition) {
if(newPosition < 0 || newPosition >= mediaMsgs.length)
if (newPosition < 0 || newPosition >= mediaMsgs.length)
throw new IllegalArgumentException("can't move outside of known area.");
position = newPosition;
}
+298 -232
View File
@@ -1,266 +1,332 @@
package com.b44t.messenger;
import android.text.TextUtils;
import org.json.JSONObject;
import java.io.File;
import java.util.Set;
import org.json.JSONObject;
public class DcMsg {
public final static int DC_MSG_UNDEFINED = 0;
public final static int DC_MSG_TEXT = 10;
public final static int DC_MSG_IMAGE = 20;
public final static int DC_MSG_GIF = 21;
public final static int DC_MSG_STICKER = 23;
public final static int DC_MSG_AUDIO = 40;
public final static int DC_MSG_VOICE = 41;
public final static int DC_MSG_VIDEO = 50;
public final static int DC_MSG_FILE = 60;
public final static int DC_MSG_CALL = 71;
public final static int DC_MSG_WEBXDC = 80;
public final static int DC_MSG_VCARD = 90;
public static final int DC_MSG_UNDEFINED = 0;
public static final int DC_MSG_TEXT = 10;
public static final int DC_MSG_IMAGE = 20;
public static final int DC_MSG_GIF = 21;
public static final int DC_MSG_STICKER = 23;
public static final int DC_MSG_AUDIO = 40;
public static final int DC_MSG_VOICE = 41;
public static final int DC_MSG_VIDEO = 50;
public static final int DC_MSG_FILE = 60;
public static final int DC_MSG_CALL = 71;
public static final int DC_MSG_WEBXDC = 80;
public static final int DC_MSG_VCARD = 90;
public final static int DC_INFO_UNKNOWN = 0;
public final static int DC_INFO_GROUP_NAME_CHANGED = 2;
public final static int DC_INFO_GROUP_IMAGE_CHANGED = 3;
public final static int DC_INFO_MEMBER_ADDED_TO_GROUP = 4;
public final static int DC_INFO_MEMBER_REMOVED_FROM_GROUP = 5;
public final static int DC_INFO_AUTOCRYPT_SETUP_MESSAGE = 6;
public final static int DC_INFO_SECURE_JOIN_MESSAGE = 7;
public final static int DC_INFO_LOCATIONSTREAMING_ENABLED = 8;
public final static int DC_INFO_LOCATION_ONLY = 9;
public final static int DC_INFO_EPHEMERAL_TIMER_CHANGED = 10;
public final static int DC_INFO_PROTECTION_ENABLED = 11;
public final static int DC_INFO_INVALID_UNENCRYPTED_MAIL = 13;
public final static int DC_INFO_WEBXDC_INFO_MESSAGE = 32;
public final static int DC_INFO_CHAT_E2EE = 50;
public static final int DC_INFO_UNKNOWN = 0;
public static final int DC_INFO_GROUP_NAME_CHANGED = 2;
public static final int DC_INFO_GROUP_IMAGE_CHANGED = 3;
public static final int DC_INFO_MEMBER_ADDED_TO_GROUP = 4;
public static final int DC_INFO_MEMBER_REMOVED_FROM_GROUP = 5;
public static final int DC_INFO_AUTOCRYPT_SETUP_MESSAGE = 6;
public static final int DC_INFO_SECURE_JOIN_MESSAGE = 7;
public static final int DC_INFO_LOCATIONSTREAMING_ENABLED = 8;
public static final int DC_INFO_LOCATION_ONLY = 9;
public static final int DC_INFO_EPHEMERAL_TIMER_CHANGED = 10;
public static final int DC_INFO_PROTECTION_ENABLED = 11;
public static final int DC_INFO_INVALID_UNENCRYPTED_MAIL = 13;
public static final int DC_INFO_WEBXDC_INFO_MESSAGE = 32;
public static final int DC_INFO_CHAT_E2EE = 50;
public static final int DC_INFO_CHAT_DESCRIPTION_CHANGED = 70;
public final static int DC_STATE_UNDEFINED = 0;
public final static int DC_STATE_IN_FRESH = 10;
public final static int DC_STATE_IN_NOTICED = 13;
public final static int DC_STATE_IN_SEEN = 16;
public final static int DC_STATE_OUT_PREPARING = 18;
public final static int DC_STATE_OUT_DRAFT = 19;
public final static int DC_STATE_OUT_PENDING = 20;
public final static int DC_STATE_OUT_FAILED = 24;
public final static int DC_STATE_OUT_DELIVERED = 26;
public final static int DC_STATE_OUT_MDN_RCVD = 28;
public static final int DC_STATE_UNDEFINED = 0;
public static final int DC_STATE_IN_FRESH = 10;
public static final int DC_STATE_IN_NOTICED = 13;
public static final int DC_STATE_IN_SEEN = 16;
public static final int DC_STATE_OUT_PREPARING = 18;
public static final int DC_STATE_OUT_DRAFT = 19;
public static final int DC_STATE_OUT_PENDING = 20;
public static final int DC_STATE_OUT_FAILED = 24;
public static final int DC_STATE_OUT_DELIVERED = 26;
public static final int DC_STATE_OUT_MDN_RCVD = 28;
public final static int DC_DOWNLOAD_DONE = 0;
public final static int DC_DOWNLOAD_AVAILABLE = 10;
public final static int DC_DOWNLOAD_FAILURE = 20;
public final static int DC_DOWNLOAD_UNDECIPHERABLE = 30;
public final static int DC_DOWNLOAD_IN_PROGRESS = 1000;
public static final int DC_DOWNLOAD_DONE = 0;
public static final int DC_DOWNLOAD_AVAILABLE = 10;
public static final int DC_DOWNLOAD_FAILURE = 20;
public static final int DC_DOWNLOAD_UNDECIPHERABLE = 30;
public static final int DC_DOWNLOAD_IN_PROGRESS = 1000;
public static final int DC_MSG_NO_ID = 0;
public final static int DC_MSG_ID_MARKER1 = 1;
public final static int DC_MSG_ID_DAYMARKER = 9;
public static final int DC_MSG_NO_ID = 0;
public static final int DC_MSG_ID_MARKER1 = 1;
public static final int DC_MSG_ID_DAYMARKER = 9;
public final static int DC_VIDEOCHATTYPE_UNKNOWN = 0;
public final static int DC_VIDEOCHATTYPE_BASICWEBRTC = 1;
public static final int DC_VIDEOCHATTYPE_UNKNOWN = 0;
public static final int DC_VIDEOCHATTYPE_BASICWEBRTC = 1;
private static final String TAG = DcMsg.class.getSimpleName();
private static final String TAG = DcMsg.class.getSimpleName();
public DcMsg(DcContext context, int viewtype) {
msgCPtr = context.createMsgCPtr(viewtype);
public DcMsg(DcContext context, int viewtype) {
msgCPtr = context.createMsgCPtr(viewtype);
}
public DcMsg(long msgCPtr) {
this.msgCPtr = msgCPtr;
}
public boolean isOk() {
return msgCPtr != 0;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
unrefMsgCPtr();
msgCPtr = 0;
}
@Override
public int hashCode() {
return this.getId();
}
@Override
public boolean equals(Object other) {
if (other == null || !(other instanceof DcMsg)) {
return false;
}
public DcMsg(long msgCPtr) {
this.msgCPtr = msgCPtr;
}
DcMsg that = (DcMsg) other;
return this.getId() == that.getId() && this.getId() != 0;
}
public boolean isOk() {
return msgCPtr != 0;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
unrefMsgCPtr();
msgCPtr = 0;
}
@Override
public int hashCode() {
return this.getId();
}
@Override
public boolean equals(Object other) {
if (other == null || !(other instanceof DcMsg)) {
return false;
}
DcMsg that = (DcMsg) other;
return this.getId()==that.getId() && this.getId()!=0;
}
/**
* If given a message, calculates the position of the message in the chat
*/
public static int getMessagePosition(DcMsg msg, DcContext dcContext) {
int msgs[] = dcContext.getChatMsgs(msg.getChatId(), 0, 0);
int startingPosition = -1;
int msgId = msg.getId();
for (int i = 0; i < msgs.length; i++) {
if (msgs[i] == msgId) {
startingPosition = msgs.length - 1 - i;
break;
}
}
return startingPosition;
}
public native int getId ();
public native String getText ();
public native String getSubject ();
public native long getTimestamp ();
public native long getSortTimestamp ();
public native boolean hasDeviatingTimestamp();
public native boolean hasLocation ();
private native int getViewType ();
public int getType () { return getDownloadState()==DC_DOWNLOAD_DONE? getViewType() : DC_MSG_TEXT; }
public native int getInfoType ();
public native int getInfoContactId ();
public native int getState ();
public native int getDownloadState ();
public native int getChatId ();
public native int getFromId ();
public native int getWidth (int def);
public native int getHeight (int def);
public native int getDuration ();
public native void lateFilingMediaSize(int width, int height, int duration);
public DcLot getSummary (DcChat chat) { return new DcLot(getSummaryCPtr(chat.getChatCPtr())); }
public native String getSummarytext (int approx_characters);
public native int showPadlock ();
public boolean hasFile () { String file = getFile(); return file!=null && !file.isEmpty(); }
public native String getFile ();
public native String getFilemime ();
public native String getFilename ();
public native long getFilebytes ();
public native byte[] getWebxdcBlob (String filename);
public JSONObject getWebxdcInfo () {
try {
String json = getWebxdcInfoJson();
if (json != null && !json.isEmpty()) return new JSONObject(json);
} catch(Exception e) {
e.printStackTrace();
/** If given a message, calculates the position of the message in the chat */
public static int getMessagePosition(DcMsg msg, DcContext dcContext) {
int msgs[] = dcContext.getChatMsgs(msg.getChatId(), 0, 0);
int startingPosition = -1;
int msgId = msg.getId();
for (int i = 0; i < msgs.length; i++) {
if (msgs[i] == msgId) {
startingPosition = msgs.length - 1 - i;
break;
}
return new JSONObject();
}
public native String getWebxdcHref ();
public native boolean isForwarded ();
public native boolean isInfo ();
public native boolean hasHtml ();
public native String getSetupCodeBegin ();
public native void setText (String text);
public native void setSubject (String text);
public native void setHtml (String text);
public native void forceSticker ();
public native void setFileAndDeduplicate(String file, String name, String filemime);
public native void setDimension (int width, int height);
public native void setDuration (int duration);
public native void setLocation (float latitude, float longitude);
public native String getPOILocation ();
public void setQuote (DcMsg quote) { setQuoteCPtr(quote.msgCPtr); }
public native String getQuotedText ();
public native String getError ();
public native String getOverrideSenderName();
public native boolean isEdited ();
return startingPosition;
}
public String getSenderName(DcContact dcContact) {
String overrideName = getOverrideSenderName();
if (overrideName != null) {
return "~" + overrideName;
} else {
return dcContact.getDisplayName();
}
}
public native int getId();
public DcMsg getQuotedMsg () {
long cPtr = getQuotedMsgCPtr();
return cPtr != 0 ? new DcMsg(cPtr) : null;
}
public native String getText();
public DcMsg getParent() {
long cPtr = getParentCPtr();
return cPtr != 0 ? new DcMsg(cPtr) : null;
}
public native String getSubject();
public native int getOriginalMsgId ();
public native int getSavedMsgId ();
public native long getTimestamp();
public boolean canSave() {
// saving info-messages out of context results in confusion, see https://github.com/deltachat/deltachat-ios/issues/2567
return !isInfo();
}
public native long getSortTimestamp();
public File getFileAsFile() {
if(getFile()==null)
throw new AssertionError("expected a file to be present.");
return new File(getFile());
}
public native boolean hasDeviatingTimestamp();
// aliases and higher-level tools
public static int[] msgSetToIds(final Set<DcMsg> dcMsgs) {
if (dcMsgs == null) {
return new int[0];
}
int[] ids = new int[dcMsgs.size()];
int i = 0;
for (DcMsg dcMsg : dcMsgs) {
ids[i++] = dcMsg.getId();
}
return ids;
}
public native boolean hasLocation();
public boolean isOutgoing() {
return getFromId() == DcContact.DC_CONTACT_ID_SELF;
}
private native int getViewType();
public String getDisplayBody() {
return getText();
}
public int getType() {
return getDownloadState() == DC_DOWNLOAD_DONE ? getViewType() : DC_MSG_TEXT;
}
public String getBody() {
return getText();
}
public native int getInfoType();
public long getDateReceived() {
return getTimestamp();
}
public native int getInfoContactId();
public boolean isFailed() {
return (getState() == DC_STATE_OUT_FAILED) || (!TextUtils.isEmpty(getError()));
}
public boolean isPreparing() {
return getState() == DC_STATE_OUT_PREPARING;
}
public boolean isSecure() {
return showPadlock()!=0;
}
public boolean isPending() {
return getState() == DC_STATE_OUT_PENDING;
}
public boolean isDelivered() {
return getState() == DC_STATE_OUT_DELIVERED;
}
public boolean isRemoteRead() {
return getState() == DC_STATE_OUT_MDN_RCVD;
}
public boolean isSeen() {
return getState() == DC_STATE_IN_SEEN;
}
public native int getState();
public native int getDownloadState();
// working with raw c-data
private long msgCPtr; // CAVE: the name is referenced in the JNI
private native void unrefMsgCPtr ();
private native long getSummaryCPtr (long chatCPtr);
private native void setQuoteCPtr (long quoteCPtr);
private native long getQuotedMsgCPtr ();
private native long getParentCPtr ();
private native String getWebxdcInfoJson ();
};
public native int getChatId();
public native int getFromId();
public native int getWidth(int def);
public native int getHeight(int def);
public native int getDuration();
public native void lateFilingMediaSize(int width, int height, int duration);
public DcLot getSummary(DcChat chat) {
return new DcLot(getSummaryCPtr(chat.getChatCPtr()));
}
public native String getSummarytext(int approx_characters);
public native int showPadlock();
public boolean hasFile() {
String file = getFile();
return file != null && !file.isEmpty();
}
public native String getFile();
public native String getFilemime();
public native String getFilename();
public native long getFilebytes();
public native byte[] getWebxdcBlob(String filename);
public JSONObject getWebxdcInfo() {
try {
String json = getWebxdcInfoJson();
if (json != null && !json.isEmpty()) return new JSONObject(json);
} catch (Exception e) {
e.printStackTrace();
}
return new JSONObject();
}
public native String getWebxdcHref();
public native boolean isForwarded();
public native boolean isInfo();
public native boolean hasHtml();
public native String getSetupCodeBegin();
public native void setText(String text);
public native void setSubject(String text);
public native void setHtml(String text);
public native void forceSticker();
public native void setFileAndDeduplicate(String file, String name, String filemime);
public native void setDimension(int width, int height);
public native void setDuration(int duration);
public native void setLocation(float latitude, float longitude);
public native String getPOILocation();
public void setQuote(DcMsg quote) {
setQuoteCPtr(quote.msgCPtr);
}
public native String getQuotedText();
public native String getError();
public native String getOverrideSenderName();
public native boolean isEdited();
public String getSenderName(DcContact dcContact) {
String overrideName = getOverrideSenderName();
if (overrideName != null) {
return "~" + overrideName;
} else {
return dcContact.getDisplayName();
}
}
public DcMsg getQuotedMsg() {
long cPtr = getQuotedMsgCPtr();
return cPtr != 0 ? new DcMsg(cPtr) : null;
}
public DcMsg getParent() {
long cPtr = getParentCPtr();
return cPtr != 0 ? new DcMsg(cPtr) : null;
}
public native int getOriginalMsgId();
public native int getSavedMsgId();
public boolean canSave() {
// saving info-messages out of context results in confusion, see
// https://github.com/deltachat/deltachat-ios/issues/2567
return !isInfo();
}
public File getFileAsFile() {
if (getFile() == null) throw new AssertionError("expected a file to be present.");
return new File(getFile());
}
// aliases and higher-level tools
public static int[] msgSetToIds(final Set<DcMsg> dcMsgs) {
if (dcMsgs == null) {
return new int[0];
}
int[] ids = new int[dcMsgs.size()];
int i = 0;
for (DcMsg dcMsg : dcMsgs) {
ids[i++] = dcMsg.getId();
}
return ids;
}
public boolean isOutgoing() {
return getFromId() == DcContact.DC_CONTACT_ID_SELF;
}
public String getDisplayBody() {
return getText();
}
public String getBody() {
return getText();
}
public long getDateReceived() {
return getTimestamp();
}
public boolean isFailed() {
return (getState() == DC_STATE_OUT_FAILED) || (!TextUtils.isEmpty(getError()));
}
public boolean isPreparing() {
return getState() == DC_STATE_OUT_PREPARING;
}
public boolean isSecure() {
return showPadlock() != 0;
}
public boolean isPending() {
return getState() == DC_STATE_OUT_PENDING;
}
public boolean isDelivered() {
return getState() == DC_STATE_OUT_DELIVERED;
}
public boolean isRemoteRead() {
return getState() == DC_STATE_OUT_MDN_RCVD;
}
public boolean isSeen() {
return getState() == DC_STATE_IN_SEEN;
}
// working with raw c-data
private long msgCPtr; // CAVE: the name is referenced in the JNI
private native void unrefMsgCPtr();
private native long getSummaryCPtr(long chatCPtr);
private native void setQuoteCPtr(long quoteCPtr);
private native long getQuotedMsgCPtr();
private native long getParentCPtr();
private native String getWebxdcInfoJson();
}
;
@@ -2,25 +2,29 @@ package com.b44t.messenger;
public class DcProvider {
public final static int DC_PROVIDER_STATUS_OK = 1;
public final static int DC_PROVIDER_STATUS_PREPARATION = 2;
public final static int DC_PROVIDER_STATUS_BROKEN = 3;
public static final int DC_PROVIDER_STATUS_OK = 1;
public static final int DC_PROVIDER_STATUS_PREPARATION = 2;
public static final int DC_PROVIDER_STATUS_BROKEN = 3;
public DcProvider(long providerCPtr) {
this.providerCPtr = providerCPtr;
}
public DcProvider(long providerCPtr) {
this.providerCPtr = providerCPtr;
}
@Override protected void finalize() throws Throwable {
super.finalize();
unrefProviderCPtr();
providerCPtr = 0;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
unrefProviderCPtr();
providerCPtr = 0;
}
public native int getStatus ();
public native String getBeforeLoginHint ();
public native String getOverviewPage ();
public native int getStatus();
// working with raw c-data
private long providerCPtr; // CAVE: the name is referenced in the JNI
private native void unrefProviderCPtr();
public native String getBeforeLoginHint();
public native String getOverviewPage();
// working with raw c-data
private long providerCPtr; // CAVE: the name is referenced in the JNI
private native void unrefProviderCPtr();
}
@@ -19,4 +19,4 @@ public class FFITransport extends BaseTransport {
protected String getResponse() {
return dcJsonrpcInstance.getNextResponse();
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,61 +1,73 @@
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;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import com.b44t.messenger.DcMsg;
import com.google.android.material.tabs.TabLayout;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
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;
public class AllMediaActivity extends PassphraseRequiredActionBarActivity
implements DcEventCenter.DcEventDelegate
{
implements DcEventCenter.DcEventDelegate {
private static final String TAG = AllMediaActivity.class.getSimpleName();
public static final String CHAT_ID_EXTRA = "chat_id";
public static final String CHAT_ID_EXTRA = "chat_id";
public static final String CONTACT_ID_EXTRA = "contact_id";
public static final String FORCE_GALLERY = "force_gallery";
public static final String FORCE_GALLERY = "force_gallery";
static class TabData {
final int title;
final int type1;
final int type2;
final int type3;
TabData(int title, int type1, int type2, int type3) {
this.title = title;
this.type1 = type1;
this.type2 = type2;
this.type3 = type3;
}
};
}
;
private DcContext dcContext;
private int chatId;
private int contactId;
private DcContext dcContext;
private int chatId;
private int contactId;
private final ArrayList<TabData> tabs = new ArrayList<>();
private Toolbar toolbar;
private TabLayout tabLayout;
private ViewPager viewPager;
private Toolbar toolbar;
private TabLayout tabLayout;
private ViewPager viewPager;
private @Nullable MediaController mediaController;
private ListenableFuture<MediaController> mediaControllerFuture;
private AudioPlaybackViewModel playbackViewModel;
@Override
protected void onPreCreate() {
@@ -67,7 +79,9 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
@Override
protected void onCreate(Bundle bundle, boolean ready) {
tabs.add(new TabData(R.string.webxdc_apps, DcMsg.DC_MSG_WEBXDC, 0, 0));
tabs.add(new TabData(R.string.tab_gallery, DcMsg.DC_MSG_IMAGE, DcMsg.DC_MSG_GIF, DcMsg.DC_MSG_VIDEO));
tabs.add(
new TabData(
R.string.tab_gallery, DcMsg.DC_MSG_IMAGE, DcMsg.DC_MSG_GIF, DcMsg.DC_MSG_VIDEO));
tabs.add(new TabData(R.string.audio, DcMsg.DC_MSG_AUDIO, DcMsg.DC_MSG_VOICE, 0));
tabs.add(new TabData(R.string.files, DcMsg.DC_MSG_FILE, 0, 0));
@@ -79,7 +93,8 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
ActionBar supportActionBar = getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setDisplayHomeAsUpEnabled(true);
supportActionBar.setTitle(isGlobalGallery() ? R.string.menu_all_media : R.string.apps_and_media);
supportActionBar.setTitle(
isGlobalGallery() ? R.string.menu_all_media : R.string.apps_and_media);
}
this.tabLayout.setupWithViewPager(viewPager);
@@ -91,41 +106,80 @@ 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();
}
@Override
public void handleEvent(@NonNull DcEvent event) {
}
public void handleEvent(@NonNull DcEvent event) {}
private void initializeResources() {
chatId = getIntent().getIntExtra(CHAT_ID_EXTRA, 0);
contactId = getIntent().getIntExtra(CONTACT_ID_EXTRA, 0);
chatId = getIntent().getIntExtra(CHAT_ID_EXTRA, 0);
contactId = getIntent().getIntExtra(CONTACT_ID_EXTRA, 0);
if (contactId!=0) {
if (contactId != 0) {
chatId = dcContext.getChatIdByContactId(contactId);
}
if(chatId!=0) {
if (chatId != 0) {
DcChat dcChat = dcContext.getChat(chatId);
if(!dcChat.isMultiUser()) {
if (!dcChat.isMultiUser()) {
final int[] members = dcContext.getChatContacts(chatId);
contactId = members.length>=1? members[0] : 0;
contactId = members.length >= 1 ? members[0] : 0;
}
}
this.viewPager = ViewUtil.findById(this, R.id.pager);
this.toolbar = ViewUtil.findById(this, R.id.toolbar);
this.toolbar = ViewUtil.findById(this, R.id.toolbar);
this.tabLayout = ViewUtil.findById(this, R.id.tab_layout);
}
private void initializeMediaController() {
SessionToken sessionToken =
new SessionToken(this, new ComponentName(this, AudioPlaybackService.class));
mediaControllerFuture = new MediaController.Builder(this, sessionToken).buildAsync();
mediaControllerFuture.addListener(
() -> {
try {
mediaController = mediaControllerFuture.get();
addActivityContext(this.getIntent().getExtras(), this.getClass().getName());
playbackViewModel.setMediaController(mediaController);
} catch (Exception e) {
Log.e(TAG, "Error connecting to audio playback service", e);
}
},
ContextCompat.getMainExecutor(this));
}
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;
return contactId == 0 && chatId == 0;
}
private class AllMediaPagerAdapter extends FragmentStatePagerAdapter {
@@ -159,10 +213,14 @@ public class AllMediaActivity extends PassphraseRequiredActionBarActivity
if (data.type1 == DcMsg.DC_MSG_IMAGE) {
fragment = new AllMediaGalleryFragment();
args.putInt(AllMediaGalleryFragment.CHAT_ID_EXTRA, (chatId==0&&!isGlobalGallery())? -1 : chatId);
args.putInt(
AllMediaGalleryFragment.CHAT_ID_EXTRA,
(chatId == 0 && !isGlobalGallery()) ? -1 : chatId);
} else {
fragment = new AllMediaDocumentsFragment();
args.putInt(AllMediaDocumentsFragment.CHAT_ID_EXTRA, (chatId==0&&!isGlobalGallery())? -1 : chatId);
args.putInt(
AllMediaDocumentsFragment.CHAT_ID_EXTRA,
(chatId == 0 && !isGlobalGallery()) ? -1 : chatId);
args.putInt(AllMediaDocumentsFragment.VIEWTYPE1, data.type1);
args.putInt(AllMediaDocumentsFragment.VIEWTYPE2, data.type2);
}
@@ -5,15 +5,16 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.b44t.messenger.DcMsg;
import com.codewaves.stickyheadergrid.StickyHeaderGridAdapter;
import org.thoughtcrime.securesms.components.AudioView;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.thoughtcrime.securesms.components.DocumentView;
import org.thoughtcrime.securesms.components.WebxdcView;
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
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;
@@ -21,30 +22,27 @@ import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.MediaUtil;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
class AllMediaDocumentsAdapter extends StickyHeaderGridAdapter {
private final Context context;
private final ItemClickListener itemClickListener;
private final Set<DcMsg> selected;
private final Context context;
private final ItemClickListener itemClickListener;
private final Set<DcMsg> selected;
private BucketedThreadMedia media;
private BucketedThreadMedia media;
private AudioPlaybackViewModel playbackViewModel;
private static class ViewHolder extends StickyHeaderGridAdapter.ItemViewHolder {
private final DocumentView documentView;
private final AudioView audioView;
private final WebxdcView webxdcView;
private final TextView date;
private final AudioView audioView;
private final WebxdcView webxdcView;
private final TextView date;
public ViewHolder(View v) {
super(v);
documentView = v.findViewById(R.id.document_view);
audioView = v.findViewById(R.id.audio_view);
webxdcView = v.findViewById(R.id.webxdc_view);
date = v.findViewById(R.id.date);
documentView = v.findViewById(R.id.document_view);
audioView = v.findViewById(R.id.audio_view);
webxdcView = v.findViewById(R.id.webxdc_view);
date = v.findViewById(R.id.date);
}
}
@@ -57,86 +55,108 @@ class AllMediaDocumentsAdapter extends StickyHeaderGridAdapter {
}
}
AllMediaDocumentsAdapter(@NonNull Context context,
BucketedThreadMedia media,
ItemClickListener clickListener)
{
this.context = context;
this.media = media;
AllMediaDocumentsAdapter(
@NonNull Context context, BucketedThreadMedia media, ItemClickListener clickListener) {
this.context = context;
this.media = media;
this.itemClickListener = clickListener;
this.selected = new HashSet<>();
this.selected = new HashSet<>();
}
public void setMedia(BucketedThreadMedia media) {
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));
public StickyHeaderGridAdapter.HeaderViewHolder onCreateHeaderViewHolder(
ViewGroup parent, int headerType) {
return new HeaderHolder(
LayoutInflater.from(context)
.inflate(R.layout.contact_selection_list_divider, parent, false));
}
@Override
public ItemViewHolder onCreateItemViewHolder(ViewGroup parent, int itemType) {
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.profile_document_item, parent, false));
return new ViewHolder(
LayoutInflater.from(context).inflate(R.layout.profile_document_item, parent, false));
}
@Override
public void onBindHeaderViewHolder(StickyHeaderGridAdapter.HeaderViewHolder viewHolder, int section) {
((HeaderHolder)viewHolder).textView.setText(media.getName(section));
public void onBindHeaderViewHolder(
StickyHeaderGridAdapter.HeaderViewHolder viewHolder, int section) {
((HeaderHolder) viewHolder).textView.setText(media.getName(section));
}
@Override
public void onBindItemViewHolder(ItemViewHolder itemViewHolder, int section, int offset) {
ViewHolder viewHolder = ((ViewHolder)itemViewHolder);
DcMsg dcMsg = media.get(section, offset);
Slide slide = MediaUtil.getSlideForMsg(context, dcMsg);
ViewHolder viewHolder = ((ViewHolder) itemViewHolder);
DcMsg dcMsg = media.get(section, offset);
Slide slide = MediaUtil.getSlideForMsg(context, dcMsg);
if (slide != null && slide.hasAudio()) {
viewHolder.documentView.setVisibility(View.GONE);
viewHolder.webxdcView.setVisibility(View.GONE);
viewHolder.audioView.setVisibility(View.VISIBLE);
viewHolder.audioView.setAudio((AudioSlide)slide, dcMsg.getDuration());
viewHolder.audioView.setPlaybackViewModel(playbackViewModel);
viewHolder.audioView.setAudio((AudioSlide) slide);
viewHolder.audioView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg));
viewHolder.audioView.setOnLongClickListener(view -> { itemClickListener.onMediaLongClicked(dcMsg); return true; });
viewHolder.audioView.setOnLongClickListener(
view -> {
itemClickListener.onMediaLongClicked(dcMsg);
return true;
});
viewHolder.audioView.disablePlayer(!selected.isEmpty());
viewHolder.itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg));
viewHolder.date.setVisibility(View.VISIBLE);
}
else if (slide != null && slide.isWebxdcDocument()) {
} else if (slide != null && slide.isWebxdcDocument()) {
viewHolder.audioView.setVisibility(View.GONE);
viewHolder.documentView.setVisibility(View.GONE);
viewHolder.webxdcView.setVisibility(View.VISIBLE);
viewHolder.webxdcView.setWebxdc(dcMsg, "");
viewHolder.webxdcView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg));
viewHolder.webxdcView.setOnLongClickListener(view -> { itemClickListener.onMediaLongClicked(dcMsg); return true; });
viewHolder.webxdcView.setOnLongClickListener(
view -> {
itemClickListener.onMediaLongClicked(dcMsg);
return true;
});
viewHolder.itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg));
viewHolder.date.setVisibility(View.GONE);
}
else if (slide != null && slide.hasDocument()) {
} else if (slide != null && slide.hasDocument()) {
viewHolder.audioView.setVisibility(View.GONE);
viewHolder.webxdcView.setVisibility(View.GONE);
viewHolder.documentView.setVisibility(View.VISIBLE);
viewHolder.documentView.setDocument((DocumentSlide)slide);
viewHolder.documentView.setDocument((DocumentSlide) slide);
viewHolder.documentView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg));
viewHolder.documentView.setOnLongClickListener(view -> { itemClickListener.onMediaLongClicked(dcMsg); return true; });
viewHolder.documentView.setOnLongClickListener(
view -> {
itemClickListener.onMediaLongClicked(dcMsg);
return true;
});
viewHolder.itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(dcMsg));
viewHolder.date.setVisibility(View.VISIBLE);
}
else {
} else {
viewHolder.documentView.setVisibility(View.GONE);
viewHolder.audioView.setVisibility(View.GONE);
viewHolder.webxdcView.setVisibility(View.GONE);
viewHolder.date.setVisibility(View.GONE);
}
viewHolder.itemView.setOnLongClickListener(view -> { itemClickListener.onMediaLongClicked(dcMsg); return true; });
viewHolder.itemView.setOnLongClickListener(
view -> {
itemClickListener.onMediaLongClicked(dcMsg);
return true;
});
viewHolder.itemView.setSelected(selected.contains(dcMsg));
viewHolder.date.setText(DateUtils.getBriefRelativeTimeSpanString(context, dcMsg.getTimestamp()));
viewHolder.date.setText(
DateUtils.getBriefRelativeTimeSpanString(context, dcMsg.getTimestamp()));
}
@Override
@@ -177,6 +197,7 @@ class AllMediaDocumentsAdapter extends StickyHeaderGridAdapter {
interface ItemClickListener {
void onMediaClicked(@NonNull DcMsg mediaRecord);
void onMediaLongClicked(DcMsg mediaRecord);
}
}
@@ -12,31 +12,27 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.lifecycle.ViewModelProvider;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.RecyclerView;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import com.b44t.messenger.DcMsg;
import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager;
import java.util.Set;
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Set;
public class AllMediaDocumentsFragment
extends MessageSelectorFragment
public class AllMediaDocumentsFragment extends MessageSelectorFragment
implements LoaderManager.LoaderCallbacks<BucketedThreadMediaLoader.BucketedThreadMedia>,
AllMediaDocumentsAdapter.ItemClickListener
{
AllMediaDocumentsAdapter.ItemClickListener {
public static final String CHAT_ID_EXTRA = "chat_id";
public static final String VIEWTYPE1 = "viewtype1";
public static final String VIEWTYPE2 = "viewtype2";
@@ -48,13 +44,12 @@ public class AllMediaDocumentsFragment
private int viewtype1;
private int viewtype2;
protected int chatId;
protected int chatId;
@Override
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);
@@ -63,19 +58,23 @@ public class AllMediaDocumentsFragment
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.profile_documents_fragment, container, false);
this.recyclerView = ViewUtil.findById(view, R.id.recycler_view);
this.noMedia = ViewUtil.findById(view, R.id.no_documents);
this.gridManager = new StickyHeaderGridLayoutManager(1);
this.noMedia = ViewUtil.findById(view, R.id.no_documents);
this.gridManager = new StickyHeaderGridLayoutManager(1);
// add padding to avoid content hidden behind system bars
ViewUtil.applyWindowInsets(recyclerView, true, false, true, true);
this.recyclerView.setAdapter(new AllMediaDocumentsAdapter(getContext(),
new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()),
this));
AllMediaDocumentsAdapter adapter =
new AllMediaDocumentsAdapter(
getContext(), new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()), this);
this.recyclerView.setAdapter(adapter);
adapter.setPlaybackViewModel(
new ViewModelProvider(requireActivity()).get(AudioPlaybackViewModel.class));
this.recyclerView.setLayoutManager(gridManager);
this.recyclerView.setHasFixedSize(true);
@@ -106,12 +105,15 @@ public class AllMediaDocumentsFragment
}
@Override
public Loader<BucketedThreadMediaLoader.BucketedThreadMedia> onCreateLoader(int i, Bundle bundle) {
public Loader<BucketedThreadMediaLoader.BucketedThreadMedia> onCreateLoader(
int i, Bundle bundle) {
return new BucketedThreadMediaLoader(getContext(), chatId, viewtype1, viewtype2, 0);
}
@Override
public void onLoadFinished(Loader<BucketedThreadMediaLoader.BucketedThreadMedia> loader, BucketedThreadMediaLoader.BucketedThreadMedia bucketedThreadMedia) {
public void onLoadFinished(
Loader<BucketedThreadMediaLoader.BucketedThreadMedia> loader,
BucketedThreadMediaLoader.BucketedThreadMedia bucketedThreadMedia) {
((AllMediaDocumentsAdapter) recyclerView.getAdapter()).setMedia(bucketedThreadMedia);
((AllMediaDocumentsAdapter) recyclerView.getAdapter()).notifyAllSectionsDataSetChanged();
@@ -119,7 +121,7 @@ public class AllMediaDocumentsFragment
if (chatId == DC_CHAT_NO_CHAT) {
if (viewtype1 == DcMsg.DC_MSG_WEBXDC) {
noMedia.setText(R.string.all_apps_empty_hint);
} else if (viewtype1 == DcMsg.DC_MSG_FILE){
} else if (viewtype1 == DcMsg.DC_MSG_FILE) {
noMedia.setText(R.string.all_files_empty_hint);
} else {
noMedia.setText(R.string.tab_all_media_empty_hint);
@@ -134,7 +136,8 @@ public class AllMediaDocumentsFragment
@Override
public void onLoaderReset(Loader<BucketedThreadMediaLoader.BucketedThreadMedia> cursorLoader) {
((AllMediaDocumentsAdapter) recyclerView.getAdapter()).setMedia(new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()));
((AllMediaDocumentsAdapter) recyclerView.getAdapter())
.setMedia(new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()));
}
@Override
@@ -165,7 +168,7 @@ public class AllMediaDocumentsFragment
private void handleMediaPreviewClick(@NonNull DcMsg dcMsg) {
// audio is started by the play-button
if (dcMsg.getType()==DcMsg.DC_MSG_AUDIO || dcMsg.getType()==DcMsg.DC_MSG_VOICE) {
if (dcMsg.getType() == DcMsg.DC_MSG_AUDIO || dcMsg.getType() == DcMsg.DC_MSG_VOICE) {
return;
}
@@ -213,7 +216,8 @@ public class AllMediaDocumentsFragment
}
menu.findItem(R.id.menu_resend).setVisible(canResend);
boolean webxdcApp = singleSelection && messageRecords.iterator().next().getType() == DcMsg.DC_MSG_WEBXDC;
boolean webxdcApp =
singleSelection && messageRecords.iterator().next().getType() == DcMsg.DC_MSG_WEBXDC;
menu.findItem(R.id.menu_add_to_home_screen).setVisible(webxdcApp);
}
@@ -240,19 +244,26 @@ public class AllMediaDocumentsFragment
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem menuItem) {
int itemId = menuItem.getItemId();
AudioPlaybackViewModel playbackViewModel =
new ViewModelProvider(requireActivity()).get(AudioPlaybackViewModel.class);
if (itemId == R.id.details) {
handleDisplayDetails(getSelectedMessageRecord(getListAdapter().getSelectedMedia()));
mode.finish();
return true;
} else if (itemId == R.id.delete) {
handleDeleteMessages(chatId, getListAdapter().getSelectedMedia());
handleDeleteMessages(
chatId,
getListAdapter().getSelectedMedia(),
playbackViewModel::stopByIds,
playbackViewModel::stopByIds);
mode.finish();
return true;
} else if (itemId == R.id.share) {
handleShare(getSelectedMessageRecord(getListAdapter().getSelectedMedia()));
return true;
} else if (itemId == R.id.menu_add_to_home_screen) {
WebxdcActivity.addToHomeScreen(getActivity(), getSelectedMessageRecord(getListAdapter().getSelectedMedia()).getId());
WebxdcActivity.addToHomeScreen(
getActivity(), getSelectedMessageRecord(getListAdapter().getSelectedMedia()).getId());
mode.finish();
return true;
} else if (itemId == R.id.show_in_chat) {
@@ -5,38 +5,34 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.b44t.messenger.DcMsg;
import com.codewaves.stickyheadergrid.StickyHeaderGridAdapter;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.thoughtcrime.securesms.components.ThumbnailView;
import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader.BucketedThreadMedia;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.util.MediaUtil;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
class AllMediaGalleryAdapter extends StickyHeaderGridAdapter {
private final Context context;
private final GlideRequests glideRequests;
private final ItemClickListener itemClickListener;
private final Set<DcMsg> selected;
private final Context context;
private final GlideRequests glideRequests;
private final ItemClickListener itemClickListener;
private final Set<DcMsg> selected;
private BucketedThreadMedia media;
private BucketedThreadMedia media;
private static class ViewHolder extends StickyHeaderGridAdapter.ItemViewHolder {
final ThumbnailView imageView;
final View selectedIndicator;
final View selectedIndicator;
ViewHolder(View v) {
super(v);
imageView = v.findViewById(R.id.image);
imageView = v.findViewById(R.id.image);
selectedIndicator = v.findViewById(R.id.selected_indicator);
}
}
@@ -50,16 +46,16 @@ class AllMediaGalleryAdapter extends StickyHeaderGridAdapter {
}
}
AllMediaGalleryAdapter(@NonNull Context context,
@NonNull GlideRequests glideRequests,
BucketedThreadMedia media,
ItemClickListener clickListener)
{
this.context = context;
this.glideRequests = glideRequests;
this.media = media;
AllMediaGalleryAdapter(
@NonNull Context context,
@NonNull GlideRequests glideRequests,
BucketedThreadMedia media,
ItemClickListener clickListener) {
this.context = context;
this.glideRequests = glideRequests;
this.media = media;
this.itemClickListener = clickListener;
this.selected = new HashSet<>();
this.selected = new HashSet<>();
}
public void setMedia(BucketedThreadMedia media) {
@@ -67,36 +63,42 @@ class AllMediaGalleryAdapter extends StickyHeaderGridAdapter {
}
@Override
public StickyHeaderGridAdapter.HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int headerType) {
return new HeaderHolder(LayoutInflater.from(context).inflate(R.layout.contact_selection_list_divider, parent, false));
public StickyHeaderGridAdapter.HeaderViewHolder onCreateHeaderViewHolder(
ViewGroup parent, int headerType) {
return new HeaderHolder(
LayoutInflater.from(context)
.inflate(R.layout.contact_selection_list_divider, parent, false));
}
@Override
public ItemViewHolder onCreateItemViewHolder(ViewGroup parent, int itemType) {
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.profile_gallery_item, parent, false));
return new ViewHolder(
LayoutInflater.from(context).inflate(R.layout.profile_gallery_item, parent, false));
}
@Override
public void onBindHeaderViewHolder(StickyHeaderGridAdapter.HeaderViewHolder viewHolder, int section) {
((HeaderHolder)viewHolder).textView.setText(media.getName(section));
public void onBindHeaderViewHolder(
StickyHeaderGridAdapter.HeaderViewHolder viewHolder, int section) {
((HeaderHolder) viewHolder).textView.setText(media.getName(section));
}
@Override
public void onBindItemViewHolder(ItemViewHolder viewHolder, int section, int offset) {
DcMsg mediaRecord = media.get(section, offset);
ThumbnailView thumbnailView = ((ViewHolder)viewHolder).imageView;
View selectedIndicator = ((ViewHolder)viewHolder).selectedIndicator;
Slide slide = MediaUtil.getSlideForMsg(context, mediaRecord);
DcMsg mediaRecord = media.get(section, offset);
ThumbnailView thumbnailView = ((ViewHolder) viewHolder).imageView;
View selectedIndicator = ((ViewHolder) viewHolder).selectedIndicator;
Slide slide = MediaUtil.getSlideForMsg(context, mediaRecord);
if (slide != null) {
thumbnailView.setImageResource(glideRequests, slide);
}
thumbnailView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord));
thumbnailView.setOnLongClickListener(view -> {
itemClickListener.onMediaLongClicked(mediaRecord);
return true;
});
thumbnailView.setOnLongClickListener(
view -> {
itemClickListener.onMediaLongClicked(mediaRecord);
return true;
});
selectedIndicator.setVisibility(selected.contains(mediaRecord) ? View.VISIBLE : View.GONE);
}
@@ -139,6 +141,7 @@ class AllMediaGalleryAdapter extends StickyHeaderGridAdapter {
interface ItemClickListener {
void onMediaClicked(@NonNull DcMsg mediaRecord);
void onMediaLongClicked(DcMsg mediaRecord);
}
}
@@ -12,19 +12,17 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.RecyclerView;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import com.b44t.messenger.DcMsg;
import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager;
import java.util.Set;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.database.Address;
@@ -32,13 +30,9 @@ import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Set;
public class AllMediaGalleryFragment
extends MessageSelectorFragment
public class AllMediaGalleryFragment extends MessageSelectorFragment
implements LoaderManager.LoaderCallbacks<BucketedThreadMediaLoader.BucketedThreadMedia>,
AllMediaGalleryAdapter.ItemClickListener
{
AllMediaGalleryAdapter.ItemClickListener {
public static final String CHAT_ID_EXTRA = "chat_id";
protected TextView noMedia;
@@ -46,33 +40,35 @@ public class AllMediaGalleryFragment
private StickyHeaderGridLayoutManager gridManager;
private final ActionModeCallback actionModeCallback = new ActionModeCallback();
private int chatId;
private int chatId;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
dcContext = DcHelper.getContext(getContext());
chatId = getArguments().getInt(CHAT_ID_EXTRA, -1);
getLoaderManager().initLoader(0, null, this);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.profile_gallery_fragment, container, false);
this.recyclerView = ViewUtil.findById(view, R.id.media_grid);
this.noMedia = ViewUtil.findById(view, R.id.no_images);
this.gridManager = new StickyHeaderGridLayoutManager(getCols());
this.noMedia = ViewUtil.findById(view, R.id.no_images);
this.gridManager = new StickyHeaderGridLayoutManager(getCols());
// add padding to avoid content hidden behind system bars
ViewUtil.applyWindowInsets(recyclerView, true, false, true, true);
this.recyclerView.setAdapter(new AllMediaGalleryAdapter(getContext(),
GlideApp.with(this),
new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()),
this));
this.recyclerView.setAdapter(
new AllMediaGalleryAdapter(
getContext(),
GlideApp.with(this),
new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()),
this));
this.recyclerView.setLayoutManager(gridManager);
this.recyclerView.setHasFixedSize(true);
@@ -95,7 +91,9 @@ public class AllMediaGalleryFragment
}
private int getCols() {
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE? 5 : 3;
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE
? 5
: 3;
}
@Override
@@ -108,12 +106,16 @@ public class AllMediaGalleryFragment
}
@Override
public Loader<BucketedThreadMediaLoader.BucketedThreadMedia> onCreateLoader(int i, Bundle bundle) {
return new BucketedThreadMediaLoader(getContext(), chatId, DcMsg.DC_MSG_IMAGE, DcMsg.DC_MSG_GIF, DcMsg.DC_MSG_VIDEO);
public Loader<BucketedThreadMediaLoader.BucketedThreadMedia> onCreateLoader(
int i, Bundle bundle) {
return new BucketedThreadMediaLoader(
getContext(), chatId, DcMsg.DC_MSG_IMAGE, DcMsg.DC_MSG_GIF, DcMsg.DC_MSG_VIDEO);
}
@Override
public void onLoadFinished(Loader<BucketedThreadMediaLoader.BucketedThreadMedia> loader, BucketedThreadMediaLoader.BucketedThreadMedia bucketedThreadMedia) {
public void onLoadFinished(
Loader<BucketedThreadMediaLoader.BucketedThreadMedia> loader,
BucketedThreadMediaLoader.BucketedThreadMedia bucketedThreadMedia) {
((AllMediaGalleryAdapter) recyclerView.getAdapter()).setMedia(bucketedThreadMedia);
((AllMediaGalleryAdapter) recyclerView.getAdapter()).notifyAllSectionsDataSetChanged();
@@ -126,7 +128,8 @@ public class AllMediaGalleryFragment
@Override
public void onLoaderReset(Loader<BucketedThreadMediaLoader.BucketedThreadMedia> cursorLoader) {
((AllMediaGalleryAdapter) recyclerView.getAdapter()).setMedia(new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()));
((AllMediaGalleryAdapter) recyclerView.getAdapter())
.setMedia(new BucketedThreadMediaLoader.BucketedThreadMedia(getContext()));
}
@Override
@@ -23,6 +23,7 @@ import androidx.work.WorkManager;
import com.b44t.messenger.DcAccounts;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import com.b44t.messenger.DcEventChannel;
import com.b44t.messenger.DcEventEmitter;
import com.b44t.messenger.FFITransport;
@@ -164,7 +165,8 @@ public class ApplicationContext extends MultiDexApplication {
StringWriter stringWriter = new StringWriter();
throwable.printStackTrace(new PrintWriter(stringWriter, true));
String errorMsg = "Android " + Build.VERSION.RELEASE +":\n" + stringWriter.getBuffer().toString();
String subject = "ArcaneChat " + BuildConfig.VERSION_NAME + " Crash Report";
errorMsg += "\n" + LogViewFragment.grabLogcat();
String subject = "ArcaneChat " + BuildConfig.VERSION_NAME + "-" + BuildConfig.FLAVOR + " Crash Report";
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
@@ -195,7 +197,29 @@ public class ApplicationContext extends MultiDexApplication {
Util.runOnBackground(() -> {
synchronized (initLock) {
try {
dcAccounts = new DcAccounts(new File(getFilesDir(), "accounts").getAbsolutePath());
DcEventChannel eventChannel = new DcEventChannel();
DcEventEmitter emitter = eventChannel.getEventEmitter();
eventCenter = new DcEventCenter(this);
new Thread(() -> {
Log.i(TAG, "Starting event loop");
while (true) {
DcEvent event = emitter.getNextEvent();
if (event == null) {
break;
}
if (isInitialized) {
eventCenter.handleEvent(event);
} else {
// not fully initialized, only handle logging events,
// ex. account migrations during DcAccounts initialization
eventCenter.handleLogging(event);
}
}
Log.i("DeltaChat", "shutting down event handler");
}, "eventThread").start();
dcAccounts = new DcAccounts(new File(getFilesDir(), "accounts").getAbsolutePath(), eventChannel);
Log.i(TAG, "DcAccounts created");
rpc = new Rpc(new FFITransport(dcAccounts.getJsonrpcInstance()));
Log.i(TAG, "Rpc created");
@@ -235,28 +259,12 @@ public class ApplicationContext extends MultiDexApplication {
}
dcContext = dcAccounts.getSelectedAccount();
notificationCenter = new NotificationCenter(this);
eventCenter = new DcEventCenter(this);
dcLocationManager = new DcLocationManager(this, dcContext);
// Mark as initialized before starting threads that depend on it
isInitialized = true;
initLock.notifyAll();
Log.i(TAG, "DcAccounts initialization complete");
new Thread(() -> {
Log.i(TAG, "Starting event loop");
DcEventEmitter emitter = dcAccounts.getEventEmitter();
Log.i(TAG, "DcEventEmitter obtained");
while (true) {
DcEvent event = emitter.getNextEvent();
if (event==null) {
break;
}
eventCenter.handleEvent(event);
}
Log.i("DeltaChat", "shutting down event handler");
}, "eventThread").start();
// set translations before starting I/O to avoid sending untranslated MDNs (issue #2288)
DcHelper.setStockTranslations(this);
@@ -21,7 +21,8 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
@@ -30,19 +31,17 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.preferences.AdvancedPreferenceFragment;
import org.thoughtcrime.securesms.preferences.AppearancePreferenceFragment;
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
import org.thoughtcrime.securesms.preferences.PrivacyPreferenceFragment;
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment;
import org.thoughtcrime.securesms.preferences.PrivacyPreferenceFragment;
import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference;
import org.thoughtcrime.securesms.qr.BackupTransferActivity;
import org.thoughtcrime.securesms.util.DynamicTheme;
@@ -55,24 +54,22 @@ import org.thoughtcrime.securesms.util.ViewUtil;
* The Activity for application preference display and management.
*
* @author Moxie Marlinspike
*
*/
public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarActivity
implements SharedPreferences.OnSharedPreferenceChangeListener
{
private static final String PREFERENCE_CATEGORY_PROFILE = "preference_category_profile";
private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "preference_category_notifications";
private static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance";
private static final String PREFERENCE_CATEGORY_CHATS = "preference_category_chats";
private static final String PREFERENCE_CATEGORY_PRIVACY = "preference_category_privacy";
private static final String PREFERENCE_CATEGORY_MULTIDEVICE = "preference_category_multidevice";
private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
private static final String PREFERENCE_CATEGORY_CONNECTIVITY = "preference_category_connectivity";
private static final String PREFERENCE_CATEGORY_DONATE = "preference_category_donate";
private static final String PREFERENCE_CATEGORY_HELP = "preference_category_help";
implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String PREFERENCE_CATEGORY_PROFILE = "preference_category_profile";
private static final String PREFERENCE_CATEGORY_NOTIFICATIONS =
"preference_category_notifications";
private static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance";
private static final String PREFERENCE_CATEGORY_CHATS = "preference_category_chats";
private static final String PREFERENCE_CATEGORY_PRIVACY = "preference_category_privacy";
private static final String PREFERENCE_CATEGORY_MULTIDEVICE = "preference_category_multidevice";
private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
private static final String PREFERENCE_CATEGORY_CONNECTIVITY = "preference_category_connectivity";
private static final String PREFERENCE_CATEGORY_DONATE = "preference_category_donate";
private static final String PREFERENCE_CATEGORY_HELP = "preference_category_help";
public static final int REQUEST_CODE_SET_BACKGROUND = 11;
public static final int REQUEST_CODE_SET_BACKGROUND = 11;
@Override
protected void onCreate(Bundle icicle, boolean ready) {
@@ -89,18 +86,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS) {
showBackupProvider();
return;
}
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment);
fragment.onActivityResult(requestCode, resultCode, data);
}
@Override
public boolean onSupportNavigateUp() {
FragmentManager fragmentManager = getSupportFragmentManager();
@@ -125,34 +110,51 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
public void showBackupProvider() {
Intent intent = new Intent(this, BackupTransferActivity.class);
intent.putExtra(BackupTransferActivity.TRANSFER_MODE, BackupTransferActivity.TransferMode.SENDER_SHOW_QR.getInt());
intent.putExtra(
BackupTransferActivity.TRANSFER_MODE,
BackupTransferActivity.TransferMode.SENDER_SHOW_QR.getInt());
startActivity(intent);
overridePendingTransition(0, 0); // let the activity appear in the same way as the other pages (which are mostly fragments)
overridePendingTransition(
0, 0); // let the activity appear in the same way as the other pages (which are mostly
// fragments)
finishAffinity(); // see comment (**2) in BackupTransferActivity.doFinish()
}
public static class ApplicationPreferenceFragment extends CorrectedPreferenceFragment implements DcEventCenter.DcEventDelegate {
public static class ApplicationPreferenceFragment extends CorrectedPreferenceFragment
implements DcEventCenter.DcEventDelegate {
private ActivityResultLauncher<Intent> screenLockLauncher;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
screenLockLauncher =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK) {
((ApplicationPreferencesActivity) getActivity()).showBackupProvider();
}
});
this.findPreference(PREFERENCE_CATEGORY_PROFILE)
.setOnPreferenceClickListener(new ProfileClickListener());
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_NOTIFICATIONS));
.setOnPreferenceClickListener(
new CategoryClickListener(PREFERENCE_CATEGORY_NOTIFICATIONS));
this.findPreference(PREFERENCE_CATEGORY_CONNECTIVITY)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_CONNECTIVITY));
.setOnPreferenceClickListener(
new CategoryClickListener(PREFERENCE_CATEGORY_CONNECTIVITY));
this.findPreference(PREFERENCE_CATEGORY_APPEARANCE)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE));
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE));
this.findPreference(PREFERENCE_CATEGORY_CHATS)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_CHATS));
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_CHATS));
this.findPreference(PREFERENCE_CATEGORY_PRIVACY)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_PRIVACY));
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_PRIVACY));
this.findPreference(PREFERENCE_CATEGORY_MULTIDEVICE)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_MULTIDEVICE));
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_MULTIDEVICE));
this.findPreference(PREFERENCE_CATEGORY_ADVANCED)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
this.findPreference(PREFERENCE_CATEGORY_DONATE)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DONATE));
@@ -160,7 +162,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
this.findPreference(PREFERENCE_CATEGORY_HELP)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_HELP));
DcHelper.getEventCenter(getActivity()).addObserver(DcContext.DC_EVENT_CONNECTIVITY_CHANGED, this);
DcHelper.getEventCenter(getActivity())
.addObserver(DcContext.DC_EVENT_CONNECTIVITY_CHANGED, this);
}
@Override
@@ -172,7 +175,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
public void onResume() {
super.onResume();
//noinspection ConstantConditions
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.menu_settings);
((ApplicationPreferencesActivity) getActivity())
.getSupportActionBar()
.setTitle(R.string.menu_settings);
setCategorySummaries();
}
@@ -186,12 +191,14 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
public void handleEvent(@NonNull DcEvent event) {
if (event.getId() == DcContext.DC_EVENT_CONNECTIVITY_CHANGED) {
this.findPreference(PREFERENCE_CATEGORY_CONNECTIVITY)
.setSummary(DcHelper.getConnectivitySummary(getActivity(), getString(R.string.connectivity_connected)));
.setSummary(
DcHelper.getConnectivitySummary(
getActivity(), getString(R.string.connectivity_connected)));
}
}
private void setCategorySummaries() {
((ProfilePreference)this.findPreference(PREFERENCE_CATEGORY_PROFILE)).refresh();
((ProfilePreference) this.findPreference(PREFERENCE_CATEGORY_PROFILE)).refresh();
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
.setSummary(NotificationsPreferenceFragment.getSummary(getActivity()));
@@ -202,7 +209,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
this.findPreference(PREFERENCE_CATEGORY_PRIVACY)
.setSummary(PrivacyPreferenceFragment.getSummary(getActivity()));
this.findPreference(PREFERENCE_CATEGORY_CONNECTIVITY)
.setSummary(DcHelper.getConnectivitySummary(getActivity(), getString(R.string.connectivity_connected)));
.setSummary(
DcHelper.getConnectivitySummary(
getActivity(), getString(R.string.connectivity_connected)));
this.findPreference(PREFERENCE_CATEGORY_HELP)
.setSummary(AdvancedPreferenceFragment.getVersion(getActivity()));
}
@@ -219,63 +228,76 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
Fragment fragment = null;
switch (category) {
case PREFERENCE_CATEGORY_NOTIFICATIONS:
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getActivity());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || notificationManager.areNotificationsEnabled()) {
fragment = new NotificationsPreferenceFragment();
} else {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.notifications_disabled)
.setMessage(R.string.perm_explain_access_to_notifications_denied)
.setPositiveButton(R.string.perm_continue, (dialog, which) -> getActivity().startActivity(Permissions.getApplicationSettingsIntent(getActivity())))
.setNegativeButton(android.R.string.cancel, null)
.show();
}
break;
case PREFERENCE_CATEGORY_CONNECTIVITY:
startActivity(new Intent(getActivity(), ConnectivityActivity.class));
break;
case PREFERENCE_CATEGORY_APPEARANCE:
fragment = new AppearancePreferenceFragment();
break;
case PREFERENCE_CATEGORY_CHATS:
fragment = new ChatsPreferenceFragment();
break;
case PREFERENCE_CATEGORY_PRIVACY:
fragment = new PrivacyPreferenceFragment();
break;
case PREFERENCE_CATEGORY_MULTIDEVICE:
if (!ScreenLockUtil.applyScreenLock(getActivity(), getString(R.string.multidevice_title),
getString(R.string.multidevice_this_creates_a_qr_code) + "\n\n" + getString(R.string.enter_system_secret_to_continue),
ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS)) {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.multidevice_title)
.setMessage(R.string.multidevice_this_creates_a_qr_code)
.setPositiveButton(R.string.perm_continue,
(dialog, which) -> ((ApplicationPreferencesActivity)getActivity()).showBackupProvider())
.setNegativeButton(R.string.cancel, null)
.show();
;
}
break;
case PREFERENCE_CATEGORY_ADVANCED:
fragment = new AdvancedPreferenceFragment();
break;
case PREFERENCE_CATEGORY_DONATE:
IntentUtils.showInBrowser(requireActivity(), "https://arcanechat.me/#contribute");
break;
case PREFERENCE_CATEGORY_HELP:
startActivity(new Intent(getActivity(), LocalHelpActivity.class));
break;
default:
throw new AssertionError();
case PREFERENCE_CATEGORY_NOTIFICATIONS:
NotificationManagerCompat notificationManager =
NotificationManagerCompat.from(getActivity());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
|| notificationManager.areNotificationsEnabled()) {
fragment = new NotificationsPreferenceFragment();
} else {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.notifications_disabled)
.setMessage(R.string.perm_explain_access_to_notifications_denied)
.setPositiveButton(
R.string.perm_continue,
(dialog, which) ->
getActivity()
.startActivity(
Permissions.getApplicationSettingsIntent(getActivity())))
.setNegativeButton(android.R.string.cancel, null)
.show();
}
break;
case PREFERENCE_CATEGORY_CONNECTIVITY:
startActivity(new Intent(getActivity(), ConnectivityActivity.class));
break;
case PREFERENCE_CATEGORY_APPEARANCE:
fragment = new AppearancePreferenceFragment();
break;
case PREFERENCE_CATEGORY_CHATS:
fragment = new ChatsPreferenceFragment();
break;
case PREFERENCE_CATEGORY_PRIVACY:
fragment = new PrivacyPreferenceFragment();
break;
case PREFERENCE_CATEGORY_MULTIDEVICE:
if (!ScreenLockUtil.applyScreenLock(
getActivity(),
getString(R.string.multidevice_title),
getString(R.string.multidevice_this_creates_a_qr_code)
+ "\n\n"
+ getString(R.string.enter_system_secret_to_continue),
screenLockLauncher)) {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.multidevice_title)
.setMessage(R.string.multidevice_this_creates_a_qr_code)
.setPositiveButton(
R.string.perm_continue,
(dialog, which) ->
((ApplicationPreferencesActivity) getActivity()).showBackupProvider())
.setNegativeButton(R.string.cancel, null)
.show();
;
}
break;
case PREFERENCE_CATEGORY_ADVANCED:
fragment = new AdvancedPreferenceFragment();
break;
case PREFERENCE_CATEGORY_DONATE:
IntentUtils.showInBrowser(requireActivity(), "https://arcanechat.me/#contribute");
break;
case PREFERENCE_CATEGORY_HELP:
startActivity(new Intent(getActivity(), LocalHelpActivity.class));
break;
default:
throw new AssertionError();
}
if (fragment != null) {
Bundle args = new Bundle();
fragment.setArguments(args);
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment, fragment);
fragmentTransaction.addToBackStack(null);
@@ -297,7 +319,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
public void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
}
@@ -2,8 +2,6 @@ package org.thoughtcrime.securesms;
import android.content.Intent;
import org.thoughtcrime.securesms.connect.DcHelper;
public class AttachContactActivity extends ContactSelectionActivity {
public static final String CONTACT_ID_EXTRA = "contact_id_extra";
@@ -1,13 +1,11 @@
package org.thoughtcrime.securesms;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import androidx.activity.EdgeToEdge;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
@@ -15,14 +13,11 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.WindowCompat;
import androidx.fragment.app.Fragment;
import java.lang.reflect.Field;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.lang.reflect.Field;
public abstract class BaseActionBarActivity extends AppCompatActivity {
private static final String TAG = BaseActionBarActivity.class.getSimpleName();
@@ -40,11 +35,13 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
// Only enable Edge-to-Edge if it is well supported
if (ViewUtil.isEdgeToEdgeSupported()) {
// docs says to use: WindowCompat.enableEdgeToEdge(getWindow());
// but it actually makes things worse, the next takes care of setting the 3-buttons navigation bar background
// but it actually makes things worse, the next takes care of setting the 3-buttons navigation
// bar background
EdgeToEdge.enable(this);
// force white text in status bar so it visible over background color
WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView()).setAppearanceLightStatusBars(false);
WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView())
.setAppearanceLightStatusBars(false);
}
}
@@ -68,14 +65,12 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
}
}
/**
* Modified from: http://stackoverflow.com/a/13098824
*/
/** Modified from: http://stackoverflow.com/a/13098824 */
private void forceOverflowMenu() {
try {
ViewConfiguration config = ViewConfiguration.get(this);
Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
if(menuKeyField != null) {
ViewConfiguration config = ViewConfiguration.get(this);
Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
if (menuKeyField != null) {
menuKeyField.setAccessible(true);
menuKeyField.setBoolean(config, false);
}
@@ -97,21 +92,18 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
} else if (item == searchItem) {
; // searchItem is just always visible
} else {
item.setVisible(!visible); // if search is shown, other items are hidden - and the other way round
item.setVisible(
!visible); // if search is shown, other items are hidden - and the other way round
}
}
}
protected <T extends Fragment> T initFragment(@IdRes int target,
@NonNull T fragment)
{
protected <T extends Fragment> T initFragment(@IdRes int target, @NonNull T fragment) {
return initFragment(target, fragment, null);
}
protected <T extends Fragment> T initFragment(@IdRes int target,
@NonNull T fragment,
@Nullable Bundle extras)
{
protected <T extends Fragment> T initFragment(
@IdRes int target, @NonNull T fragment, @Nullable Bundle extras) {
Bundle args = new Bundle();
if (extras != null) {
@@ -119,9 +111,10 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
}
fragment.setArguments(args);
getSupportFragmentManager().beginTransaction()
.replace(target, fragment)
.commitAllowingStateLoss();
getSupportFragmentManager()
.beginTransaction()
.replace(target, fragment)
.commitAllowingStateLoss();
return fragment;
}
}
@@ -6,42 +6,37 @@ import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import chat.delta.rpc.Rpc;
import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcMsg;
import java.util.HashSet;
import java.util.Set;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.HashSet;
import java.util.Set;
import chat.delta.rpc.Rpc;
public abstract class BaseConversationItem extends LinearLayout
implements BindableConversationItem
{
implements BindableConversationItem {
static final long PULSE_HIGHLIGHT_MILLIS = 500;
protected DcMsg messageRecord;
protected DcChat dcChat;
protected TextView bodyText;
protected DcMsg messageRecord;
protected DcChat dcChat;
protected TextView bodyText;
protected final Context context;
protected final DcContext dcContext;
protected final Context context;
protected final DcContext dcContext;
protected final Rpc rpc;
protected Recipient conversationRecipient;
protected Recipient conversationRecipient;
protected @NonNull Set<DcMsg> batchSelected = new HashSet<>();
protected @NonNull Set<DcMsg> batchSelected = new HashSet<>();
protected final PassthroughClickListener passthroughClickListener = new PassthroughClickListener();
protected final PassthroughClickListener passthroughClickListener =
new PassthroughClickListener();
public BaseConversationItem(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -50,32 +45,32 @@ public abstract class BaseConversationItem extends LinearLayout
this.rpc = DcHelper.getRpc(context);
}
protected void bind(@NonNull DcMsg messageRecord,
@NonNull DcChat dcChat,
@NonNull Set<DcMsg> batchSelected,
boolean pulseHighlight,
@NonNull Recipient conversationRecipient)
{
this.messageRecord = messageRecord;
this.dcChat = dcChat;
this.batchSelected = batchSelected;
this.conversationRecipient = conversationRecipient;
protected void bindPartial(
@NonNull DcMsg messageRecord,
@NonNull DcChat dcChat,
@NonNull Set<DcMsg> batchSelected,
boolean pulseHighlight,
@NonNull Recipient conversationRecipient) {
this.messageRecord = messageRecord;
this.dcChat = dcChat;
this.batchSelected = batchSelected;
this.conversationRecipient = conversationRecipient;
setInteractionState(messageRecord, pulseHighlight);
}
protected void setInteractionState(DcMsg messageRecord, boolean pulseHighlight) {
final int[] attributes = new int[] {
R.attr.conversation_item_background,
R.attr.conversation_item_background_animated,
};
final int[] attributes =
new int[] {
R.attr.conversation_item_background, R.attr.conversation_item_background_animated,
};
if (batchSelected.contains(messageRecord)) {
final TypedArray attrs = context.obtainStyledAttributes(attributes);
final TypedArray attrs = context.obtainStyledAttributes(attributes);
ViewUtil.setBackground(this, attrs.getDrawable(0));
attrs.recycle();
setSelected(true);
} else if (pulseHighlight) {
final TypedArray attrs = context.obtainStyledAttributes(attributes);
final TypedArray attrs = context.obtainStyledAttributes(attributes);
ViewUtil.setBackground(this, attrs.getDrawable(1));
attrs.recycle();
setSelected(true);
@@ -92,15 +87,16 @@ public abstract class BaseConversationItem extends LinearLayout
protected boolean shouldInterceptClicks(DcMsg messageRecord) {
return batchSelected.isEmpty()
&& (messageRecord.isFailed()
|| messageRecord.getInfoType() == DcMsg.DC_INFO_CHAT_E2EE
|| messageRecord.getInfoType() == DcMsg.DC_INFO_PROTECTION_ENABLED
|| messageRecord.getInfoType() == DcMsg.DC_INFO_INVALID_UNENCRYPTED_MAIL);
&& (messageRecord.isFailed()
|| messageRecord.getInfoType() == DcMsg.DC_INFO_CHAT_E2EE
|| messageRecord.getInfoType() == DcMsg.DC_INFO_PROTECTION_ENABLED
|| messageRecord.getInfoType() == DcMsg.DC_INFO_INVALID_UNENCRYPTED_MAIL);
}
protected void onAccessibilityClick() {}
protected class PassthroughClickListener implements View.OnLongClickListener, View.OnClickListener {
protected class PassthroughClickListener
implements View.OnLongClickListener, View.OnClickListener {
@Override
public boolean onLongClick(View v) {
@@ -126,6 +122,8 @@ public abstract class BaseConversationItem extends LinearLayout
public void onClick(View v) {
if (!shouldInterceptClicks(messageRecord) && parent != null) {
// The click workaround on ConversationItem shall be revised.
// In fact, it is probably better rethinking accessibility approach for the items.
if (batchSelected.isEmpty() && Util.isTouchExplorationEnabled(context)) {
BaseConversationItem.this.onAccessibilityClick();
}
@@ -135,13 +133,15 @@ public abstract class BaseConversationItem extends LinearLayout
TextView detailsText = view.findViewById(R.id.details_text);
detailsText.setText(messageRecord.getError());
AlertDialog d = new AlertDialog.Builder(context)
AlertDialog d =
new AlertDialog.Builder(context)
.setView(view)
.setTitle(R.string.error)
.setPositiveButton(R.string.ok, null)
.create();
d.show();
} else if (messageRecord.getInfoType() == DcMsg.DC_INFO_CHAT_E2EE || messageRecord.getInfoType() == DcMsg.DC_INFO_PROTECTION_ENABLED) {
} else if (messageRecord.getInfoType() == DcMsg.DC_INFO_CHAT_E2EE
|| messageRecord.getInfoType() == DcMsg.DC_INFO_PROTECTION_ENABLED) {
DcHelper.showProtectionEnabledDialog(context);
} else if (messageRecord.getInfoType() == DcMsg.DC_INFO_INVALID_UNENCRYPTED_MAIL) {
DcHelper.showInvalidUnencryptedDialog(context);
@@ -1,14 +1,14 @@
package org.thoughtcrime.securesms;
import androidx.recyclerview.widget.RecyclerView;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public abstract class BaseConversationListAdapter<T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T> {
protected final Set<Long> batchSet = Collections.synchronizedSet(new HashSet<Long>());
protected boolean batchMode = false;
public abstract class BaseConversationListAdapter<T extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<T> {
protected final Set<Long> batchSet = Collections.synchronizedSet(new HashSet<Long>());
protected boolean batchMode = false;
public abstract void selectAllThreads();
@@ -17,7 +17,6 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
@@ -27,36 +26,36 @@ import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
import androidx.fragment.app.Fragment;
import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcContext;
import com.google.android.material.snackbar.Snackbar;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.connect.DirectShareUtil;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ShareUtil;
import org.thoughtcrime.securesms.util.SendRelayedMessageUtil;
import org.thoughtcrime.securesms.util.ShareUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask;
import org.thoughtcrime.securesms.util.views.ProgressDialog;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public abstract class BaseConversationListFragment extends Fragment implements ActionMode.Callback {
protected ActionMode actionMode;
protected PulsingFloatingActionButton fab;
protected abstract boolean offerToArchive();
protected abstract void setFabVisibility(boolean isActionMode);
protected abstract BaseConversationListAdapter getListAdapter();
protected void onItemClick(long chatId) {
if (actionMode == null) {
((ConversationSelectedListener) requireActivity()).onCreateConversation((int)chatId);
((ConversationSelectedListener) requireActivity()).onCreateConversation((int) chatId);
} else {
BaseConversationListAdapter adapter = getListAdapter();
adapter.toggleThreadInBatchSet(chatId);
@@ -71,8 +70,9 @@ public abstract class BaseConversationListFragment extends Fragment implements A
adapter.notifyDataSetChanged();
}
}
public void onItemLongClick(long chatId) {
actionMode = ((AppCompatActivity)requireActivity()).startSupportActionMode(this);
actionMode = ((AppCompatActivity) requireActivity()).startSupportActionMode(this);
if (actionMode != null) {
getListAdapter().initializeBatchMode(true);
@@ -89,36 +89,54 @@ public abstract class BaseConversationListFragment extends Fragment implements A
Intent intent = new Intent(getActivity(), NewConversationActivity.class);
if (isRelayingMessageContent(getActivity())) {
if (isActionMode) {
fab.setOnClickListener(v -> {
final Set<Long> selectedChats = getListAdapter().getBatchSelections();
ArrayList<Uri> uris = getSharedUris(getActivity());
String message;
if (isForwarding(getActivity())) {
message = String.format(Util.getLocale(), getString(R.string.ask_forward_multiple), selectedChats.size());
} else if (!uris.isEmpty()) {
message = String.format(Util.getLocale(), getString(R.string.ask_send_files_to_selected_chats), uris.size(), selectedChats.size());
} else {
message = String.format(Util.getLocale(), getString(R.string.share_text_multiple_chats), selectedChats.size(), getSharedText(getActivity()));
}
fab.setOnClickListener(
v -> {
final Set<Long> selectedChats = getListAdapter().getBatchSelections();
ArrayList<Uri> uris = getSharedUris(getActivity());
String message;
if (isForwarding(getActivity())) {
message =
String.format(
Util.getLocale(),
getString(R.string.ask_forward_multiple),
selectedChats.size());
} else if (!uris.isEmpty()) {
message =
String.format(
Util.getLocale(),
getString(R.string.ask_send_files_to_selected_chats),
uris.size(),
selectedChats.size());
} else {
message =
String.format(
Util.getLocale(),
getString(R.string.share_text_multiple_chats),
selectedChats.size(),
getSharedText(getActivity()));
}
Context context = getContext();
if (context != null) {
if (SendRelayedMessageUtil.containsVideoType(context, uris)) {
message += "\n\n" + getString(R.string.videos_sent_without_recoding);
}
new AlertDialog.Builder(context)
Context context = getContext();
if (context != null) {
if (SendRelayedMessageUtil.containsVideoType(context, uris)) {
message += "\n\n" + getString(R.string.videos_sent_without_recoding);
}
new AlertDialog.Builder(context)
.setMessage(message)
.setCancelable(false)
.setNegativeButton(android.R.string.cancel, ((dialog, which) -> {}))
.setPositiveButton(R.string.menu_send, (dialog, which) -> {
SendRelayedMessageUtil.immediatelyRelay(getActivity(), selectedChats.toArray(new Long[selectedChats.size()]));
actionMode.finish();
actionMode = null;
getActivity().finish();
})
.setPositiveButton(
R.string.menu_send,
(dialog, which) -> {
SendRelayedMessageUtil.immediatelyRelay(
getActivity(), selectedChats.toArray(new Long[selectedChats.size()]));
actionMode.finish();
actionMode = null;
getActivity().finish();
})
.show();
}
});
}
});
} else {
acquireRelayMessageContent(getActivity(), intent);
fab.setOnClickListener(v -> requireActivity().startActivity(intent));
@@ -132,8 +150,8 @@ public abstract class BaseConversationListFragment extends Fragment implements A
DcContext dcContext = DcHelper.getContext(requireActivity());
final Set<Long> selectedChats = getListAdapter().getBatchSelections();
for (long chatId : selectedChats) {
DcChat dcChat = dcContext.getChat((int)chatId);
if (dcChat.getVisibility()!=DcChat.DC_CHAT_VISIBILITY_PINNED) {
DcChat dcChat = dcContext.getChat((int) chatId);
if (dcChat.getVisibility() != DcChat.DC_CHAT_VISIBILITY_PINNED) {
return true;
}
}
@@ -144,7 +162,7 @@ public abstract class BaseConversationListFragment extends Fragment implements A
DcContext dcContext = DcHelper.getContext(requireActivity());
final Set<Long> selectedChats = getListAdapter().getBatchSelections();
for (long chatId : selectedChats) {
DcChat dcChat = dcContext.getChat((int)chatId);
DcChat dcChat = dcContext.getChat((int) chatId);
if (!dcChat.isMuted()) {
return true;
}
@@ -153,12 +171,14 @@ public abstract class BaseConversationListFragment extends Fragment implements A
}
private void handlePinAllSelected() {
final DcContext dcContext = DcHelper.getContext(requireActivity());
final Set<Long> selectedConversations = new HashSet<Long>(getListAdapter().getBatchSelections());
final DcContext dcContext = DcHelper.getContext(requireActivity());
final Set<Long> selectedConversations =
new HashSet<Long>(getListAdapter().getBatchSelections());
boolean doPin = areSomeSelectedChatsUnpinned();
for (long chatId : selectedConversations) {
dcContext.setChatVisibility((int)chatId,
doPin? DcChat.DC_CHAT_VISIBILITY_PINNED : DcChat.DC_CHAT_VISIBILITY_NORMAL);
dcContext.setChatVisibility(
(int) chatId,
doPin ? DcChat.DC_CHAT_VISIBILITY_PINNED : DcChat.DC_CHAT_VISIBILITY_NORMAL);
}
if (actionMode != null) {
actionMode.finish();
@@ -167,23 +187,26 @@ public abstract class BaseConversationListFragment extends Fragment implements A
}
private void handleMuteAllSelected() {
final DcContext dcContext = DcHelper.getContext(requireActivity());
final Set<Long> selectedConversations = new HashSet<Long>(getListAdapter().getBatchSelections());
final DcContext dcContext = DcHelper.getContext(requireActivity());
final Set<Long> selectedConversations =
new HashSet<Long>(getListAdapter().getBatchSelections());
if (areSomeSelectedChatsUnmuted()) {
MuteDialog.show(getActivity(), duration -> {
for (long chatId : selectedConversations) {
dcContext.setChatMuteDuration((int)chatId, duration);
}
MuteDialog.show(
getActivity(),
duration -> {
for (long chatId : selectedConversations) {
dcContext.setChatMuteDuration((int) chatId, duration);
}
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
});
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
});
} else {
// unmute
for (long chatId : selectedConversations) {
dcContext.setChatMuteDuration((int)chatId, 0);
dcContext.setChatMuteDuration((int) chatId, 0);
}
if (actionMode != null) {
@@ -194,10 +217,11 @@ public abstract class BaseConversationListFragment extends Fragment implements A
}
private void handleMarknoticedSelected() {
final DcContext dcContext = DcHelper.getContext(requireActivity());
final Set<Long> selectedConversations = new HashSet<Long>(getListAdapter().getBatchSelections());
final DcContext dcContext = DcHelper.getContext(requireActivity());
final Set<Long> selectedConversations =
new HashSet<Long>(getListAdapter().getBatchSelections());
for (long chatId : selectedConversations) {
dcContext.marknoticedChat((int)chatId);
dcContext.marknoticedChat((int) chatId);
}
if (actionMode != null) {
actionMode.finish();
@@ -207,22 +231,21 @@ public abstract class BaseConversationListFragment extends Fragment implements A
@SuppressLint("StaticFieldLeak")
private void handleArchiveAllSelected() {
final DcContext dcContext = DcHelper.getContext(requireActivity());
final Set<Long> selectedConversations = new HashSet<Long>(getListAdapter().getBatchSelections());
final boolean archive = offerToArchive();
final DcContext dcContext = DcHelper.getContext(requireActivity());
final Set<Long> selectedConversations =
new HashSet<Long>(getListAdapter().getBatchSelections());
final boolean archive = offerToArchive();
int snackBarTitleId;
if (archive) snackBarTitleId = R.plurals.chat_archived;
else snackBarTitleId = R.plurals.chat_unarchived;
else snackBarTitleId = R.plurals.chat_unarchived;
int count = selectedConversations.size();
int count = selectedConversations.size();
String snackBarTitle = getResources().getQuantityString(snackBarTitleId, count, count);
new SnackbarAsyncTask<Void>(getView(), snackBarTitle,
getString(R.string.undo),
Snackbar.LENGTH_LONG, true)
{
new SnackbarAsyncTask<Void>(
getView(), snackBarTitle, getString(R.string.undo), Snackbar.LENGTH_LONG, true) {
@Override
protected void onPostExecute(Void result) {
@@ -237,16 +260,18 @@ public abstract class BaseConversationListFragment extends Fragment implements A
@Override
protected void executeAction(@Nullable Void parameter) {
for (long chatId : selectedConversations) {
dcContext.setChatVisibility((int)chatId,
archive? DcChat.DC_CHAT_VISIBILITY_ARCHIVED : DcChat.DC_CHAT_VISIBILITY_NORMAL);
dcContext.setChatVisibility(
(int) chatId,
archive ? DcChat.DC_CHAT_VISIBILITY_ARCHIVED : DcChat.DC_CHAT_VISIBILITY_NORMAL);
}
}
@Override
protected void reverseAction(@Nullable Void parameter) {
for (long threadId : selectedConversations) {
dcContext.setChatVisibility((int)threadId,
archive? DcChat.DC_CHAT_VISIBILITY_NORMAL : DcChat.DC_CHAT_VISIBILITY_ARCHIVED);
dcContext.setChatVisibility(
(int) threadId,
archive ? DcChat.DC_CHAT_VISIBILITY_NORMAL : DcChat.DC_CHAT_VISIBILITY_ARCHIVED);
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@@ -262,51 +287,73 @@ public abstract class BaseConversationListFragment extends Fragment implements A
final String alertText;
if (chatsCount == 1) {
long chatId = selectedChats.iterator().next();
alertText = activity.getResources().getString(R.string.ask_delete_named_chat, dcContext.getChat((int)chatId).getName());
alertText =
activity
.getResources()
.getString(R.string.ask_delete_named_chat, dcContext.getChat((int) chatId).getName());
} else {
alertText = activity.getResources().getQuantityString(R.plurals.ask_delete_chat, chatsCount, chatsCount);
alertText =
activity
.getResources()
.getQuantityString(R.plurals.ask_delete_chat, chatsCount, chatsCount);
}
String alertButton = getString(R.string.delete_for_me);
for (long chatId : selectedChats) {
if (dcContext.getChat((int) chatId).shallLeaveBeforeDelete(dcContext)) {
alertButton = getString(R.string.menu_leave_and_delete);
break;
}
}
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
alert.setMessage(alertText);
alert.setCancelable(true);
alert.setPositiveButton(R.string.delete, (dialog, which) -> {
alert.setPositiveButton(
alertButton,
(dialog, which) -> {
if (!selectedChats.isEmpty()) {
new AsyncTask<Void, Void, Void>() {
private ProgressDialog dialog;
if (!selectedChats.isEmpty()) {
new AsyncTask<Void, Void, Void>() {
private ProgressDialog dialog;
@Override
protected void onPreExecute() {
dialog =
ProgressDialog.show(
getActivity(),
"",
requireActivity().getString(R.string.one_moment),
true,
false);
}
@Override
protected void onPreExecute() {
dialog = ProgressDialog.show(getActivity(),
"",
requireActivity().getString(R.string.one_moment),
true, false);
@Override
protected Void doInBackground(Void... params) {
int accountId = dcContext.getAccountId();
for (long chatId : selectedChats) {
DcHelper.getNotificationCenter(requireContext())
.removeNotifications(accountId, (int) chatId);
if (dcContext.getChat((int) chatId).shallLeaveBeforeDelete(dcContext)) {
dcContext.removeContactFromChat((int) chatId, DcContact.DC_CONTACT_ID_SELF);
}
dcContext.deleteChat((int) chatId);
DirectShareUtil.clearShortcut(requireContext(), (int) chatId);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
dialog.dismiss();
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
protected Void doInBackground(Void... params) {
int accountId = dcContext.getAccountId();
for (long chatId : selectedChats) {
DcHelper.getNotificationCenter(requireContext()).removeNotifications(accountId, (int) chatId);
dcContext.deleteChat((int) chatId);
DirectShareUtil.clearShortcut(requireContext(), (int) chatId);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
dialog.dismiss();
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
});
});
alert.setNegativeButton(android.R.string.cancel, null);
AlertDialog dialog = alert.show();
@@ -331,22 +378,30 @@ public abstract class BaseConversationListFragment extends Fragment implements A
intent.putExtra(ShareActivity.EXTRA_CHAT_ID, chat.getId());
Recipient recipient = new Recipient(activity, chat);
Util.runOnAnyBackgroundThread(() -> {
Bitmap avatar = DirectShareUtil.getIconForShortcut(activity, recipient);
ShortcutInfoCompat shortcutInfoCompat = new ShortcutInfoCompat.Builder(activity, "chat-" + dcContext.getAccountId() + "-" + chat.getId())
.setShortLabel(chat.getName())
.setIcon(IconCompat.createWithAdaptiveBitmap(avatar))
.setIntent(intent)
.build();
Util.runOnMain(() -> {
if (!ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfoCompat, null)) {
Toast.makeText(activity, "ErrAddToHomescreen: requestPinShortcut() failed", Toast.LENGTH_LONG).show();
} else if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
});
});
Util.runOnAnyBackgroundThread(
() -> {
Bitmap avatar = DirectShareUtil.getIconForShortcut(activity, recipient);
ShortcutInfoCompat shortcutInfoCompat =
new ShortcutInfoCompat.Builder(
activity, "chat-" + dcContext.getAccountId() + "-" + chat.getId())
.setShortLabel(chat.getName())
.setIcon(IconCompat.createWithAdaptiveBitmap(avatar))
.setIntent(intent)
.build();
Util.runOnMain(
() -> {
if (!ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfoCompat, null)) {
Toast.makeText(
activity,
"ErrAddToHomescreen: requestPinShortcut() failed",
Toast.LENGTH_LONG)
.show();
} else if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
});
});
}
private void updateActionModeItems(Menu menu) {
@@ -356,11 +411,11 @@ public abstract class BaseConversationListFragment extends Fragment implements A
menu.findItem(R.id.menu_add_to_home_screen).setVisible(selectedCount == 1);
MenuItem archiveItem = menu.findItem(R.id.menu_archive_selected);
if (offerToArchive()) {
archiveItem.setIcon(R.drawable.ic_archive_white_24dp);
archiveItem.setTitle(R.string.menu_archive_chat);
archiveItem.setIcon(R.drawable.ic_archive_white_24dp);
archiveItem.setTitle(R.string.menu_archive_chat);
} else {
archiveItem.setIcon(R.drawable.ic_unarchive_white_24dp);
archiveItem.setTitle(R.string.menu_unarchive_chat);
archiveItem.setIcon(R.drawable.ic_unarchive_white_24dp);
archiveItem.setTitle(R.string.menu_unarchive_chat);
}
MenuItem pinItem = menu.findItem(R.id.menu_pin_selected);
if (areSomeSelectedChatsUnpinned()) {
@@ -383,7 +438,8 @@ public abstract class BaseConversationListFragment extends Fragment implements A
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
if (isRelayingMessageContent(getActivity())) {
if (ShareUtil.getSharedContactId(getActivity()) != 0) {
return false; // no sharing of a contact to multiple recipients at the same time, we can reconsider when that becomes a real-world need
return false; // no sharing of a contact to multiple recipients at the same time, we can
// reconsider when that becomes a real-world need
}
Context context = getContext();
if (context != null) {
@@ -451,6 +507,7 @@ public abstract class BaseConversationListFragment extends Fragment implements A
public interface ConversationSelectedListener {
void onCreateConversation(int chatId);
void onSwitchToArchive();
}
}
@@ -2,22 +2,24 @@ package org.thoughtcrime.securesms;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcMsg;
import java.util.Set;
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
import org.thoughtcrime.securesms.components.audioplay.AudioView;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.util.Set;
public interface BindableConversationItem extends Unbindable {
void bind(@NonNull DcMsg messageRecord,
@NonNull DcChat dcChat,
@NonNull GlideRequests glideRequests,
@NonNull Set<DcMsg> batchSelected,
@NonNull Recipient recipients,
boolean pulseHighlight);
void bind(
@NonNull DcMsg messageRecord,
@NonNull DcChat dcChat,
@NonNull GlideRequests glideRequests,
@NonNull Set<DcMsg> batchSelected,
@NonNull Recipient recipients,
boolean pulseHighlight,
@Nullable AudioPlaybackViewModel playbackViewModel,
AudioView.OnActionListener audioPlayPauseListener);
DcMsg getMessageRecord();
@@ -25,9 +27,15 @@ public interface BindableConversationItem extends Unbindable {
interface EventListener {
void onQuoteClicked(DcMsg messageRecord);
void onJumpToOriginalClicked(DcMsg messageRecord);
void onShowFullClicked(DcMsg messageRecord);
void onDownloadClicked(DcMsg messageRecord);
void onReactionClicked(DcMsg messageRecord);
void onStickerClicked(DcMsg messageRecord);
}
}
@@ -1,19 +1,18 @@
package org.thoughtcrime.securesms;
import androidx.annotation.NonNull;
import com.b44t.messenger.DcLot;
import java.util.Set;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.mms.GlideRequests;
import java.util.Set;
public interface BindableConversationListItem extends Unbindable {
public void bind(@NonNull ThreadRecord thread,
int msgId,
@NonNull DcLot dcSummary,
@NonNull GlideRequests glideRequests,
@NonNull Set<Long> selectedThreads, boolean batchMode);
public void bind(
@NonNull ThreadRecord thread,
int msgId,
@NonNull DcLot dcSummary,
@NonNull GlideRequests glideRequests,
@NonNull Set<Long> selectedThreads,
boolean batchMode);
}
@@ -6,7 +6,6 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
@@ -14,10 +13,8 @@ import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import org.thoughtcrime.securesms.connect.DcContactsLoader;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
@@ -39,17 +36,18 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: finish(); return true;
case android.R.id.home:
finish();
return true;
}
return false;
}
public static class BlockedAndShareContactsFragment
extends Fragment
implements LoaderManager.LoaderCallbacks<DcContactsLoader.Ret>,
DcEventCenter.DcEventDelegate, ContactSelectionListAdapter.ItemClickListener {
public static class BlockedAndShareContactsFragment extends Fragment
implements LoaderManager.LoaderCallbacks<DcContactsLoader.Ret>,
DcEventCenter.DcEventDelegate,
ContactSelectionListAdapter.ItemClickListener {
private RecyclerView recyclerView;
private TextView emptyStateView;
@@ -57,7 +55,7 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false);
recyclerView = ViewUtil.findById(view, R.id.recycler_view);
recyclerView = ViewUtil.findById(view, R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
// add padding to avoid content hidden behind system bars
@@ -81,11 +79,8 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
}
private void initializeAdapter() {
ContactSelectionListAdapter adapter = new ContactSelectionListAdapter(getActivity(),
GlideApp.with(this),
this,
false,
false);
ContactSelectionListAdapter adapter =
new ContactSelectionListAdapter(getActivity(), GlideApp.with(this), this, false, false);
recyclerView.setAdapter(adapter);
}
@@ -112,7 +107,7 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
@Override
public void handleEvent(@NonNull DcEvent event) {
if (event.getId()==DcContext.DC_EVENT_CONTACTS_CHANGED) {
if (event.getId() == DcContext.DC_EVENT_CONTACTS_CHANGED) {
restartLoader();
}
}
@@ -128,10 +123,12 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
@Override
public void onItemClick(ContactSelectionListItem item, boolean handleActionMode) {
new AlertDialog.Builder(getActivity())
.setMessage(R.string.ask_unblock_contact)
.setCancelable(true)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.menu_unblock_contact, (dialog, which) -> unblockContact(item.getContactId())).show();
.setMessage(R.string.ask_unblock_contact)
.setCancelable(true)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(
R.string.menu_unblock_contact, (dialog, which) -> unblockContact(item.getContactId()))
.show();
}
private void unblockContact(int contactId) {
@@ -143,5 +140,4 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
@Override
public void onItemLongClick(ContactSelectionListItem view) {}
}
}
@@ -2,12 +2,9 @@ package org.thoughtcrime.securesms;
import android.os.Bundle;
import android.view.Menu;
import androidx.annotation.NonNull;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
@@ -35,8 +32,10 @@ public class ConnectivityActivity extends WebViewActivity implements DcEventCent
}
private void refresh() {
final String connectivityHtml = DcHelper.getContext(this).getConnectivityHtml()
.replace("</style>", " html { color-scheme: dark light; }</style>");
final String connectivityHtml =
DcHelper.getContext(this)
.getConnectivityHtml()
.replace("</style>", " html { color-scheme: dark light; }</style>");
webView.loadDataWithBaseURL(null, connectivityHtml, "text/html", "utf-8", null);
}
@@ -21,7 +21,6 @@ import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import java.util.ArrayList;
import java.util.List;
@@ -29,11 +28,11 @@ import java.util.List;
* Activity container for selecting a list of contacts.
*
* @author Moxie Marlinspike
*
*/
public class ContactMultiSelectionActivity extends ContactSelectionActivity {
public static final String CONTACTS_EXTRA = "contacts_extra";
public static final String DESELECTED_CONTACTS_EXTRA = "deselected_contacts_extra";
@Override
protected void onCreate(Bundle icicle, boolean ready) {
@@ -71,7 +70,10 @@ 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);
}
}
@@ -18,7 +18,6 @@ package org.thoughtcrime.securesms;
import android.os.Bundle;
import android.view.MenuItem;
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -27,11 +26,9 @@ import org.thoughtcrime.securesms.util.ViewUtil;
* Base activity container for selecting a list of contacts.
*
* @author Moxie Marlinspike
*
*/
public abstract class ContactSelectionActivity extends PassphraseRequiredActionBarActivity
implements ContactSelectionListFragment.OnContactSelectedListener
{
implements ContactSelectionListFragment.OnContactSelectedListener {
private static final String TAG = ContactSelectionActivity.class.getSimpleName();
protected ContactSelectionListFragment contactsFragment;
@@ -61,7 +58,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
this.toolbar = ViewUtil.findById(this, R.id.toolbar);
setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowTitleEnabled(false);
getSupportActionBar().setIcon(null);
@@ -69,7 +66,9 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
}
private void initializeResources() {
contactsFragment = (ContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
contactsFragment =
(ContactSelectionListFragment)
getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
contactsFragment.setOnContactSelectedListener(this);
}
@@ -81,8 +80,9 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case android.R.id.home: super.onBackPressed(); return true;
if (item.getItemId() == android.R.id.home) {
getOnBackPressedDispatcher().onBackPressed();
return true;
}
return false;
@@ -16,12 +16,12 @@
*/
package org.thoughtcrime.securesms;
import static org.thoughtcrime.securesms.util.ShareUtil.isRelayingMessageContent;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -29,7 +29,9 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
@@ -39,11 +41,14 @@ import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.thoughtcrime.securesms.connect.DcContactsLoader;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
@@ -55,45 +60,52 @@ import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* Fragment for selecting a one or more contacts from a list.
*
* @author Moxie Marlinspike
*
*/
public class ContactSelectionListFragment extends Fragment
implements LoaderManager.LoaderCallbacks<DcContactsLoader.Ret>,
DcEventCenter.DcEventDelegate
{
public class ContactSelectionListFragment extends Fragment
implements LoaderManager.LoaderCallbacks<DcContactsLoader.Ret>, DcEventCenter.DcEventDelegate {
private static final String TAG = ContactSelectionListFragment.class.getSimpleName();
public static final String MULTI_SELECT = "multi_select";
public static final String MULTI_SELECT = "multi_select";
public static final String SELECT_UNENCRYPTED_EXTRA = "select_unencrypted_extra";
public static final String ALLOW_CREATION = "allow_creation";
public static final String PRESELECTED_CONTACTS = "preselected_contacts";
public static final int CONTACT_ADDR_RESULT_CODE = 61123;
private DcContext dcContext;
private Set<Integer> selectedContacts;
private Set<Integer> selectedContacts;
private Set<Integer> deselectedContacts;
private OnContactSelectedListener onContactSelectedListener;
private String cursorFilter;
private RecyclerView recyclerView;
private ActionMode actionMode;
private ActionMode.Callback actionModeCallback;
private String cursorFilter;
private RecyclerView recyclerView;
private TextView emptyView;
private ActionMode actionMode;
private ActionMode.Callback actionModeCallback;
private ActivityResultLauncher<Intent> newContactLauncher;
@Override
public void onActivityCreated(Bundle icicle) {
super.onActivityCreated(icicle);
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
dcContext = DcHelper.getContext(getActivity());
DcHelper.getEventCenter(getActivity()).addObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
initializeCursor();
dcContext = DcHelper.getContext(requireContext());
newContactLauncher =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
int contactId =
result.getData().getIntExtra(NewContactActivity.CONTACT_ID_EXTRA, 0);
if (contactId != 0) {
selectedContacts.add(contactId);
deselectedContacts.remove(contactId);
}
LoaderManager.getInstance(this)
.restartLoader(0, null, ContactSelectionListFragment.this);
}
});
}
@Override
@@ -109,71 +121,75 @@ public class ContactSelectionListFragment extends Fragment
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false);
recyclerView = ViewUtil.findById(view, R.id.recycler_view);
recyclerView = ViewUtil.findById(view, R.id.recycler_view);
emptyView = ViewUtil.findById(view, android.R.id.empty);
// add padding to avoid content hidden behind system bars
ViewUtil.applyWindowInsets(recyclerView, true, false, true, true);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
actionModeCallback = new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.contact_list, menu);
setCorrectMenuVisibility(menu);
actionMode.setTitle("1");
return true;
}
actionModeCallback =
new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.contact_list, menu);
menu.findItem(R.id.menu_delete_selected).setVisible(!isMulti());
updateActionModeState(actionMode);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
return false;
}
@Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
int itemId = menuItem.getItemId();
if (itemId == R.id.menu_select_all) {
handleSelectAll();
return true;
} else if (itemId == R.id.menu_view_profile) {
handleViewProfile();
return true;
} else if (itemId == R.id.menu_delete_selected) {
handleDeleteSelected();
return true;
}
return false;
}
@Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
int itemId = menuItem.getItemId();
if (itemId == R.id.menu_select_all) {
handleSelectAll();
return true;
} else if (itemId == R.id.menu_view_profile) {
handleViewProfile();
return true;
} else if (itemId == R.id.menu_delete_selected) {
handleDeleteSelected();
return true;
}
return false;
}
@Override
public void onDestroyActionMode(ActionMode actionMode) {
ContactSelectionListFragment.this.actionMode = null;
getContactSelectionListAdapter().resetActionModeSelection();
}
};
@Override
public void onDestroyActionMode(ActionMode actionMode) {
ContactSelectionListFragment.this.actionMode = null;
getContactSelectionListAdapter().resetActionModeSelection();
}
};
DcHelper.getEventCenter(requireActivity())
.addObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
initializeCursor();
return view;
}
private void handleSelectAll() {
getContactSelectionListAdapter().selectAll();
updateActionModeTitle();
updateActionModeState(actionMode);
}
private void updateActionModeTitle() {
actionMode.setTitle(String.valueOf(getContactSelectionListAdapter().getActionModeSelection().size()));
}
private void setCorrectMenuVisibility(Menu menu) {
ContactSelectionListAdapter adapter = getContactSelectionListAdapter();
if (adapter.getActionModeSelection().size() > 1) {
menu.findItem(R.id.menu_view_profile).setVisible(false);
private void updateActionModeState(ActionMode actionMode) {
int size = getContactSelectionListAdapter().getActionModeSelection().size();
if (size == 0) {
actionMode.finish();
} else {
menu.findItem(R.id.menu_view_profile).setVisible(true);
actionMode.getMenu().findItem(R.id.menu_view_profile).setVisible(size == 1);
actionMode.setTitle(String.valueOf(size));
}
}
@@ -189,22 +205,28 @@ public class ContactSelectionListFragment extends Fragment
}
private void handleDeleteSelected() {
AlertDialog dialog = new AlertDialog.Builder(getActivity())
.setMessage(R.string.ask_delete_contacts)
.setPositiveButton(R.string.delete, (d, i) -> {
ContactSelectionListAdapter adapter = getContactSelectionListAdapter();
final SparseIntArray actionModeSelection = adapter.getActionModeSelection().clone();
new Thread(() -> {
for (int index = 0; index < actionModeSelection.size(); index++) {
int contactId = actionModeSelection.valueAt(index);
dcContext.deleteContact(contactId);
}
}).start();
adapter.resetActionModeSelection();
actionMode.finish();
})
.setNegativeButton(R.string.cancel, null)
.show();
AlertDialog dialog =
new AlertDialog.Builder(getActivity())
.setMessage(R.string.ask_delete_contacts)
.setPositiveButton(
R.string.delete,
(d, i) -> {
ContactSelectionListAdapter adapter = getContactSelectionListAdapter();
final SparseIntArray actionModeSelection =
adapter.getActionModeSelection().clone();
new Thread(
() -> {
for (int index = 0; index < actionModeSelection.size(); index++) {
int contactId = actionModeSelection.valueAt(index);
dcContext.deleteContact(contactId);
}
})
.start();
adapter.resetActionModeSelection();
actionMode.finish();
})
.setNegativeButton(R.string.cancel, null)
.show();
Util.redPositiveButton(dialog);
}
@@ -213,7 +235,8 @@ public class ContactSelectionListFragment extends Fragment
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
public void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
@@ -226,6 +249,15 @@ public class ContactSelectionListFragment extends Fragment
return selected;
}
public @NonNull List<Integer> getDeselectedContacts() {
List<Integer> deselected = new LinkedList<>();
if (deselectedContacts != null) {
deselected.addAll(deselectedContacts);
}
return deselected;
}
private boolean isMulti() {
return getActivity().getIntent().getBooleanExtra(MULTI_SELECT, false);
}
@@ -235,14 +267,14 @@ public class ContactSelectionListFragment extends Fragment
}
private void initializeCursor() {
ContactSelectionListAdapter adapter = new ContactSelectionListAdapter(getActivity(),
GlideApp.with(this),
new ListClickListener(),
isMulti(),
true);
ContactSelectionListAdapter adapter =
new ContactSelectionListAdapter(
getActivity(), GlideApp.with(this), new ListClickListener(), isMulti(), true);
selectedContacts = adapter.getSelectedContacts();
ArrayList<Integer> preselectedContacts = getActivity().getIntent().getIntegerArrayListExtra(PRESELECTED_CONTACTS);
if(preselectedContacts!=null) {
deselectedContacts = new HashSet<>();
ArrayList<Integer> preselectedContacts =
getActivity().getIntent().getIntegerArrayListExtra(PRESELECTED_CONTACTS);
if (preselectedContacts != null) {
selectedContacts.addAll(preselectedContacts);
}
recyclerView.setAdapter(adapter);
@@ -257,16 +289,34 @@ public class ContactSelectionListFragment extends Fragment
public Loader<DcContactsLoader.Ret> onCreateLoader(int id, Bundle args) {
final boolean allowCreation = getActivity().getIntent().getBooleanExtra(ALLOW_CREATION, true);
final boolean addCreateContactLink = allowCreation && isUnencrypted();
final boolean addCreateGroupLinks = allowCreation && !isRelayingMessageContent(getActivity()) && !isMulti();
final boolean addCreateGroupLinks =
allowCreation && !isRelayingMessageContent(getActivity()) && !isMulti();
final boolean addScanQRLink = allowCreation && !isMulti();
final int listflags = DcContext.DC_GCL_ADD_SELF | (isUnencrypted()? DcContext.DC_GCL_ADDRESS : 0);
return new DcContactsLoader(getActivity(), listflags, cursorFilter, addCreateGroupLinks, addCreateContactLink, addScanQRLink, false);
final int listflags =
DcContext.DC_GCL_ADD_SELF | (isUnencrypted() ? DcContext.DC_GCL_ADDRESS : 0);
return new DcContactsLoader(
getActivity(),
listflags,
cursorFilter,
addCreateGroupLinks,
addCreateContactLink,
addScanQRLink,
false);
}
@Override
public void onLoadFinished(Loader<DcContactsLoader.Ret> loader, DcContactsLoader.Ret data) {
((ContactSelectionListAdapter) recyclerView.getAdapter()).changeData(data);
ContactSelectionListAdapter adapter = (ContactSelectionListAdapter) recyclerView.getAdapter();
adapter.changeData(data);
if (emptyView != null) {
if (adapter.getItemCount() > 0 || TextUtils.isEmpty(cursorFilter)) {
emptyView.setVisibility(View.GONE);
} else {
emptyView.setText(getString(R.string.search_no_result_for_x, cursorFilter));
emptyView.setVisibility(View.VISIBLE);
}
}
}
@Override
@@ -276,18 +326,14 @@ public class ContactSelectionListFragment extends Fragment
private class ListClickListener implements ContactSelectionListAdapter.ItemClickListener {
@Override
public void onItemClick(ContactSelectionListItem contact, boolean handleActionMode)
{
public void onItemClick(ContactSelectionListItem contact, boolean handleActionMode) {
if (handleActionMode) {
if (actionMode != null) {
Menu menu = actionMode.getMenu();
setCorrectMenuVisibility(menu);
updateActionModeTitle();
finishActionModeIfSelectionIsEmpty();
updateActionModeState(actionMode);
}
return;
}
int contactId = contact.getSpecialId();
int contactId = contact.getSpecialId();
if (!isMulti() || !selectedContacts.contains(contactId)) {
if (contactId == DcContact.DC_CONTACT_ID_NEW_CLASSIC_CONTACT) {
Intent intent = new Intent(getContext(), NewContactActivity.class);
@@ -295,7 +341,7 @@ public class ContactSelectionListFragment extends Fragment
intent.putExtra(NewContactActivity.ADDR_EXTRA, cursorFilter);
}
if (isMulti()) {
startActivityForResult(intent, CONTACT_ADDR_RESULT_CODE);
newContactLauncher.launch(intent);
} else {
requireContext().startActivity(intent);
}
@@ -303,12 +349,14 @@ public class ContactSelectionListFragment extends Fragment
}
selectedContacts.add(contactId);
deselectedContacts.remove(contactId);
contact.setChecked(true);
if (onContactSelectedListener != null) {
onContactSelectedListener.onContactSelected(contactId);
}
} else {
selectedContacts.remove(contactId);
deselectedContacts.add(contactId);
contact.setChecked(false);
if (onContactSelectedListener != null) {
onContactSelectedListener.onContactDeselected(contactId);
@@ -319,43 +367,26 @@ public class ContactSelectionListFragment extends Fragment
@Override
public void onItemLongClick(ContactSelectionListItem view) {
if (actionMode == null) {
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback);
actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(actionModeCallback);
} else {
finishActionModeIfSelectionIsEmpty();
updateActionModeState(actionMode);
}
}
}
private void finishActionModeIfSelectionIsEmpty() {
if (getContactSelectionListAdapter().getActionModeSelection().size() == 0) {
actionMode.finish();
}
}
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
this.onContactSelectedListener = onContactSelectedListener;
}
public interface OnContactSelectedListener {
void onContactSelected(int contactId);
void onContactDeselected(int contactId);
}
@Override
public void handleEvent(@NonNull DcEvent event) {
if (event.getId()==DcContext.DC_EVENT_CONTACTS_CHANGED) {
getLoaderManager().restartLoader(0, null, ContactSelectionListFragment.this);
}
}
@Override
public void onActivityResult(int reqCode, int resultCode, final Intent data) {
super.onActivityResult(reqCode, resultCode, data);
if (resultCode == Activity.RESULT_OK && reqCode == CONTACT_ADDR_RESULT_CODE) {
int contactId = data.getIntExtra(NewContactActivity.CONTACT_ID_EXTRA, 0);
if (contactId != 0) {
selectedContacts.add(contactId);
}
if (event.getId() == DcContext.DC_EVENT_CONTACTS_CHANGED) {
getLoaderManager().restartLoader(0, null, ContactSelectionListFragment.this);
}
}
@@ -25,6 +25,7 @@ import android.Manifest;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -57,6 +58,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
@@ -64,7 +66,12 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.view.WindowCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.session.MediaController;
import androidx.media3.session.SessionCommand;
import androidx.media3.session.SessionToken;
import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcContact;
@@ -75,7 +82,7 @@ import com.b44t.messenger.DcMsg;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.UriAttachment;
import org.thoughtcrime.securesms.audio.AudioRecorder;
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
import org.thoughtcrime.securesms.calls.CallUtil;
import org.thoughtcrime.securesms.components.AnimatingToggle;
import org.thoughtcrime.securesms.components.AttachmentTypeSelector;
import org.thoughtcrime.securesms.components.ComposeText;
@@ -85,6 +92,8 @@ import org.thoughtcrime.securesms.components.InputPanel;
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener;
import org.thoughtcrime.securesms.components.ScaleStableImageView;
import org.thoughtcrime.securesms.components.SendButton;
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
import org.thoughtcrime.securesms.components.audioplay.AudioView;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
import org.thoughtcrime.securesms.connect.AccountManager;
import org.thoughtcrime.securesms.connect.DcEventCenter;
@@ -103,19 +112,19 @@ import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.scribbles.ScribbleActivity;
import org.thoughtcrime.securesms.service.AudioPlaybackService;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.ShareUtil;
import org.thoughtcrime.securesms.util.SendRelayedMessageUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.ShareUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
import org.thoughtcrime.securesms.util.guava.Optional;
import org.thoughtcrime.securesms.util.views.ProgressDialog;
import org.thoughtcrime.securesms.video.recode.VideoRecoder;
import org.thoughtcrime.securesms.calls.CallUtil;
import java.io.File;
import java.util.ArrayList;
@@ -137,14 +146,13 @@ import chat.delta.util.SettableFuture;
*/
@SuppressLint("StaticFieldLeak")
public class ConversationActivity extends PassphraseRequiredActionBarActivity
implements ConversationFragment.ConversationFragmentListener,
AttachmentManager.AttachmentListener,
SearchView.OnQueryTextListener,
DcEventCenter.DcEventDelegate,
OnKeyboardShownListener,
InputPanel.Listener,
InputPanel.MediaListener
{
implements ConversationFragment.ConversationFragmentListener,
AttachmentManager.AttachmentListener,
SearchView.OnQueryTextListener,
DcEventCenter.DcEventDelegate,
OnKeyboardShownListener,
InputPanel.Listener,
InputPanel.MediaListener, AudioView.OnActionListener {
private static final String TAG = ConversationActivity.class.getSimpleName();
public static final String ACCOUNT_ID_EXTRA = "account_id";
@@ -184,10 +192,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private MediaKeyboard emojiPicker;
protected HidingLinearLayout quickAttachmentToggle;
private InputPanel inputPanel;
private @Nullable MediaController mediaController;
private com.google.common.util.concurrent.ListenableFuture<MediaController> mediaControllerFuture;
private AudioPlaybackViewModel playbackViewModel;
private ApplicationContext context;
private Recipient recipient;
private DcContext dcContext;
private Rpc rpc;
private DcChat dcChat = new DcChat(0, 0);
private int chatId;
@@ -196,11 +206,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private boolean isSecurityInitialized = false;
private boolean successfulForwardingAttempt = false;
private boolean isEditing = false;
private boolean switchedProfile = false;
@Override
protected void onCreate(Bundle state, boolean ready) {
this.context = ApplicationContext.getInstance(getApplicationContext());
this.dcContext = DcHelper.getContext(context);
this.rpc = DcHelper.getRpc(context);
supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY);
@@ -217,6 +227,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
initializeActionBar();
initializeViews();
initializeResources();
playbackViewModel = new ViewModelProvider(this).get(AudioPlaybackViewModel.class);
initializeMediaController();
initializeSecurity(false, isDefaultSms).addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
@@ -237,17 +251,64 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
});
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (container.isInputOpen()) {
container.hideCurrentInput(composeText);
} else {
handleReturnToConversationList();
}
}
});
setDcEventListener();
handleRelaying();
}
private void setDcEventListener() {
DcEventCenter eventCenter = DcHelper.getEventCenter(this);
eventCenter.addObserver(DcContext.DC_EVENT_CHAT_MODIFIED, this);
eventCenter.addObserver(DcContext.DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED, this);
eventCenter.addObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
// first cleanup in case it was already registered for other chat
eventCenter.removeObservers(this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_CHAT_MODIFIED, this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED, this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
if (!isMultiUser()) {
eventCenter.addObserver(DcContext.DC_EVENT_INCOMING_MSG, this);
eventCenter.addObserver(DcContext.DC_EVENT_MSG_READ, this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_INCOMING_MSG, this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_MSG_READ, this);
}
}
private void initializeMediaController() {
SessionToken sessionToken = new SessionToken(this,
new ComponentName(this, AudioPlaybackService.class));
mediaControllerFuture = new MediaController.Builder(this, sessionToken)
.buildAsync();
mediaControllerFuture.addListener(() -> {
try {
mediaController = mediaControllerFuture.get();
playbackViewModel.setMediaController(mediaController);
} catch (Exception e) {
Log.e(TAG, "Error connecting to audio playback service", e);
}
}, ContextCompat.getMainExecutor(this));
}
private void addActivityContext(Bundle extras, String activityClassName) {
if (mediaController == null) return;
Bundle commandArgs = new Bundle();
commandArgs.putString("activity_class", activityClassName);
if (extras != null) {
commandArgs.putAll(extras);
}
handleRelaying();
SessionCommand updateContextCommand =
new SessionCommand("UPDATE_ACTIVITY_CONTEXT", Bundle.EMPTY);
mediaController.sendCustomCommand(updateContextCommand, commandArgs);
}
@Override
@@ -273,6 +334,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
});
setDcEventListener(); // reset event listener
handleRelaying();
if (fragment != null) {
@@ -299,7 +361,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
titleView.setTitle(glideRequests, dcChat);
DcHelper.getNotificationCenter(this).updateVisibleChat(dcContext.getAccountId(), chatId);
try {
int accId = rpc.getSelectedAccountId();
DcHelper.getNotificationCenter(this).updateVisibleChat(accId, chatId);
} catch (RpcException e) {
Log.e(TAG, "rpc.getSelectedAccountId() failed", e);
}
attachmentManager.onResume();
}
@@ -313,7 +381,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
DcHelper.getNotificationCenter(this).clearVisibleChat();
if (isFinishing()) overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right);
inputPanel.onPause();
AudioSlidePlayer.stopAll();
}
@Override
@@ -333,6 +400,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
protected void onDestroy() {
DcHelper.getEventCenter(this).removeObservers(this);
if (mediaController != null) {
MediaController.releaseFuture(mediaControllerFuture);
mediaController = null;
playbackViewModel.setMediaController(null);
}
super.onDestroy();
}
@@ -389,7 +461,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
break;
case GROUP_EDIT:
dcChat = dcContext.getChat(chatId);
dcChat = DcHelper.getContext(context).getChat(chatId);
titleView.setTitle(glideRequests, dcChat);
break;
@@ -459,15 +531,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
menu.findItem(R.id.menu_ephemeral_messages).setVisible(false);
}
if (isMultiUser()) {
if (dcChat.isInBroadcast() && !dcChat.isContactRequest()) {
menu.findItem(R.id.menu_leave).setTitle(R.string.menu_leave_channel).setVisible(true);
} else if (dcChat.isEncrypted()
&& dcChat.canSend()
&& !dcChat.isOutBroadcast()
&& !dcChat.isMailingList()) {
menu.findItem(R.id.menu_leave).setVisible(true);
if (dcChat.shallLeaveBeforeDelete(DcHelper.getContext(context))) {
if (dcChat.isInBroadcast()) {
menu.findItem(R.id.menu_leave).setTitle(R.string.menu_leave_channel);
}
menu.findItem(R.id.menu_leave).setVisible(true);
menu.findItem(R.id.menu_delete_chat).setVisible(false);
}
if (isArchived()) {
@@ -545,8 +614,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} else if (itemId == R.id.menu_show_map) {
WebxdcActivity.openMaps(this, chatId);
return true;
} else if (itemId == R.id.menu_start_call) {
CallUtil.startCall(this, chatId);
} else if (itemId == R.id.menu_start_audio_call) {
CallUtil.startCall(this, chatId, false);
return true;
} else if (itemId == R.id.menu_start_video_call) {
CallUtil.startCall(this, chatId, true);
return true;
} else if (itemId == R.id.menu_all_media) {
handleAllMedia();
@@ -568,15 +640,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
return false;
}
@Override
public void onBackPressed() {
if (container.isInputOpen()){
container.hideCurrentInput(composeText);
} else {
handleReturnToConversationList();
}
}
@Override
public void onKeyboardShown() {
inputPanel.onKeyboardShown();
@@ -599,6 +662,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
//////// Event Handlers
private void handleEphemeralMessages() {
DcContext dcContext = DcHelper.getContext(context);
int preselected = dcContext.getChatEphemeralTimer(chatId);
EphemeralMessagesDialog.show(this, preselected, duration -> {
dcContext.setChatEphemeralTimer(chatId, (int) duration);
@@ -610,6 +674,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void handleReturnToConversationList(@Nullable Bundle extras) {
if (switchedProfile) { // force refreshing of chatlist
if (extras == null) extras = new Bundle();
extras.putInt(ConversationListFragment.RELOAD_LIST, 1);
}
playbackViewModel.stopNonMessageAudioPlayback();
boolean archived = getIntent().getBooleanExtra(FROM_ARCHIVED_CHATS_EXTRA, false);
Intent intent = new Intent(this, (archived ? ConversationListArchiveActivity.class : ConversationListActivity.class));
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -619,6 +690,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void handleMuteNotifications() {
DcContext dcContext = DcHelper.getContext(context);
if(!dcChat.isMuted()) {
MuteDialog.show(this, duration -> {
dcContext.setChatMuteDuration(chatId, duration);
@@ -654,15 +726,23 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
AlertDialog dialog = new AlertDialog.Builder(this)
.setMessage(getString(R.string.ask_leave_group))
.setPositiveButton(leaveLabel, (d, which) -> {
dcContext.removeContactFromChat(chatId, DcContact.DC_CONTACT_ID_SELF);
DcHelper.getContext(context).removeContactFromChat(chatId, DcContact.DC_CONTACT_ID_SELF);
Toast.makeText(this, getString(R.string.done), Toast.LENGTH_SHORT).show();
})
.setNegativeButton(R.string.cancel, null)
.setNegativeButton(R.string.menu_leave_and_delete, (d, which) -> {
DcHelper.getContext(context).removeContactFromChat(chatId, DcContact.DC_CONTACT_ID_SELF);
DcHelper.getContext(context).deleteChat(chatId);
DirectShareUtil.clearShortcut(this, chatId);
finish();
})
.setNeutralButton(R.string.cancel, null)
.show();
Util.redPositiveButton(dialog);
Util.redButton(dialog, AlertDialog.BUTTON_NEGATIVE);
}
private void handleArchiveChat() {
DcContext dcContext = DcHelper.getContext(context);
int newVisibility = isArchived() ?
DcChat.DC_CHAT_VISIBILITY_NORMAL : DcChat.DC_CHAT_VISIBILITY_ARCHIVED;
dcContext.setChatVisibility(chatId, newVisibility);
@@ -677,8 +757,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void handleDeleteChat() {
AlertDialog dialog = new AlertDialog.Builder(this)
.setMessage(getResources().getString(R.string.ask_delete_named_chat, dcChat.getName()))
.setPositiveButton(R.string.delete, (d, which) -> {
dcContext.deleteChat(chatId);
.setPositiveButton(R.string.delete_for_me, (d, which) -> {
DcHelper.getContext(context).deleteChat(chatId);
DirectShareUtil.clearShortcut(this, chatId);
finish();
})
@@ -708,6 +788,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void handleForwarding() {
DcContext dcContext = DcHelper.getContext(context);
DcChat dcChat = dcContext.getChat(chatId);
if (dcChat.isSelfTalk()) {
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
@@ -756,7 +837,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
} else {
Uri uri = uriList.isEmpty()? null : uriList.get(0);
dcContext.setDraft(chatId, SendRelayedMessageUtil.createMessage(this, uri, ShareUtil.getSharedType(this), null, null, getSharedText(this)));
DcHelper.getContext(context).setDraft(chatId, SendRelayedMessageUtil.createMessage(this, uri, ShareUtil.getSharedType(this), null, null, getSharedText(this)));
}
initializeDraft();
}
@@ -771,7 +852,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private ListenableFuture<Boolean> initializeDraft() {
isEditing = false;
final SettableFuture<Boolean> future = new SettableFuture<>();
DcMsg draft = dcContext.getDraft(chatId);
DcMsg draft = DcHelper.getContext(context).getDraft(chatId);
final String sharedText = ShareUtil.getSharedText(this);
if (!draft.isOk()) {
@@ -932,7 +1013,15 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void initializeBackground() {
String backgroundImagePath = Prefs.getBackgroundImagePath(this, dcContext.getAccountId());
int accId;
try {
accId = rpc.getSelectedAccountId();
} catch (RpcException e) {
Log.e(TAG, "rpc.getSelectedAccountId() failed", e);
return;
}
String backgroundImagePath = Prefs.getBackgroundImagePath(this, accId);
Drawable background;
if(!backgroundImagePath.isEmpty()) {
background = Drawable.createFromPath(backgroundImagePath);
@@ -958,33 +1047,44 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void initializeResources() {
int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, dcContext.getAccountId());
if (accountId != dcContext.getAccountId()) {
int selectedAccId = DcHelper.getContext(context).getAccountId();
int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, selectedAccId);
if (accountId != selectedAccId) {
switchedProfile = true;
AccountManager.getInstance().switchAccount(context, accountId);
fragment.dcContext = dcContext = context.getDcContext();
initializeBackground();
}
chatId = getIntent().getIntExtra(CHAT_ID_EXTRA, -1);
if(chatId == DcChat.DC_CHAT_NO_CHAT)
throw new IllegalStateException("can't display a conversation for no chat.");
dcChat = dcContext.getChat(chatId);
dcChat = DcHelper.getContext(context).getChat(chatId);
recipient = new Recipient(this, dcChat);
glideRequests = GlideApp.with(this);
setComposePanelVisibility();
setComposePanelVisibility(true);
initializeContactRequest();
}
private void setComposePanelVisibility() {
private void setComposePanelVisibility(boolean isInitialization) {
if (dcChat.canSend()) {
composePanel.setVisibility(View.VISIBLE);
attachmentManager.setHidden(false);
inputPanel.setSubjectVisible(!dcChat.isEncrypted());
// FIXME: disabled for now to avoid problems with chat scrolling and keyboard covering input bar
// ViewUtil.forceApplyWindowInsets(findViewById(R.id.root_layout), true, false, true, true);
// fragment.handleRemoveBottomInsets();
} else {
composePanel.setVisibility(View.GONE);
attachmentManager.setHidden(true);
hideSoftKeyboard();
inputPanel.setSubjectVisible(false);
// FIXME: disabled for now to avoid problems with chat scrolling and keyboard covering input bar
/*
if (isInitialization) {
ViewUtil.forceApplyWindowInsets(findViewById(R.id.root_layout), true, false, true, false);
fragment.handleAddBottomInsets();
}
*/
}
}
@@ -1021,11 +1121,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
return new SettableFuture<>(false);
}
return attachmentManager.setMedia(glideRequests, uri, null, mediaType, 0, 0, chatId);
return attachmentManager.setMedia(glideRequests, uri, null, mediaType, 0, 0, chatId, playbackViewModel);
}
private ListenableFuture<Boolean> setMedia(DcMsg msg, @NonNull MediaType mediaType) {
return attachmentManager.setMedia(glideRequests, Uri.fromFile(new File(msg.getFile())), msg, mediaType, 0, 0, chatId);
return attachmentManager.setMedia(glideRequests, Uri.fromFile(new File(msg.getFile())), msg, mediaType, 0, 0, chatId, playbackViewModel);
}
private void addAttachmentContactInfo(int contactId) {
@@ -1034,11 +1134,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
try {
byte[] vcard = rpc.makeVcard(dcContext.getAccountId(), Collections.singletonList(contactId)).getBytes();
byte[] vcard = rpc.makeVcard(rpc.getSelectedAccountId(), Collections.singletonList(contactId)).getBytes();
String mimeType = "application/octet-stream";
setMedia(PersistentBlobProvider.getInstance().create(this, vcard, mimeType, "vcard.vcf"), MediaType.DOCUMENT);
} catch (RpcException e) {
Log.e(TAG, "makeVcard() failed", e);
Log.e(TAG, "RPC failed", e);
}
}
@@ -1077,6 +1177,12 @@ 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);
final int currentChatId = dcChat.getId();
Util.runOnAnyBackgroundThread(() -> {
DcMsg msg = null;
int recompress = 0;
@@ -1086,9 +1192,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (action == ACTION_SEND_OUT) {
dcContext.sendEditRequest(msgId, body);
} else {
dcContext.setDraft(chatId, null);
dcContext.setDraft(currentChatId, null);
}
future.set(chatId);
future.set(currentChatId);
return;
}
@@ -1099,7 +1205,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
try {
if (slideDeck.getWebxdctDraftId() != 0) {
msg = dcContext.getDraft(chatId);
msg = dcContext.getDraft(currentChatId);
} else {
List<Attachment> attachments = slideDeck.asAttachments();
for (Attachment attachment : attachments) {
@@ -1148,7 +1254,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
// for WEBXDC, drafts are just sent out as is.
// for preparations and other cases, cleanup draft soon.
if (msg == null || msg.getType() != DcMsg.DC_MSG_WEBXDC) {
dcContext.setDraft(dcChat.getId(), null);
dcContext.setDraft(currentChatId, null);
}
if(msg!=null) {
@@ -1164,7 +1270,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
false
);
});
doSend = VideoRecoder.prepareVideo(ConversationActivity.this, dcChat.getId(), msg);
doSend = VideoRecoder.prepareVideo(ConversationActivity.this, currentChatId, msg);
Util.runOnMain(() -> {
try {
if (progressDialog != null) progressDialog.dismiss();
@@ -1175,43 +1281,34 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
if (doSend) {
if (dcContext.sendMsg(dcChat.getId(), msg) == 0) {
if (dcContext.sendMsg(currentChatId, msg) == 0) {
String lastError = dcContext.getLastError();
if (!"".equals(lastError)) {
Util.runOnMain(() -> Toast.makeText(ConversationActivity.this, lastError, Toast.LENGTH_LONG).show());
}
future.set(chatId);
future.set(currentChatId);
return;
}
}
Util.runOnMain(() -> sendComplete(dcChat.getId()));
Util.runOnMain(() -> sendComplete());
}
} else {
dcContext.setDraft(dcChat.getId(), msg);
dcContext.setDraft(currentChatId, msg);
}
future.set(chatId);
future.set(currentChatId);
});
return future;
}
protected void sendComplete(int chatId) {
boolean refreshFragment = (chatId != this.chatId);
this.chatId = chatId;
protected void sendComplete() {
if (fragment == null || !fragment.isVisible() || isFinishing()) {
return;
}
fragment.setLastSeen(-1);
if (refreshFragment) {
fragment.reload(recipient, chatId);
DcHelper.getNotificationCenter(this).updateVisibleChat(dcContext.getAccountId(), chatId);
}
fragment.scrollToBottom();
attachmentManager.cleanup();
}
@@ -1350,6 +1447,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
isEditing = false;
}
@Override
public void onStickerPicked(Uri stickerUri) {
if (isEditing) return;
sendSticker(stickerUri, "image/webp");
}
public void refreshStickerPicker() {
if (emojiPicker != null) {
emojiPicker.refreshStickerPicker();
}
}
// media selected by the system keyboard
@Override
public void onMediaSelected(@NonNull Uri uri, String contentType) {
@@ -1371,6 +1480,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
Optional<QuoteModel> quote = inputPanel.getQuote();
inputPanel.clearQuote();
DcContext dcContext = DcHelper.getContext(context);
DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_STICKER);
if (quote.isPresent()) {
msg.setQuote(quote.get().getQuotedMsg());
@@ -1382,6 +1492,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
// Listeners
@Override
public void onPlayPauseButtonClicked(View view) {
addActivityContext(
this.getIntent().getExtras(),
this.getClass().getName()
);
}
private class AttachmentTypeListener implements AttachmentTypeSelector.AttachmentClickedListener {
@Override
public void onClick(int type) {
@@ -1492,7 +1610,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (isEditing) composeText.setText("");
isEditing = false;
// If you modify these lines you may also want to modify ConversationItem.setQuote():
Recipient author = new Recipient(this, dcContext.getContact(msg.getFromId()));
Recipient author = new Recipient(this, DcHelper.getContext(context).getContact(msg.getFromId()));
SlideDeck slideDeck = new SlideDeck();
if (msg.hasFile()) {
@@ -1515,7 +1633,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void handleEditMessage(DcMsg msg) {
isEditing = true;
Recipient author = new Recipient(this, dcContext.getContact(msg.getFromId()));
Recipient author = new Recipient(this, DcHelper.getContext(context).getContact(msg.getFromId()));
SlideDeck slideDeck = new SlideDeck();
String text = msg.getSummarytext(500);
@@ -1540,6 +1658,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void handleEvent(@NonNull DcEvent event) {
DcContext dcContext = DcHelper.getContext(context);
if (event.getAccountId() != dcContext.getAccountId()) return;
int eventId = event.getId();
if ((eventId == DcContext.DC_EVENT_CHAT_MODIFIED && event.getData1Int() == chatId)
|| (eventId == DcContext.DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED && event.getData1Int() == chatId)
@@ -1547,7 +1668,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
dcChat = dcContext.getChat(chatId);
titleView.setTitle(glideRequests, dcChat);
initializeSecurity(isSecureText, isDefaultSms);
setComposePanelVisibility();
setComposePanelVisibility(false);
initializeContactRequest();
} else if ((eventId == DcContext.DC_EVENT_INCOMING_MSG
|| eventId == DcContext.DC_EVENT_MSG_READ)
@@ -1626,7 +1747,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
String normQuery = query.trim();
searchResult = dcContext.searchMsgs(chatId, normQuery);
searchResult = DcHelper.getContext(context).searchMsgs(chatId, normQuery);
if(searchResult.length>0) {
searchResultPosition = searchResult.length - 1;
@@ -1657,7 +1778,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
messageRequestBottomView.setVisibility(View.VISIBLE);
messageRequestBottomView.setAcceptOnClickListener(v -> {
dcContext.acceptChat(chatId);
DcHelper.getContext(context).acceptChat(chatId);
messageRequestBottomView.setVisibility(View.GONE);
composePanel.setVisibility(View.VISIBLE);
});
@@ -1675,7 +1796,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
// avoid showing compose panel on receiving DC_EVENT_CONTACTS_CHANGED for the chat that is no longer a request after blocking
DcHelper.getEventCenter(this).removeObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
dcContext.blockChat(chatId);
DcHelper.getContext(context).blockChat(chatId);
Bundle extras = new Bundle();
extras.putInt(ConversationListFragment.RELOAD_LIST, 1);
handleReturnToConversationList(extras);
@@ -23,17 +23,23 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcMsg;
import java.lang.ref.SoftReference;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder;
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
import org.thoughtcrime.securesms.components.audioplay.AudioView;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -43,60 +49,52 @@ import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.lang.ref.SoftReference;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A DC adapter for a conversation thread. Ultimately
* used by ConversationActivity to display a conversation
* thread in a ListActivity.
* A DC adapter for a conversation thread. Ultimately used by ConversationActivity to display a
* conversation thread in a ListActivity.
*
* @author Moxie Marlinspike
*
*/
public class ConversationAdapter <V extends View & BindableConversationItem>
// 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>
{
implements StickyHeaderDecoration.StickyHeaderAdapter<HeaderViewHolder> {
private static final int MAX_CACHE_SIZE = 40;
private final Map<Integer,SoftReference<DcMsg>> recordCache =
Collections.synchronizedMap(new LRUCache<Integer,SoftReference<DcMsg>>(MAX_CACHE_SIZE));
private final Map<Integer, SoftReference<DcMsg>> recordCache =
Collections.synchronizedMap(new LRUCache<Integer, SoftReference<DcMsg>>(MAX_CACHE_SIZE));
private static final int MESSAGE_TYPE_OUTGOING = 0;
private static final int MESSAGE_TYPE_INCOMING = 1;
private static final int MESSAGE_TYPE_INFO = 2;
private static final int MESSAGE_TYPE_AUDIO_OUTGOING = 3;
private static final int MESSAGE_TYPE_AUDIO_INCOMING = 4;
private static final int MESSAGE_TYPE_OUTGOING = 0;
private static final int MESSAGE_TYPE_INCOMING = 1;
private static final int MESSAGE_TYPE_INFO = 2;
private static final int MESSAGE_TYPE_AUDIO_OUTGOING = 3;
private static final int MESSAGE_TYPE_AUDIO_INCOMING = 4;
private static final int MESSAGE_TYPE_THUMBNAIL_OUTGOING = 5;
private static final int MESSAGE_TYPE_THUMBNAIL_INCOMING = 6;
private static final int MESSAGE_TYPE_DOCUMENT_OUTGOING = 7;
private static final int MESSAGE_TYPE_DOCUMENT_INCOMING = 8;
private static final int MESSAGE_TYPE_STICKER_INCOMING = 9;
private static final int MESSAGE_TYPE_STICKER_OUTGOING = 10;
private static final int MESSAGE_TYPE_DOCUMENT_OUTGOING = 7;
private static final int MESSAGE_TYPE_DOCUMENT_INCOMING = 8;
private static final int MESSAGE_TYPE_STICKER_INCOMING = 9;
private static final int MESSAGE_TYPE_STICKER_OUTGOING = 10;
private final Set<DcMsg> batchSelected = Collections.synchronizedSet(new HashSet<DcMsg>());
private final @Nullable ItemClickListener clickListener;
private final @NonNull GlideRequests glideRequests;
private final @NonNull Recipient recipient;
private final @NonNull LayoutInflater inflater;
private final @NonNull Context context;
private final @NonNull Calendar calendar;
private final @NonNull GlideRequests glideRequests;
private final @NonNull Recipient recipient;
private final @NonNull LayoutInflater inflater;
private final @NonNull Context context;
private final @NonNull Calendar calendar;
private final DcContext dcContext;
private @NonNull DcChat dcChat;
private @NonNull int[] dcMsgList = new int[0];
private int positionToPulseHighlight = -1;
private int positionCurrentlyPulseHighlighting = -1;
private long pulseHighlightingSince = -1;
private int lastSeenPosition = -1;
private long lastSeen = -1;
private final DcContext dcContext;
private @NonNull DcChat dcChat;
private @NonNull int[] dcMsgList = new int[0];
private int positionToPulseHighlight = -1;
private int positionCurrentlyPulseHighlighting = -1;
private long pulseHighlightingSince = -1;
private int lastSeenPosition = -1;
private long lastSeen = -1;
private AudioPlaybackViewModel playbackViewModel;
private AudioView.OnActionListener audioPlayPauseListener;
protected static class ViewHolder extends RecyclerView.ViewHolder {
public <V extends View & BindableConversationItem> ViewHolder(final @NonNull V itemView) {
@@ -105,7 +103,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
@SuppressWarnings("unchecked")
public <V extends View & BindableConversationItem> V getView() {
return (V)itemView;
return (V) itemView;
}
public BindableConversationItem getItem() {
@@ -113,16 +111,14 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
}
}
public boolean isActive() {
return dcMsgList.length > 0;
}
public @NonNull DcChat getChat(){
public @NonNull DcChat getChat() {
return dcChat;
}
public void setLastSeen(long timestamp) {
lastSeen = timestamp;
}
@@ -146,14 +142,14 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
@Override
public long getItemId(int position) {
if (position<0 || position>=dcMsgList.length) {
if (position < 0 || position >= dcMsgList.length) {
return 0;
}
return dcMsgList[dcMsgList.length-1-position];
return dcMsgList[dcMsgList.length - 1 - position];
}
public @NonNull DcMsg getMsg(int position) {
if(position<0 || position>=dcMsgList.length) {
if (position < 0 || position >= dcMsgList.length) {
return new DcMsg(0);
}
@@ -165,17 +161,23 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
}
}
final DcMsg fromDb = dcContext.getMsg((int)getItemId(position));
final DcMsg fromDb = dcContext.getMsg((int) getItemId(position));
recordCache.put(position, new SoftReference<>(fromDb));
return fromDb;
}
/**
* Returns the position of the message with msgId in the chat list, counted from the top
*/
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 */
public int msgIdToPosition(int msgId) {
for(int i=0; i<dcMsgList.length; i++ ) {
if(dcMsgList[i]==msgId) {
for (int i = 0; i < dcMsgList.length; i++) {
if (dcMsgList[i] == msgId) {
return dcMsgList.length - 1 - i;
}
}
@@ -200,17 +202,18 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
}
}
interface ItemClickListener extends BindableConversationItem.EventListener {
void onItemClick(DcMsg item);
void onItemLongClick(DcMsg item, View view);
}
public ConversationAdapter(@NonNull Context context,
@NonNull DcChat dcChat,
@NonNull GlideRequests glideRequests,
@Nullable ItemClickListener clickListener,
@NonNull Recipient recipient) {
public ConversationAdapter(
@NonNull Context context,
@NonNull DcChat dcChat,
@NonNull GlideRequests glideRequests,
@Nullable ItemClickListener clickListener,
@NonNull Recipient recipient) {
this.dcChat = dcChat;
this.glideRequests = glideRequests;
this.clickListener = clickListener;
@@ -218,14 +221,14 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
this.context = context;
this.inflater = LayoutInflater.from(context);
this.calendar = Calendar.getInstance();
this.dcContext = DcHelper.getContext(context);
this.dcContext = DcHelper.getContext(context);
setHasStableIds(true);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
ConversationAdapter.ViewHolder holder = (ConversationAdapter.ViewHolder)viewHolder;
ConversationAdapter.ViewHolder holder = (ConversationAdapter.ViewHolder) viewHolder;
long now = System.currentTimeMillis();
if (position == positionToPulseHighlight) {
@@ -235,14 +238,25 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
}
long elapsed = now - pulseHighlightingSince;
boolean pulseHighlight = (positionCurrentlyPulseHighlighting == position && elapsed < PULSE_HIGHLIGHT_MILLIS);
boolean pulseHighlight =
(positionCurrentlyPulseHighlighting == position && elapsed < PULSE_HIGHLIGHT_MILLIS);
holder.getItem().bind(getMsg(position), dcChat, glideRequests, batchSelected, recipient, pulseHighlight);
holder
.getItem()
.bind(
getMsg(position),
dcChat,
glideRequests,
batchSelected,
recipient,
pulseHighlight,
playbackViewModel,
audioPlayPauseListener);
}
@Override
public void onViewRecycled(@NonNull RecyclerView.ViewHolder viewHolder) {
if (viewHolder.itemView instanceof ConversationItem) {
if (viewHolder.itemView instanceof ConversationItem) {
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, 0, 1);
}
}
@@ -250,17 +264,19 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final V itemView = ViewUtil.inflate(inflater, parent, getLayoutForViewType(viewType));
itemView.setOnClickListener(view -> {
if (clickListener != null) {
clickListener.onItemClick(itemView.getMessageRecord());
}
});
itemView.setOnLongClickListener(view -> {
if (clickListener != null) {
clickListener.onItemLongClick(itemView.getMessageRecord(), view);
}
return true;
});
itemView.setOnClickListener(
view -> {
if (clickListener != null) {
clickListener.onItemClick(itemView.getMessageRecord());
}
});
itemView.setOnLongClickListener(
view -> {
if (clickListener != null) {
clickListener.onItemLongClick(itemView.getMessageRecord(), view);
}
return true;
});
itemView.setEventListener(clickListener);
return new ViewHolder(itemView);
}
@@ -271,14 +287,19 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
case MESSAGE_TYPE_THUMBNAIL_OUTGOING:
case MESSAGE_TYPE_DOCUMENT_OUTGOING:
case MESSAGE_TYPE_STICKER_OUTGOING:
case MESSAGE_TYPE_OUTGOING: return R.layout.conversation_item_sent;
case MESSAGE_TYPE_OUTGOING:
return R.layout.conversation_item_sent;
case MESSAGE_TYPE_AUDIO_INCOMING:
case MESSAGE_TYPE_THUMBNAIL_INCOMING:
case MESSAGE_TYPE_DOCUMENT_INCOMING:
case MESSAGE_TYPE_STICKER_INCOMING:
case MESSAGE_TYPE_INCOMING: return R.layout.conversation_item_received;
case MESSAGE_TYPE_INFO: return R.layout.conversation_item_update;
default: throw new IllegalArgumentException("unsupported item view type given to ConversationAdapter");
case MESSAGE_TYPE_INCOMING:
return R.layout.conversation_item_received;
case MESSAGE_TYPE_INFO:
return R.layout.conversation_item_update;
default:
throw new IllegalArgumentException(
"unsupported item view type given to ConversationAdapter");
}
}
@@ -288,21 +309,18 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
int type = dcMsg.getType();
if (dcMsg.isInfo()) {
return MESSAGE_TYPE_INFO;
}
else if (type==DcMsg.DC_MSG_AUDIO || type==DcMsg.DC_MSG_VOICE) {
return dcMsg.isOutgoing()? MESSAGE_TYPE_AUDIO_OUTGOING : MESSAGE_TYPE_AUDIO_INCOMING;
}
else if (type==DcMsg.DC_MSG_FILE) {
return dcMsg.isOutgoing()? MESSAGE_TYPE_DOCUMENT_OUTGOING : MESSAGE_TYPE_DOCUMENT_INCOMING;
}
else if (type==DcMsg.DC_MSG_IMAGE || type==DcMsg.DC_MSG_GIF || type==DcMsg.DC_MSG_VIDEO) {
return dcMsg.isOutgoing()? MESSAGE_TYPE_THUMBNAIL_OUTGOING : MESSAGE_TYPE_THUMBNAIL_INCOMING;
}
else if (type == DcMsg.DC_MSG_STICKER) {
return dcMsg.isOutgoing()? MESSAGE_TYPE_STICKER_OUTGOING : MESSAGE_TYPE_STICKER_INCOMING;
}
else {
return dcMsg.isOutgoing()? MESSAGE_TYPE_OUTGOING : MESSAGE_TYPE_INCOMING;
} else if (type == DcMsg.DC_MSG_AUDIO || type == DcMsg.DC_MSG_VOICE) {
return dcMsg.isOutgoing() ? MESSAGE_TYPE_AUDIO_OUTGOING : MESSAGE_TYPE_AUDIO_INCOMING;
} else if (type == DcMsg.DC_MSG_FILE) {
return dcMsg.isOutgoing() ? MESSAGE_TYPE_DOCUMENT_OUTGOING : MESSAGE_TYPE_DOCUMENT_INCOMING;
} else if (type == DcMsg.DC_MSG_IMAGE
|| type == DcMsg.DC_MSG_GIF
|| type == DcMsg.DC_MSG_VIDEO) {
return dcMsg.isOutgoing() ? MESSAGE_TYPE_THUMBNAIL_OUTGOING : MESSAGE_TYPE_THUMBNAIL_INCOMING;
} else if (type == DcMsg.DC_MSG_STICKER) {
return dcMsg.isOutgoing() ? MESSAGE_TYPE_STICKER_OUTGOING : MESSAGE_TYPE_STICKER_INCOMING;
} else {
return dcMsg.isOutgoing() ? MESSAGE_TYPE_OUTGOING : MESSAGE_TYPE_INCOMING;
}
}
@@ -325,16 +343,16 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
}
public void pulseHighlightItem(int position) {
if (position>=0 && position < getItemCount()) {
if (position >= 0 && position < getItemCount()) {
positionToPulseHighlight = position;
notifyItemChanged(position);
}
}
public long getSortTimestamp(int position) {
if (!isActive()) return 0;
if (!isActive()) return 0;
if (position >= getItemCount()) return 0;
if (position < 0) return 0;
if (position < 0) return 0;
DcMsg msg = getMsg(position);
return msg.getSortTimestamp();
@@ -348,7 +366,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
@Override
public long getHeaderId(int position) {
if (position >= getItemCount()) return -1;
if (position < 0) return -1;
if (position < 0) return -1;
calendar.setTime(new Date(getSortTimestamp(position)));
return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR));
@@ -356,18 +374,17 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
@Override
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) {
return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.conversation_item_header, parent, false));
return new HeaderViewHolder(
LayoutInflater.from(getContext())
.inflate(R.layout.conversation_item_header, parent, false));
}
/**
* date header view
*/
/** date header view */
@Override
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
viewHolder.setText(DateUtils.getRelativeDate(getContext(), getSortTimestamp(position)));
}
public void changeData(@Nullable int[] dcMsgList) {
// should be called when there are new messages
this.dcMsgList = dcMsgList == null ? new int[0] : dcMsgList;
@@ -387,12 +404,11 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
}
private int findLastSeenPosition(long lastSeen) {
if (lastSeen <= 0) return -1;
if (!isActive()) return -1;
if (lastSeen <= 0) return -1;
if (!isActive()) return -1;
int count = getItemCount();
for (int i = 0; i < count; i++) {
DcMsg msg = getMsg(i);
if (msg.isOutgoing() || msg.getTimestamp() <= lastSeen) {
@@ -404,11 +420,16 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
}
public HeaderViewHolder onCreateLastSeenViewHolder(ViewGroup parent) {
return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.conversation_item_last_seen, parent, false));
return new HeaderViewHolder(
LayoutInflater.from(getContext())
.inflate(R.layout.conversation_item_last_seen, parent, false));
}
public void onBindLastSeenViewHolder(HeaderViewHolder viewHolder, int position) {
viewHolder.setText(getContext().getResources().getQuantityString(R.plurals.chat_n_new_messages, (position + 1), (position + 1)));
viewHolder.setText(
getContext()
.getResources()
.getQuantityString(R.plurals.chat_n_new_messages, (position + 1), (position + 1)));
}
static class LastSeenHeader extends StickyHeaderDecoration {
@@ -416,32 +437,45 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
LastSeenHeader(ConversationAdapter adapter) {
super(adapter, false, false);
this.adapter = adapter;
this.adapter = adapter;
}
@Override
protected boolean hasHeader(RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) {
return adapter.isActive() && position == adapter.getLastSeenPosition();
protected boolean hasHeader(
RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) {
return adapter.isActive() && position == adapter.getLastSeenPosition();
}
@Override
protected int getHeaderTop(RecyclerView parent, View child, View header, int adapterPos, int layoutPos) {
protected int getHeaderTop(
RecyclerView parent, View child, View header, int adapterPos, int layoutPos) {
return parent.getLayoutManager().getDecoratedTop(child);
}
@Override
protected HeaderViewHolder getHeader(RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) {
protected HeaderViewHolder getHeader(
RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) {
HeaderViewHolder viewHolder = adapter.onCreateLastSeenViewHolder(parent);
adapter.onBindLastSeenViewHolder(viewHolder, position);
int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);
int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
int heightSpec =
View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);
int childWidth = ViewGroup.getChildMeasureSpec(widthSpec, parent.getPaddingLeft() + parent.getPaddingRight(), viewHolder.itemView.getLayoutParams().width);
int childHeight = ViewGroup.getChildMeasureSpec(heightSpec, parent.getPaddingTop() + parent.getPaddingBottom(), viewHolder.itemView.getLayoutParams().height);
int childWidth =
ViewGroup.getChildMeasureSpec(
widthSpec,
parent.getPaddingLeft() + parent.getPaddingRight(),
viewHolder.itemView.getLayoutParams().width);
int childHeight =
ViewGroup.getChildMeasureSpec(
heightSpec,
parent.getPaddingTop() + parent.getPaddingBottom(),
viewHolder.itemView.getLayoutParams().height);
viewHolder.itemView.measure(childWidth, childHeight);
viewHolder.itemView.layout(0, 0, viewHolder.itemView.getMeasuredWidth(), viewHolder.itemView.getMeasuredHeight());
viewHolder.itemView.layout(
0, 0, viewHolder.itemView.getMeasuredWidth(), viewHolder.itemView.getMeasuredHeight());
return viewHolder;
}
File diff suppressed because it is too large Load Diff
@@ -41,8 +41,7 @@ import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcMsg;
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
import org.thoughtcrime.securesms.components.AudioView;
import org.thoughtcrime.securesms.calls.CallUtil;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.BorderlessImageView;
import org.thoughtcrime.securesms.components.CallItemView;
@@ -52,6 +51,8 @@ import org.thoughtcrime.securesms.components.DocumentView;
import org.thoughtcrime.securesms.components.QuoteView;
import org.thoughtcrime.securesms.components.VcardView;
import org.thoughtcrime.securesms.components.WebxdcView;
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
import org.thoughtcrime.securesms.components.audioplay.AudioView;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.mms.AudioSlide;
import org.thoughtcrime.securesms.mms.DocumentSlide;
@@ -71,7 +72,6 @@ import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.Stub;
import org.thoughtcrime.securesms.calls.CallUtil;
import java.util.List;
import java.util.Set;
@@ -181,9 +181,11 @@ public class ConversationItem extends BaseConversationItem
@NonNull GlideRequests glideRequests,
@NonNull Set<DcMsg> batchSelected,
@NonNull Recipient recipients,
boolean pulseHighlight)
boolean pulseHighlight,
@Nullable AudioPlaybackViewModel playbackViewModel,
AudioView.OnActionListener audioPlayPauseListener)
{
bind(messageRecord, dcChat, batchSelected, pulseHighlight, recipients);
bindPartial(messageRecord, dcChat, batchSelected, pulseHighlight, recipients);
this.glideRequests = glideRequests;
this.showSender = ((dcChat.isMultiUser() || dcChat.isSelfTalk()) && !messageRecord.isOutgoing()) || messageRecord.getOverrideSenderName() != null;
@@ -204,7 +206,7 @@ public class ConversationItem extends BaseConversationItem
setGutterSizes(messageRecord, showSender);
setMessageShape(messageRecord);
setMediaAttributes(messageRecord, showSender);
setMediaAttributes(messageRecord, showSender, playbackViewModel, audioPlayPauseListener);
setBodyText(messageRecord);
setBubbleState(messageRecord);
setContactPhoto();
@@ -482,24 +484,10 @@ public class ConversationItem extends BaseConversationItem
}
private void setMediaAttributes(@NonNull DcMsg messageRecord,
boolean showSender)
boolean showSender,
AudioPlaybackViewModel playbackViewModel,
AudioView.OnActionListener audioPlayPauseListener)
{
class SetDurationListener implements AudioSlidePlayer.Listener {
@Override
public void onStart() {}
@Override
public void onStop() {}
@Override
public void onProgress(AudioSlide slide, double progress, long millis) {}
@Override
public void onReceivedDuration(int millis) {
messageRecord.lateFilingMediaSize(0,0, millis);
audioViewStub.get().setDuration(millis);
}
}
if (hasAudio(messageRecord)) {
audioViewStub.get().setVisibility(View.VISIBLE);
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
@@ -509,15 +497,9 @@ public class ConversationItem extends BaseConversationItem
if (vcardViewStub.resolved()) vcardViewStub.get().setVisibility(View.GONE);
if (callViewStub.resolved()) callViewStub.get().setVisibility(View.GONE);
//noinspection ConstantConditions
int duration = messageRecord.getDuration();
if (duration == 0) {
AudioSlide audio = new AudioSlide(context, messageRecord);
AudioSlidePlayer audioSlidePlayer = AudioSlidePlayer.createFor(getContext(), audio, new SetDurationListener());
audioSlidePlayer.requestDuration();
}
audioViewStub.get().setAudio(new AudioSlide(context, messageRecord), duration);
audioViewStub.get().setPlaybackViewModel(playbackViewModel);
audioViewStub.get().setOnActionListener(audioPlayPauseListener);
audioViewStub.get().setAudio(new AudioSlide(context, messageRecord));
audioViewStub.get().setOnClickListener(passthroughClickListener);
audioViewStub.get().setOnLongClickListener(passthroughClickListener);
audioViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
@@ -825,8 +807,6 @@ public class ConversationItem extends BaseConversationItem
return stickerStub.get().getFooter();
} else if (hasOnlyThumbnail(messageRecord) && TextUtils.isEmpty(messageRecord.getText())) {
return mediaThumbnailStub.get().getFooter();
} else if (messageRecord.getType() == DcMsg.DC_MSG_CALL) {
return callViewStub.get().getFooter();
} else {
return footer;
}
@@ -987,6 +967,8 @@ public class ConversationItem extends BaseConversationItem
public void onClick(final View v, final Slide slide) {
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
performClick();
} else if (eventListener != null) {
eventListener.onStickerClicked(messageRecord);
}
}
}
@@ -1000,9 +982,9 @@ public class ConversationItem extends BaseConversationItem
int chatId = messageRecord.getChatId();
if (!messageRecord.isOutgoing() && callInfo.state instanceof CallState.Alerting) {
int callId = messageRecord.getId();
CallUtil.openCall(getContext(), accId, chatId, callId, callInfo.sdpOffer);
CallUtil.openCall(getContext(), accId, chatId, callId, callInfo.sdpOffer, callInfo.hasVideo);
} else {
CallUtil.startCall(getContext(), accId, chatId);
CallUtil.startCall(getContext(), accId, chatId, callInfo.hasVideo);
}
}
}
@@ -6,40 +6,37 @@ import android.graphics.Canvas;
import android.os.Vibrator;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.b44t.messenger.DcMsg;
import org.thoughtcrime.securesms.util.AccessibilityUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
private static final float SWIPE_SUCCESS_DX = ConversationSwipeAnimationHelper.TRIGGER_DX;
private static final long SWIPE_SUCCESS_VIBE_TIME_MS = 10;
private static final float SWIPE_SUCCESS_DX = ConversationSwipeAnimationHelper.TRIGGER_DX;
private static final long SWIPE_SUCCESS_VIBE_TIME_MS = 10;
private boolean swipeBack;
private boolean shouldTriggerSwipeFeedback;
private boolean canTriggerSwipe;
private float latestDownX;
private float latestDownY;
private float latestDownX;
private float latestDownY;
private final SwipeAvailabilityProvider swipeAvailabilityProvider;
private final SwipeAvailabilityProvider swipeAvailabilityProvider;
private final ConversationItemTouchListener itemTouchListener;
private final OnSwipeListener onSwipeListener;
private final OnSwipeListener onSwipeListener;
ConversationItemSwipeCallback(@NonNull SwipeAvailabilityProvider swipeAvailabilityProvider,
@NonNull OnSwipeListener onSwipeListener)
{
ConversationItemSwipeCallback(
@NonNull SwipeAvailabilityProvider swipeAvailabilityProvider,
@NonNull OnSwipeListener onSwipeListener) {
super(0, ItemTouchHelper.END);
this.itemTouchListener = new ConversationItemTouchListener(this::updateLatestDownCoordinate);
this.swipeAvailabilityProvider = swipeAvailabilityProvider;
this.onSwipeListener = onSwipeListener;
this.itemTouchListener = new ConversationItemTouchListener(this::updateLatestDownCoordinate);
this.swipeAvailabilityProvider = swipeAvailabilityProvider;
this.onSwipeListener = onSwipeListener;
this.shouldTriggerSwipeFeedback = true;
this.canTriggerSwipe = true;
this.canTriggerSwipe = true;
}
void attachToRecyclerView(@NonNull RecyclerView recyclerView) {
@@ -48,21 +45,19 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder target)
{
public boolean onMove(
@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
}
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {}
@Override
public int getSwipeDirs(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder)
{
public int getSwipeDirs(
@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
if (cannotSwipeViewHolder(viewHolder)) return 0;
return super.getSwipeDirs(recyclerView, viewHolder);
}
@@ -78,18 +73,21 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
@Override
public void onChildDraw(
@NonNull Canvas c,
@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
float dx, float dy, int actionState, boolean isCurrentlyActive)
{
@NonNull Canvas c,
@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
float dx,
float dy,
int actionState,
boolean isCurrentlyActive) {
if (cannotSwipeViewHolder(viewHolder)) return;
float sign = getSignFromDirection(viewHolder.itemView);
float sign = getSignFromDirection(viewHolder.itemView);
boolean isCorrectSwipeDir = sameSign(dx, sign);
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && isCorrectSwipeDir) {
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, Math.abs(dx), sign);
ConversationSwipeAnimationHelper.update(
(ConversationItem) viewHolder.itemView, Math.abs(dx), sign);
handleSwipeFeedback((ConversationItem) viewHolder.itemView, Math.abs(dx));
if (canTriggerSwipe) {
setTouchListener(recyclerView, viewHolder, Math.abs(dx));
@@ -100,7 +98,7 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
if (dx == 0) {
shouldTriggerSwipeFeedback = true;
canTriggerSwipe = true;
canTriggerSwipe = true;
}
}
@@ -122,50 +120,50 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
}
@SuppressLint("ClickableViewAccessibility")
private void setTouchListener(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
float dx)
{
recyclerView.setOnTouchListener(new View.OnTouchListener() {
private void setTouchListener(
@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dx) {
recyclerView.setOnTouchListener(
new View.OnTouchListener() {
// This variable is necessary to make sure that the handleTouchActionUp() and therefore onSwiped() is called only once.
// Otherwise, any subsequent little swipe would invoke onSwiped().
// We can't call recyclerView.setOnTouchListener(null) because another ConversationItem might have set its own
// on touch listener in the meantime and we don't want to cancel it
private boolean listenerCalled = false;
// This variable is necessary to make sure that the handleTouchActionUp() and therefore
// onSwiped() is called only once.
// Otherwise, any subsequent little swipe would invoke onSwiped().
// We can't call recyclerView.setOnTouchListener(null) because another ConversationItem
// might have set its own
// on touch listener in the meantime and we don't want to cancel it
private boolean listenerCalled = false;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
shouldTriggerSwipeFeedback = true;
break;
case MotionEvent.ACTION_UP:
if (!listenerCalled) {
listenerCalled = true;
ConversationItemSwipeCallback.this.handleTouchActionUp(recyclerView, viewHolder, dx);
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
shouldTriggerSwipeFeedback = true;
break;
case MotionEvent.ACTION_UP:
if (!listenerCalled) {
listenerCalled = true;
ConversationItemSwipeCallback.this.handleTouchActionUp(
recyclerView, viewHolder, dx);
}
// fallthrough
case MotionEvent.ACTION_CANCEL:
swipeBack = true;
shouldTriggerSwipeFeedback = false;
// Sometimes the view does not go back correctly, so make sure that after 2s the
// progress is reset:
viewHolder.itemView.postDelayed(() -> resetProgress(viewHolder), 2000);
if (AccessibilityUtil.areAnimationsDisabled(viewHolder.itemView.getContext())) {
resetProgress(viewHolder);
}
break;
}
//fallthrough
case MotionEvent.ACTION_CANCEL:
swipeBack = true;
shouldTriggerSwipeFeedback = false;
// Sometimes the view does not go back correctly, so make sure that after 2s the progress is reset:
viewHolder.itemView.postDelayed(() -> resetProgress(viewHolder), 2000);
if (AccessibilityUtil.areAnimationsDisabled(viewHolder.itemView.getContext())) {
resetProgress(viewHolder);
}
break;
}
return false;
}
});
return false;
}
});
}
private void handleTouchActionUp(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
float dx)
{
private void handleTouchActionUp(
@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dx) {
if (dx > SWIPE_SUCCESS_DX) {
canTriggerSwipe = false;
onSwiped(viewHolder);
@@ -177,17 +175,16 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
}
private static void resetProgress(RecyclerView.ViewHolder viewHolder) {
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView,
0f,
getSignFromDirection(viewHolder.itemView));
ConversationSwipeAnimationHelper.update(
(ConversationItem) viewHolder.itemView, 0f, getSignFromDirection(viewHolder.itemView));
}
private boolean cannotSwipeViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
if (!(viewHolder.itemView instanceof ConversationItem)) return true;
ConversationItem item = ((ConversationItem) viewHolder.itemView);
return !swipeAvailabilityProvider.isSwipeAvailable(item.getMessageRecord()) ||
item.disallowSwipe(latestDownX, latestDownY);
return !swipeAvailabilityProvider.isSwipeAvailable(item.getMessageRecord())
|| item.disallowSwipe(latestDownX, latestDownY);
}
private void updateLatestDownCoordinate(float x, float y) {
@@ -1,7 +1,6 @@
package org.thoughtcrime.securesms;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
@@ -16,12 +16,11 @@
*/
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;
import static org.thoughtcrime.securesms.util.ShareUtil.getDirectSharingChatId;
import static org.thoughtcrime.securesms.util.ShareUtil.getForwardedMessageAccountId;
import static org.thoughtcrime.securesms.util.ShareUtil.getSharedTitle;
import static org.thoughtcrime.securesms.util.ShareUtil.isDirectSharing;
import static org.thoughtcrime.securesms.util.ShareUtil.isForwarding;
@@ -29,6 +28,7 @@ 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;
@@ -45,13 +45,16 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.appcompat.widget.TooltipCompat;
import androidx.core.view.MenuCompat;
import chat.delta.rpc.types.SecurejoinSource;
import chat.delta.rpc.types.SecurejoinUiPath;
import com.amulyakhare.textdrawable.TextDrawable;
import com.b44t.messenger.DcAccounts;
import com.b44t.messenger.DcContact;
@@ -59,7 +62,8 @@ import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcMsg;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
import java.util.ArrayList;
import java.util.Date;
import org.thoughtcrime.securesms.components.AvatarView;
import org.thoughtcrime.securesms.components.SearchToolbar;
import org.thoughtcrime.securesms.connect.AccountManager;
@@ -76,45 +80,51 @@ import org.thoughtcrime.securesms.search.SearchFragment;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.ScreenLockUtil;
import org.thoughtcrime.securesms.util.ShareUtil;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.ScreenLockUtil;
import org.thoughtcrime.securesms.util.SendRelayedMessageUtil;
import org.thoughtcrime.securesms.util.ShareUtil;
import org.thoughtcrime.securesms.util.StorageUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import chat.delta.rpc.types.SecurejoinSource;
import chat.delta.rpc.types.SecurejoinUiPath;
import java.util.ArrayList;
import java.util.Date;
public class ConversationListActivity extends PassphraseRequiredActionBarActivity
implements ConversationListFragment.ConversationSelectedListener
{
implements ConversationListFragment.ConversationSelectedListener {
private static final String TAG = ConversationListActivity.class.getSimpleName();
private static final String OPENPGP4FPR = "openpgp4fpr";
private static final String NDK_ARCH_WARNED = "ndk_arch_warned";
public static final String CLEAR_NOTIFICATIONS = "clear_notifications";
public static final String ACCOUNT_ID_EXTRA = "account_id";
public static final String FROM_WELCOME = "from_welcome";
private static final int REQUEST_CODE_CONFIRM_CREDENTIALS_DELETE_PROFILE = ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS+1;
public static final String FROM_WELCOME = "from_welcome";
public static final String FROM_WELCOME_RAW_QR = "from_welcome_raw_qr";
private ConversationListFragment conversationListFragment;
public TextView title;
private AvatarView selfAvatar;
private ImageView unreadIndicator;
private SearchFragment searchFragment;
private SearchToolbar searchToolbar;
private ImageView searchAction;
private ViewGroup fragmentContainer;
private ViewGroup selfAvatarContainer;
public TextView title;
private AvatarView selfAvatar;
private ImageView unreadIndicator;
private SearchFragment searchFragment;
private SearchToolbar searchToolbar;
private ImageView searchAction;
private ViewGroup fragmentContainer;
private ViewGroup selfAvatarContainer;
/** used to store temporarily scanned QR to pass it back to QrCodeHandler when ScreenLockUtil is used */
/**
* used to store temporarily scanned QR to pass it back to QrCodeHandler when ScreenLockUtil is
* used
*/
private String qrData = null;
/** used to store temporarily profile ID to delete after authorization is granted via ScreenLockUtil */
private ActivityResultLauncher<Intent> relayLockLauncher;
private ActivityResultLauncher<Intent> qrScannerLauncher;
/**
* used to store temporarily profile ID to delete after authorization is granted via
* ScreenLockUtil
*/
private int deleteProfileId = 0;
private ActivityResultLauncher<Intent> deleteProfileLockLauncher;
@Override
protected void onPreCreate() {
dynamicTheme = new DynamicNoActionBarTheme();
@@ -123,36 +133,47 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
@Override
protected void onCreate(Bundle icicle, boolean ready) {
// update messages - for new messages, do not reuse or modify strings but create new ones.
// it is not needed to keep all past update messages, however, when deleted, also the strings should be deleted.
try {
DcContext dcContext = DcHelper.getContext(this);
final String deviceMsgLabel = "update_2_33_1_android";
if (!dcContext.wasDeviceMsgEverAdded(deviceMsgLabel)) {
DcMsg msg = null;
if (!getIntent().getBooleanExtra(FROM_WELCOME, false)) {
msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
relayLockLauncher =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK) {
// QrCodeHandler requested user authorization before adding a relay
// and it was granted, so proceed to add the relay
if (qrData != null) {
new QrCodeHandler(this).addRelay(qrData);
qrData = null;
}
}
});
deleteProfileLockLauncher =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK) {
if (deleteProfileId != 0) {
deleteProfile(deleteProfileId);
deleteProfileId = 0;
}
}
});
qrScannerLauncher =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK) {
IntentResult scanResult =
IntentIntegrator.parseActivityResult(result.getResultCode(), result.getData());
qrData = scanResult.getContents();
new QrCodeHandler(this)
.handleQrData(
qrData, SecurejoinSource.Scan, SecurejoinUiPath.QrIcon, relayLockLauncher);
}
});
// InputStream inputStream = getResources().getAssets().open("device-messages/green-checkmark.jpg");
// String outputFile = DcHelper.getBlobdirFile(dcContext, "green-checkmark", ".jpg");
// Util.copy(inputStream, new FileOutputStream(outputFile));
// msg.setFile(outputFile, "image/jpeg");
msg.setText(getString(R.string.update_2_33, "https://arcanechat.me/#contribute"));
}
dcContext.addDeviceMsg(deviceMsgLabel, msg);
if (Prefs.getStringPreference(this, Prefs.LAST_DEVICE_MSG_LABEL, "").equals(deviceMsgLabel)) {
int deviceChatId = dcContext.getChatIdByContactId(DcContact.DC_CONTACT_ID_DEVICE);
if (deviceChatId != 0) {
dcContext.marknoticedChat(deviceChatId);
}
}
Prefs.setStringPreference(this, Prefs.LAST_DEVICE_MSG_LABEL, deviceMsgLabel);
}
} catch(Exception e) {
e.printStackTrace();
addDeviceMessages(getIntent().getBooleanExtra(FROM_WELCOME, false));
if (getIntent().getIntExtra(ACCOUNT_ID_EXTRA, -1) <= 0) {
getIntent().putExtra(ACCOUNT_ID_EXTRA, DcHelper.getContext(this).getAccountId());
}
// create view
@@ -160,44 +181,88 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
selfAvatar = findViewById(R.id.self_avatar);
selfAvatarContainer = findViewById(R.id.self_avatar_container);
unreadIndicator = findViewById(R.id.unread_indicator);
title = findViewById(R.id.toolbar_title);
searchToolbar = findViewById(R.id.search_toolbar);
searchAction = findViewById(R.id.search_action);
fragmentContainer = findViewById(R.id.fragment_container);
selfAvatar = findViewById(R.id.self_avatar);
selfAvatarContainer = findViewById(R.id.self_avatar_container);
unreadIndicator = findViewById(R.id.unread_indicator);
title = findViewById(R.id.toolbar_title);
searchToolbar = findViewById(R.id.search_toolbar);
searchAction = findViewById(R.id.search_action);
fragmentContainer = findViewById(R.id.fragment_container);
// add margin to avoid content hidden behind system bars
ViewUtil.applyWindowInsetsAsMargin(searchToolbar, true, true, true, false);
Bundle bundle = new Bundle();
conversationListFragment = initFragment(R.id.fragment_container, new ConversationListFragment(), bundle);
conversationListFragment =
initFragment(R.id.fragment_container, new ConversationListFragment(), bundle);
initializeSearchListener();
getOnBackPressedDispatcher()
.addCallback(
this,
new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (searchToolbar.isVisible()) {
searchToolbar.collapse();
} else {
Activity activity = ConversationListActivity.this;
if (isRelayingMessageContent(activity)) {
int selectedAccId = DcHelper.getContext(activity).getAccountId();
int initialAccId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, selectedAccId);
if (initialAccId != selectedAccId) {
// allowing to go back is dangerous, it could be activity on previously
// selected account,
// instead of figuring out account rollback in onResume in each activity
// (conversation, gallery, media preview, webxdc, etc.)
// just clear the back stack and stay in newly selected account
finishAffinity();
startActivity(new Intent(activity, ConversationListActivity.class));
return;
} else {
handleResetRelaying();
}
}
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));
findViewById(R.id.avatar_and_title).setOnClickListener(v -> {
if (!isRelayingMessageContent(this)) {
AccountManager.getInstance().showSwitchAccountMenu(this);
}
});
selfAvatar.setOnClickListener(
v -> AccountManager.getInstance().showSwitchAccountMenu(this, false));
findViewById(R.id.avatar_and_title)
.setOnClickListener(
v -> {
if (!isRelayingMessageContent(this)) {
AccountManager.getInstance().showSwitchAccountMenu(this, false);
}
});
refresh();
if (BuildConfig.DEBUG) checkNdkArchitecture();
DcHelper.maybeShowMigrationError(this);
String rawQrString = getIntent().getStringExtra(FROM_WELCOME_RAW_QR);
// Launch chat directly, if coming from onboarding with a join chat/group QR
if (rawQrString != null) {
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.secureJoinByQr(rawQrString, SecurejoinSource.Scan, SecurejoinUiPath.Unknown);
}
}
/**
* If the build script is invoked with a specific architecture (e.g.`ndk-make.sh arm64-v8a`), it
* will compile the core only for this arch. This method checks if the arch was correct.
*
* In order to do this, `ndk-make.sh` writes its argument into the file `ndkArch`.
* <p>In order to do this, `ndk-make.sh` writes its argument into the file `ndkArch`.
* `getNdkArch()` in `build.gradle` then reads this file and its content is assigned to
* `BuildConfig.NDK_ARCH`.
*/
@@ -210,7 +275,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
String arch;
// armv8l is 32 bit mode in 64 bit CPU:
if (archProperty.startsWith("armv7") || archProperty.startsWith("armv8l")) arch = "armeabi-v7a";
if (archProperty.startsWith("armv7") || archProperty.startsWith("armv8l"))
arch = "armeabi-v7a";
else if (archProperty.equals("aarch64")) arch = "arm64-v8a";
else if (archProperty.equals("i686")) arch = "x86";
else if (archProperty.equals("x86_64")) arch = "x86_64";
@@ -224,23 +290,35 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
String message;
if (arch.equals("")) {
message = "This phone has the unknown architecture " + archProperty + ".\n\n"+
"Please open an issue at https://github.com/deltachat/deltachat-android/issues.";
message =
"This phone has the unknown architecture "
+ archProperty
+ ".\n\n"
+ "Please open an issue at https://github.com/deltachat/deltachat-android/issues.";
} else {
message = "Apparently you used `ndk-make.sh " + BuildConfig.NDK_ARCH + "`, but this device is " + arch + ".\n\n" +
"You can use the app, but changes you made to the Rust code were not applied.\n\n" +
"To compile in your changes, you can:\n" +
"- Either run `ndk-make.sh " + arch + "` to build only for " + arch + " in debug mode\n" +
"- Or run `ndk-make.sh` without argument to build for all architectures in release mode\n\n" +
"If something doesn't work, please open an issue at https://github.com/deltachat/deltachat-android/issues!!";
message =
"Apparently you used `ndk-make.sh "
+ BuildConfig.NDK_ARCH
+ "`, but this device is "
+ arch
+ ".\n\n"
+ "You can use the app, but changes you made to the Rust code were not applied.\n\n"
+ "To compile in your changes, you can:\n"
+ "- Either run `ndk-make.sh "
+ arch
+ "` to build only for "
+ arch
+ " in debug mode\n"
+ "- Or run `ndk-make.sh` without argument to build for all architectures in release mode\n\n"
+ "If something doesn't work, please open an issue at https://github.com/deltachat/deltachat-android/issues!!";
}
Log.e(TAG, message);
if (!Prefs.getBooleanPreference(this, NDK_ARCH_WARNED, false)) {
new AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.show();
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.show();
Prefs.setBooleanPreference(this, NDK_ARCH_WARNED, true);
}
}
@@ -257,24 +335,29 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
}
super.onNewIntent(intent);
setIntent(intent);
if (getIntent().getIntExtra(ACCOUNT_ID_EXTRA, -1) <= 0) {
getIntent().putExtra(ACCOUNT_ID_EXTRA, DcHelper.getContext(this).getAccountId());
}
refresh();
conversationListFragment.onNewIntent();
invalidateOptionsMenu();
}
private void refresh() {
DcContext dcContext = DcHelper.getContext(this);
int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, dcContext.getAccountId());
int selectedAccId = DcHelper.getContext(this).getAccountId();
int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, selectedAccId);
if (getIntent().getBooleanExtra(CLEAR_NOTIFICATIONS, false)) {
DcHelper.getNotificationCenter(this).removeAllNotifications(accountId);
}
if (accountId != dcContext.getAccountId()) {
AccountManager.getInstance().switchAccountAndStartActivity(this, accountId);
if (accountId != selectedAccId) {
AccountManager.getInstance().switchAccount(this, accountId);
onProfileSwitched(accountId);
} else {
refreshAvatar();
refreshUnreadIndicator();
refreshTitle();
}
refreshAvatar();
refreshUnreadIndicator();
refreshTitle();
handleOpenpgp4fpr();
if (isDirectSharing(this)) {
openConversation(getDirectSharingChatId(this), -1);
@@ -296,7 +379,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} else {
title.setText(DcHelper.getContext(this).getName());
// refreshTitle is called by ConversationListFragment when connectivity changes so update connectivity dot here
// refreshTitle is called by ConversationListFragment when connectivity changes so update
// connectivity dot here
selfAvatar.setConnectivity(DcHelper.getContext(this).getConnectivity());
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
}
@@ -332,23 +416,26 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
}
}
if(unreadCount == 0) {
if (unreadCount == 0) {
unreadIndicator.setVisibility(View.GONE);
} else {
boolean isDarkTheme = DynamicTheme.isDarkTheme(this);
int badgeColor = Color.WHITE;
if (isDarkTheme) {
final TypedArray attrs = obtainStyledAttributes(new int[] {
R.attr.conversation_list_item_unreadcount_color,
});
final TypedArray attrs =
obtainStyledAttributes(
new int[] {
R.attr.conversation_list_item_unreadcount_color,
});
badgeColor = attrs.getColor(0, Color.BLACK);
}
String label = unreadCount>99? "99+" : String.valueOf(unreadCount);
unreadIndicator.setImageDrawable(TextDrawable.builder()
String label = unreadCount > 99 ? "99+" : String.valueOf(unreadCount);
unreadIndicator.setImageDrawable(
TextDrawable.builder()
.beginConfig()
.width(ViewUtil.dpToPx(this, 20))
.height(ViewUtil.dpToPx(this, 20))
.textColor(isDarkTheme? Color.WHITE : Color.BLACK)
.textColor(isDarkTheme ? Color.WHITE : Color.BLACK)
.bold()
.endConfig()
.buildRound(label, badgeColor));
@@ -371,9 +458,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
if (isRelayingMessageContent(this)) {
inflater.inflate(R.menu.forwarding_menu, menu);
menu.findItem(R.id.menu_export_attachment).setVisible(
ShareUtil.isFromWebxdc(this) && ShareUtil.getSharedUris(this).size() == 1
);
menu.findItem(R.id.menu_export_attachment)
.setVisible(ShareUtil.isFromWebxdc(this) && ShareUtil.getSharedUris(this).size() == 1);
} else {
inflater.inflate(R.menu.text_secure_normal, menu);
menu.findItem(R.id.menu_global_map).setVisible(Prefs.isLocationStreamingEnabled(this));
@@ -382,7 +468,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
proxyItem.setVisible(false);
} else {
boolean proxyEnabled = DcHelper.getInt(this, CONFIG_PROXY_ENABLED) == 1;
proxyItem.setIcon(proxyEnabled? R.drawable.ic_proxy_enabled_24 : R.drawable.ic_proxy_disabled_24);
proxyItem.setIcon(
proxyEnabled ? R.drawable.ic_proxy_enabled_24 : R.drawable.ic_proxy_disabled_24);
proxyItem.setVisible(true);
}
MenuCompat.setGroupDividerEnabled(menu, true);
@@ -393,42 +480,42 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
}
private void initializeSearchListener() {
searchAction.setOnClickListener(v -> {
searchToolbar.display(searchAction.getX() + (searchAction.getWidth() / 2),
searchAction.getY() + (searchAction.getHeight() / 2));
});
searchAction.setOnClickListener(
v -> {
searchToolbar.display(
searchAction.getX() + (searchAction.getWidth() / 2),
searchAction.getY() + (searchAction.getHeight() / 2));
});
searchToolbar.setListener(new SearchToolbar.SearchListener() {
@Override
public void onSearchTextChange(String text) {
String trimmed = text.trim();
searchToolbar.setListener(
new SearchToolbar.SearchListener() {
@Override
public void onSearchTextChange(String text) {
String trimmed = text.trim();
if (trimmed.length() > 0) {
if (searchFragment == null) {
searchFragment = SearchFragment.newInstance();
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, searchFragment, null)
.commit();
if (trimmed.length() > 0) {
if (searchFragment == null) {
searchFragment = SearchFragment.newInstance();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_container, searchFragment, null)
.commit();
}
searchFragment.updateSearchQuery(trimmed);
} else if (searchFragment != null) {
getSupportFragmentManager().beginTransaction().remove(searchFragment).commit();
searchFragment = null;
}
}
searchFragment.updateSearchQuery(trimmed);
} else if (searchFragment != null) {
getSupportFragmentManager().beginTransaction()
.remove(searchFragment)
.commit();
searchFragment = null;
}
}
@Override
public void onSearchClosed() {
if (searchFragment != null) {
getSupportFragmentManager().beginTransaction()
.remove(searchFragment)
.commit();
searchFragment = null;
}
}
});
@Override
public void onSearchClosed() {
if (searchFragment != null) {
getSupportFragmentManager().beginTransaction().remove(searchFragment).commit();
searchFragment = null;
}
}
});
}
@Override
@@ -443,7 +530,9 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
startActivity(new Intent(this, ApplicationPreferencesActivity.class));
return true;
} else if (itemId == R.id.menu_qr) {
new IntentIntegrator(this).setCaptureActivity(QrActivity.class).initiateScan();
Intent intent =
new IntentIntegrator(this).setCaptureActivity(QrActivity.class).createScanIntent();
qrScannerLauncher.launch(intent);
return true;
} else if (itemId == R.id.menu_global_map) {
WebxdcActivity.openMaps(this, 0);
@@ -452,51 +541,56 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
startActivity(new Intent(this, ProxySettingsActivity.class));
return true;
} else if (itemId == android.R.id.home) {
onBackPressed();
getOnBackPressedDispatcher().onBackPressed();
return true;
} else if (itemId == R.id.menu_all_media) {
startActivity(new Intent(this, AllMediaActivity.class));
return true;
} else if (itemId == R.id.menu_export_attachment) {
handleSaveAttachment();
} else if (itemId == R.id.menu_switch_account) {
AccountManager.getInstance().showSwitchAccountMenu(this, true);
return true;
}
return false;
}
private void handleSaveAttachment() {
SaveAttachmentTask.showWarningDialog(this, (dialogInterface, i) -> {
if (StorageUtil.canWriteToMediaStore(this)) {
performSave();
return;
}
SaveAttachmentTask.showWarningDialog(
this,
(dialogInterface, i) -> {
if (StorageUtil.canWriteToMediaStore(this)) {
performSave();
return;
}
Permissions.with(this)
Permissions.with(this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.alwaysGrantOnSdk30()
.ifNecessary()
.withPermanentDenialDialog(getString(R.string.perm_explain_access_to_storage_denied))
.onAllGranted(this::performSave)
.execute();
});
});
}
private void performSave() {
ArrayList<Uri> uriList = ShareUtil.getSharedUris(this);
ArrayList<Uri> uriList = ShareUtil.getSharedUris(this);
Uri uri = uriList.get(0);
String mimeType = PersistentBlobProvider.getMimeType(this, uri);
String fileName = PersistentBlobProvider.getFileName(this, uri);
SaveAttachmentTask.Attachment[] attachments = new SaveAttachmentTask.Attachment[]{
new SaveAttachmentTask.Attachment(uri, mimeType, new Date().getTime(), fileName)
};
SaveAttachmentTask.Attachment[] attachments =
new SaveAttachmentTask.Attachment[] {
new SaveAttachmentTask.Attachment(uri, mimeType, new Date().getTime(), fileName)
};
SaveAttachmentTask saveTask = new SaveAttachmentTask(this);
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachments);
onBackPressed();
getOnBackPressedDispatcher().onBackPressed();
}
private void handleOpenpgp4fpr() {
if (getIntent() != null &&
Intent.ACTION_VIEW.equals(getIntent().getAction())) {
if (getIntent() != null && Intent.ACTION_VIEW.equals(getIntent().getAction())) {
Uri uri = getIntent().getData();
if (uri == null) {
return;
@@ -526,15 +620,21 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
searchToolbar.clearFocus();
final DcContext dcContext = DcHelper.getContext(this);
if (isForwarding(this) && dcContext.getChat(chatId).isSelfTalk()) {
int fwdAccId = getForwardedMessageAccountId(this);
if (fwdAccId == dcContext.getAccountId() && dcContext.getChat(chatId).isSelfTalk()) {
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
Toast.makeText(this, DynamicTheme.getCheckmarkEmoji(this) + " " + getString(R.string.saved), Toast.LENGTH_SHORT).show();
Toast.makeText(
this,
DynamicTheme.getCheckmarkEmoji(this) + " " + getString(R.string.saved),
Toast.LENGTH_SHORT)
.show();
handleResetRelaying();
finish();
} else {
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(CHAT_ID_EXTRA, chatId);
intent.putExtra(STARTING_POSITION_EXTRA, startingPosition);
intent.putExtra(ConversationActivity.ACCOUNT_ID_EXTRA, dcContext.getAccountId());
intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, chatId);
intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, startingPosition);
if (isRelayingMessageContent(this)) {
acquireRelayMessageContent(this, intent);
}
@@ -554,15 +654,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out);
}
@Override
public void onBackPressed() {
if (searchToolbar.isVisible()) searchToolbar.collapse();
else if (isRelayingMessageContent(this)) {
handleResetRelaying();
finish();
} else super.onBackPressed();
}
private void createChat() {
Intent intent = new Intent(this, NewConversationActivity.class);
if (isRelayingMessageContent(this)) {
@@ -579,9 +670,59 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
startActivity(Intent.createChooser(intent, getString(R.string.chat_share_with_title)));
}
private void addDeviceMessages(boolean fromWelcome) {
// update messages - for new messages, do not reuse or modify strings but create new ones.
// it is not needed to keep all past update messages, however, when deleted, also the strings
// should be deleted.
try {
DcContext dcContext = DcHelper.getContext(this);
final String deviceMsgLabel = "update_2_33_1_android";
if (!dcContext.wasDeviceMsgEverAdded(deviceMsgLabel)) {
DcMsg msg = null;
if (!getIntent().getBooleanExtra(FROM_WELCOME, false)) {
msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
// InputStream inputStream =
// getResources().getAssets().open("device-messages/green-checkmark.jpg");
// String outputFile = DcHelper.getBlobdirFile(dcContext, "green-checkmark", ".jpg");
// Util.copy(inputStream, new FileOutputStream(outputFile));
// msg.setFile(outputFile, "image/jpeg");
msg.setText(getString(R.string.update_2_33, "https://arcanechat.me/#contribute"));
}
dcContext.addDeviceMsg(deviceMsgLabel, msg);
if (Prefs.getStringPreference(this, Prefs.LAST_DEVICE_MSG_LABEL, "")
.equals(deviceMsgLabel)) {
int deviceChatId = dcContext.getChatIdByContactId(DcContact.DC_CONTACT_ID_DEVICE);
if (deviceChatId != 0) {
dcContext.marknoticedChat(deviceChatId);
}
}
Prefs.setStringPreference(this, Prefs.LAST_DEVICE_MSG_LABEL, deviceMsgLabel);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void onProfileSwitched(int profileId) {
addDeviceMessages(false);
refreshAvatar();
refreshUnreadIndicator();
refreshTitle();
conversationListFragment.loadChatlistAsync();
}
public void onDeleteProfile(int profileId) {
deleteProfileId = profileId;
boolean result = ScreenLockUtil.applyScreenLock(this, getString(R.string.delete_account), getString(R.string.enter_system_secret_to_continue), REQUEST_CODE_CONFIRM_CREDENTIALS_DELETE_PROFILE);
boolean result =
ScreenLockUtil.applyScreenLock(
this,
getString(R.string.delete_account),
getString(R.string.enter_system_secret_to_continue),
deleteProfileLockLauncher);
if (!result) {
deleteProfile(profileId);
}
@@ -594,9 +735,14 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
accounts.removeAccount(profileId);
if (selected) {
DcContext selAcc = accounts.getSelectedAccount();
AccountManager.getInstance().switchAccountAndStartActivity(this, selAcc.isOk()? selAcc.getAccountId() : 0);
if (selAcc.isOk()) {
AccountManager.getInstance().switchAccount(this, selAcc.getAccountId());
onProfileSwitched(selAcc.getAccountId());
} else {
AccountManager.getInstance().switchAccountAndStartActivity(this, 0);
}
} else {
AccountManager.getInstance().showSwitchAccountMenu(this);
AccountManager.getInstance().showSwitchAccountMenu(this, false);
}
// title update needed to show "Delta Chat" in case there is only one profile left
@@ -604,38 +750,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK) return;
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
switch (requestCode) {
case IntentIntegrator.REQUEST_CODE:
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
qrData = scanResult.getContents();
qrCodeHandler.handleQrData(qrData, SecurejoinSource.Scan, SecurejoinUiPath.QrIcon);
break;
case ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS:
// QrCodeHandler requested user authorization before adding a relay
// and it was granted, so proceed to add the relay
if (qrData != null) {
qrCodeHandler.addRelay(qrData);
qrData = null;
}
break;
case REQUEST_CODE_CONFIRM_CREDENTIALS_DELETE_PROFILE:
if (deleteProfileId != 0) {
deleteProfile(deleteProfileId);
deleteProfileId = 0;
}
break;
default:
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
public void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
}
@@ -20,48 +20,44 @@ import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcChatlist;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcLot;
import java.lang.ref.WeakReference;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.lang.ref.WeakReference;
/**
* A CursorAdapter for building a list of conversation threads.
*
* @author Moxie Marlinspike
*/
class ConversationListAdapter extends BaseConversationListAdapter<ConversationListAdapter.ViewHolder> {
class ConversationListAdapter
extends BaseConversationListAdapter<ConversationListAdapter.ViewHolder> {
private static final int MESSAGE_TYPE_SWITCH_ARCHIVE = 1;
private static final int MESSAGE_TYPE_THREAD = 2;
private static final int MESSAGE_TYPE_INBOX_ZERO = 3;
private static final int MESSAGE_TYPE_THREAD = 2;
private static final int MESSAGE_TYPE_INBOX_ZERO = 3;
private final WeakReference<Context> context;
private @NonNull DcContext dcContext;
private @NonNull DcChatlist dcChatlist;
private final @NonNull GlideRequests glideRequests;
private final @NonNull LayoutInflater inflater;
private final @Nullable ItemClickListener clickListener;
private final WeakReference<Context> context;
private @NonNull DcContext dcContext;
private @NonNull DcChatlist dcChatlist;
private final @NonNull GlideRequests glideRequests;
private final @NonNull LayoutInflater inflater;
private final @Nullable ItemClickListener clickListener;
protected static class ViewHolder extends RecyclerView.ViewHolder {
public <V extends View & BindableConversationListItem> ViewHolder(final @NonNull V itemView)
{
public <V extends View & BindableConversationListItem> ViewHolder(final @NonNull V itemView) {
super(itemView);
}
public BindableConversationListItem getItem() {
return (BindableConversationListItem)itemView;
return (BindableConversationListItem) itemView;
}
}
@@ -75,17 +71,17 @@ class ConversationListAdapter extends BaseConversationListAdapter<ConversationLi
return dcChatlist.getChatId(i);
}
ConversationListAdapter(@NonNull Context context,
@NonNull GlideRequests glideRequests,
@Nullable ItemClickListener clickListener)
{
ConversationListAdapter(
@NonNull Context context,
@NonNull GlideRequests glideRequests,
@Nullable ItemClickListener clickListener) {
super();
this.context = new WeakReference<>(context);
this.glideRequests = glideRequests;
this.dcContext = DcHelper.getContext(context);
this.dcChatlist = new DcChatlist(0, 0);
this.inflater = LayoutInflater.from(context);
this.clickListener = clickListener;
this.context = new WeakReference<>(context);
this.glideRequests = glideRequests;
this.dcContext = DcHelper.getContext(context);
this.dcChatlist = new DcChatlist(0, 0);
this.inflater = LayoutInflater.from(context);
this.clickListener = clickListener;
setHasStableIds(true);
}
@@ -93,29 +89,37 @@ class ConversationListAdapter extends BaseConversationListAdapter<ConversationLi
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == MESSAGE_TYPE_SWITCH_ARCHIVE) {
final ConversationListItem item = (ConversationListItem)inflater.inflate(R.layout.conversation_list_item_view, parent, false);
final ConversationListItem item =
(ConversationListItem)
inflater.inflate(R.layout.conversation_list_item_view, parent, false);
item.getLayoutParams().height = ViewUtil.dpToPx(54);
item.findViewById(R.id.subject).setVisibility(View.GONE);
item.findViewById(R.id.date).setVisibility(View.GONE);
item.setOnClickListener(v -> {
if (clickListener != null) clickListener.onSwitchToArchive();
});
item.setOnClickListener(
v -> {
if (clickListener != null) clickListener.onSwitchToArchive();
});
return new ViewHolder(item);
} else if (viewType == MESSAGE_TYPE_INBOX_ZERO) {
return new ViewHolder((ConversationListItemInboxZero)inflater.inflate(R.layout.conversation_list_item_inbox_zero, parent, false));
return new ViewHolder(
(ConversationListItemInboxZero)
inflater.inflate(R.layout.conversation_list_item_inbox_zero, parent, false));
} else {
final ConversationListItem item = (ConversationListItem)inflater.inflate(R.layout.conversation_list_item_view,
parent, false);
final ConversationListItem item =
(ConversationListItem)
inflater.inflate(R.layout.conversation_list_item_view, parent, false);
item.setOnClickListener(view -> {
if (clickListener != null) clickListener.onItemClick(item);
});
item.setOnClickListener(
view -> {
if (clickListener != null) clickListener.onItemClick(item);
});
item.setOnLongClickListener(view -> {
if (clickListener != null) clickListener.onItemLongClick(item);
return true;
});
item.setOnLongClickListener(
view -> {
if (clickListener != null) clickListener.onItemLongClick(item);
return true;
});
return new ViewHolder(item);
}
@@ -130,7 +134,15 @@ class ConversationListAdapter extends BaseConversationListAdapter<ConversationLi
DcChat chat = dcContext.getChat(dcChatlist.getChatId(i));
DcLot summary = dcChatlist.getSummary(i, chat);
viewHolder.getItem().bind(DcHelper.getThreadRecord(context, summary, chat), dcChatlist.getMsgId(i), summary, glideRequests, batchSet, batchMode);
viewHolder
.getItem()
.bind(
DcHelper.getThreadRecord(context, summary, chat),
dcChatlist.getMsgId(i),
summary,
glideRequests,
batchSet,
batchMode);
}
@Override
@@ -139,7 +151,7 @@ class ConversationListAdapter extends BaseConversationListAdapter<ConversationLi
if (chatId == DcChat.DC_CHAT_ID_ARCHIVED_LINK) {
return MESSAGE_TYPE_SWITCH_ARCHIVE;
} else if(chatId == DcChat.DC_CHAT_ID_ALLDONE_HINT) {
} else if (chatId == DcChat.DC_CHAT_ID_ALLDONE_HINT) {
return MESSAGE_TYPE_INBOX_ZERO;
} else {
return MESSAGE_TYPE_THREAD;
@@ -159,7 +171,9 @@ class ConversationListAdapter extends BaseConversationListAdapter<ConversationLi
interface ItemClickListener {
void onItemClick(ConversationListItem item);
void onItemLongClick(ConversationListItem item);
void onSwitchToArchive();
}
@@ -169,7 +183,7 @@ class ConversationListAdapter extends BaseConversationListAdapter<ConversationLi
return;
}
if (chatlist == null) {
dcChatlist = new DcChatlist(0, 0);
dcChatlist = new DcChatlist(0, 0);
} else {
dcChatlist = chatlist;
dcContext = DcHelper.getContext(context);
@@ -11,20 +11,19 @@ import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.activity.OnBackPressedCallback;
import com.b44t.messenger.DcChat;
import org.thoughtcrime.securesms.connect.DcHelper;
public class ConversationListArchiveActivity extends PassphraseRequiredActionBarActivity
implements ConversationListFragment.ConversationSelectedListener
{
implements ConversationListFragment.ConversationSelectedListener {
@Override
protected void onCreate(Bundle icicle, boolean ready) {
setContentView(R.layout.activity_conversation_list_archive);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (isRelayingMessageContent(this)) {
getSupportActionBar().setTitle(isSharing(this) ? R.string.chat_share_with_title : R.string.forward_to);
getSupportActionBar()
.setTitle(isSharing(this) ? R.string.chat_share_with_title : R.string.forward_to);
getSupportActionBar().setSubtitle(R.string.chat_archived_label);
} else {
getSupportActionBar().setTitle(R.string.chat_archived_label);
@@ -33,6 +32,26 @@ public class ConversationListArchiveActivity extends PassphraseRequiredActionBar
Bundle bundle = new Bundle();
bundle.putBoolean(ConversationListFragment.ARCHIVE, true);
initFragment(R.id.fragment, new ConversationListFragment(), bundle);
getOnBackPressedDispatcher()
.addCallback(
this,
new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (!isRelayingMessageContent(ConversationListArchiveActivity.this)) {
// Load the ConversationListActivity in case it's not existent for some reason
Intent intent =
new Intent(
ConversationListArchiveActivity.this, ConversationListActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
}
setEnabled(false);
getOnBackPressedDispatcher().onBackPressed();
}
});
}
@Override
@@ -63,7 +82,7 @@ public class ConversationListArchiveActivity extends PassphraseRequiredActionBar
int itemId = item.getItemId();
if (itemId == android.R.id.home) {
onBackPressed();
getOnBackPressedDispatcher().onBackPressed();
return true;
} else if (itemId == R.id.mark_as_read) {
DcHelper.getContext(this).marknoticedChat(DcChat.DC_CHAT_ID_ARCHIVED_LINK);
@@ -73,20 +92,6 @@ public class ConversationListArchiveActivity extends PassphraseRequiredActionBar
return false;
}
@Override
public void onBackPressed() {
if (isRelayingMessageContent(this)) {
// Go back to the ConversationListRelayingActivity
super.onBackPressed();
} else {
// Load the ConversationListActivity in case it's not existent for some reason
Intent intent = new Intent(this, ConversationListActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
}
}
@Override
public void onCreateConversation(int chatId) {
Intent intent = new Intent(this, ConversationActivity.class);
@@ -29,16 +29,15 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.b44t.messenger.DcChatlist;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import com.b44t.messenger.DcMsg;
import java.util.Timer;
import java.util.TimerTask;
import org.thoughtcrime.securesms.ConversationListAdapter.ItemClickListener;
import org.thoughtcrime.securesms.components.recyclerview.DeleteItemAnimator;
import org.thoughtcrime.securesms.components.reminder.DozeReminder;
@@ -52,25 +51,21 @@ import org.thoughtcrime.securesms.util.ShareUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Timer;
import java.util.TimerTask;
public class ConversationListFragment extends BaseConversationListFragment
implements ItemClickListener, DcEventCenter.DcEventDelegate {
implements ItemClickListener, DcEventCenter.DcEventDelegate {
public static final String ARCHIVE = "archive";
public static final String RELOAD_LIST = "reload_list";
private static final String TAG = ConversationListFragment.class.getSimpleName();
private RecyclerView list;
private View emptyState;
private TextView emptySearch;
private final String queryFilter = "";
private boolean archive;
private Timer reloadTimer;
private boolean chatlistJustLoaded;
private boolean reloadTimerInstantly;
private RecyclerView list;
private View emptyState;
private TextView emptySearch;
private final String queryFilter = "";
private boolean archive;
private Timer reloadTimer;
private boolean chatlistJustLoaded;
private boolean reloadTimerInstantly;
@Override
public void onCreate(Bundle icicle) {
@@ -103,10 +98,10 @@ public class ConversationListFragment extends BaseConversationListFragment
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) {
final View view = inflater.inflate(R.layout.conversation_list_fragment, container, false);
list = ViewUtil.findById(view, R.id.list);
fab = ViewUtil.findById(view, R.id.fab);
emptyState = ViewUtil.findById(view, R.id.empty_state);
emptySearch = ViewUtil.findById(view, R.id.empty_search);
list = ViewUtil.findById(view, R.id.list);
fab = ViewUtil.findById(view, R.id.fab);
emptyState = ViewUtil.findById(view, R.id.empty_state);
emptySearch = ViewUtil.findById(view, R.id.empty_search);
// add padding to avoid content hidden behind system bars
ViewUtil.applyWindowInsets(list, true, archive, true, true);
@@ -145,22 +140,25 @@ public class ConversationListFragment extends BaseConversationListFragment
updateReminders();
if (requireActivity().getIntent().getIntExtra(RELOAD_LIST, 0) == 1
&& !chatlistJustLoaded) {
if (requireActivity().getIntent().getIntExtra(RELOAD_LIST, 0) == 1 && !chatlistJustLoaded) {
loadChatlist();
reloadTimerInstantly = false;
}
chatlistJustLoaded = false;
reloadTimer = new Timer();
reloadTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Util.runOnMain(() -> {
list.getAdapter().notifyDataSetChanged();
});
}
}, reloadTimerInstantly? 0 : 60 * 1000, 60 * 1000);
reloadTimer.scheduleAtFixedRate(
new TimerTask() {
@Override
public void run() {
Util.runOnMain(
() -> {
list.getAdapter().notifyDataSetChanged();
});
}
},
reloadTimerInstantly ? 0 : 60 * 1000,
60 * 1000);
}
@Override
@@ -184,7 +182,8 @@ public class ConversationListFragment extends BaseConversationListFragment
@SuppressLint({"StaticFieldLeak", "NewApi"})
private void updateReminders() {
// by the time onPostExecute() is asynchronously run, getActivity() might return null, so get the activity here:
// by the time onPostExecute() is asynchronously run, getActivity() might return null, so get
// the activity here:
Activity activity = requireActivity();
new AsyncTask<Context, Void, Void>() {
@Override
@@ -204,22 +203,29 @@ public class ConversationListFragment extends BaseConversationListFragment
@Override
protected void onPostExecute(Void result) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (!Prefs.getBooleanPreference(activity, Prefs.ASKED_FOR_NOTIFICATION_PERMISSION, false)) {
if (!Prefs.getBooleanPreference(
activity, Prefs.ASKED_FOR_NOTIFICATION_PERMISSION, false)) {
Prefs.setBooleanPreference(activity, Prefs.ASKED_FOR_NOTIFICATION_PERMISSION, true);
Permissions.with(activity)
.request(Manifest.permission.POST_NOTIFICATIONS)
.ifNecessary()
.onAllGranted(() -> {
DozeReminder.maybeAskDirectly(activity);
})
.onAnyDenied(() -> {
final DcContext dcContext = DcHelper.getContext(activity);
DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
msg.setText("\uD83D\uDC49 "+activity.getString(R.string.notifications_disabled)+" \uD83D\uDC48\n\n"
+activity.getString(R.string.perm_explain_access_to_notifications_denied));
dcContext.addDeviceMsg("android.notifications-disabled", msg);
})
.execute();
.request(Manifest.permission.POST_NOTIFICATIONS)
.ifNecessary()
.onAllGranted(
() -> {
DozeReminder.maybeAskDirectly(activity);
})
.onAnyDenied(
() -> {
final DcContext dcContext = DcHelper.getContext(activity);
DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
msg.setText(
"\uD83D\uDC49 "
+ activity.getString(R.string.notifications_disabled)
+ " \uD83D\uDC48\n\n"
+ activity.getString(
R.string.perm_explain_access_to_notifications_denied));
dcContext.addDeviceMsg("android.notifications-disabled", msg);
})
.execute();
} else {
DozeReminder.maybeAskDirectly(activity);
}
@@ -233,7 +239,8 @@ public class ConversationListFragment extends BaseConversationListFragment
private final Object loadChatlistLock = new Object();
private boolean inLoadChatlist;
private boolean needsAnotherLoad;
private void loadChatlistAsync() {
public void loadChatlistAsync() {
synchronized (loadChatlistLock) {
needsAnotherLoad = true;
if (inLoadChatlist) {
@@ -243,21 +250,22 @@ public class ConversationListFragment extends BaseConversationListFragment
inLoadChatlist = true;
}
Util.runOnAnyBackgroundThread(() -> {
while(true) {
synchronized (loadChatlistLock) {
if (!needsAnotherLoad) {
inLoadChatlist = false;
return;
}
needsAnotherLoad = false;
}
Util.runOnAnyBackgroundThread(
() -> {
while (true) {
synchronized (loadChatlistLock) {
if (!needsAnotherLoad) {
inLoadChatlist = false;
return;
}
needsAnotherLoad = false;
}
Log.i(TAG, "executing debounced chatlist loading");
loadChatlist();
Util.sleep(100);
}
});
Log.i(TAG, "executing debounced chatlist loading");
loadChatlist();
Util.sleep(100);
}
});
}
private void loadChatlist() {
@@ -272,32 +280,36 @@ public class ConversationListFragment extends BaseConversationListFragment
Context context = getContext();
if (context == null) {
// can't load chat list at this time, see: https://github.com/deltachat/deltachat-android/issues/2012
// can't load chat list at this time, see:
// https://github.com/deltachat/deltachat-android/issues/2012
Log.w(TAG, "Ignoring call to loadChatlist()");
return;
}
DcChatlist chatlist = DcHelper.getContext(context).getChatlist(listflags, queryFilter.isEmpty() ? null : queryFilter, 0);
DcChatlist chatlist =
DcHelper.getContext(context)
.getChatlist(listflags, queryFilter.isEmpty() ? null : queryFilter, 0);
Util.runOnMain(() -> {
if (chatlist.getCnt() <= 0 && TextUtils.isEmpty(queryFilter)) {
list.setVisibility(View.INVISIBLE);
emptyState.setVisibility(View.VISIBLE);
emptySearch.setVisibility(View.INVISIBLE);
fab.startPulse(3 * 1000);
} else if (chatlist.getCnt() <= 0 && !TextUtils.isEmpty(queryFilter)) {
list.setVisibility(View.INVISIBLE);
emptyState.setVisibility(View.GONE);
emptySearch.setVisibility(View.VISIBLE);
emptySearch.setText(getString(R.string.search_no_result_for_x, queryFilter));
} else {
list.setVisibility(View.VISIBLE);
emptyState.setVisibility(View.GONE);
emptySearch.setVisibility(View.INVISIBLE);
fab.stopPulse();
}
Util.runOnMain(
() -> {
if (chatlist.getCnt() <= 0 && TextUtils.isEmpty(queryFilter)) {
list.setVisibility(View.INVISIBLE);
emptyState.setVisibility(View.VISIBLE);
emptySearch.setVisibility(View.INVISIBLE);
fab.startPulse(3 * 1000);
} else if (chatlist.getCnt() <= 0 && !TextUtils.isEmpty(queryFilter)) {
list.setVisibility(View.INVISIBLE);
emptyState.setVisibility(View.GONE);
emptySearch.setVisibility(View.VISIBLE);
emptySearch.setText(getString(R.string.search_no_result_for_x, queryFilter));
} else {
list.setVisibility(View.VISIBLE);
emptyState.setVisibility(View.GONE);
emptySearch.setVisibility(View.INVISIBLE);
fab.stopPulse();
}
((ConversationListAdapter)list.getAdapter()).changeData(chatlist);
});
((ConversationListAdapter) list.getAdapter()).changeData(chatlist);
});
}
@Override
@@ -307,12 +319,12 @@ public class ConversationListFragment extends BaseConversationListFragment
@Override
protected void setFabVisibility(boolean isActionMode) {
fab.setVisibility((isActionMode || !archive)? View.VISIBLE : View.GONE);
fab.setVisibility((isActionMode || !archive) ? View.VISIBLE : View.GONE);
}
@Override
public void onItemClick(ConversationListItem item) {
onItemClick(item.getChatId());
onItemClick(item.getChatId());
}
@Override
@@ -322,14 +334,15 @@ public class ConversationListFragment extends BaseConversationListFragment
@Override
public void onSwitchToArchive() {
((ConversationSelectedListener)requireActivity()).onSwitchToArchive();
((ConversationSelectedListener) requireActivity()).onSwitchToArchive();
}
@Override
public void handleEvent(@NonNull DcEvent event) {
final int accId = event.getAccountId();
if (event.getId() == DcContext.DC_EVENT_CHAT_DELETED) {
DcHelper.getNotificationCenter(requireActivity()).removeNotifications(accId, event.getData1Int());
DcHelper.getNotificationCenter(requireActivity())
.removeNotifications(accId, event.getData1Int());
} else if (accId != DcHelper.getContext(requireActivity()).getAccountId()) {
Activity activity = getActivity();
if (activity instanceof ConversationListActivity) {
@@ -352,7 +365,4 @@ public class ConversationListFragment extends BaseConversationListFragment
loadChatlistAsync();
}
}
}
@@ -30,17 +30,16 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.amulyakhare.textdrawable.TextDrawable;
import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcLot;
import com.b44t.messenger.DcMsg;
import java.util.Collections;
import java.util.Set;
import org.thoughtcrime.securesms.components.AvatarView;
import org.thoughtcrime.securesms.components.DeliveryStatusView;
import org.thoughtcrime.securesms.components.FromTextView;
@@ -53,25 +52,22 @@ import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Collections;
import java.util.Set;
public class ConversationListItem extends RelativeLayout
implements BindableConversationListItem, Unbindable
{
private final static Typeface BOLD_TYPEFACE = Typeface.create("sans-serif-medium", Typeface.NORMAL);
private final static Typeface LIGHT_TYPEFACE = Typeface.create("sans-serif", Typeface.NORMAL);
implements BindableConversationListItem, Unbindable {
private static final Typeface BOLD_TYPEFACE =
Typeface.create("sans-serif-medium", Typeface.NORMAL);
private static final Typeface LIGHT_TYPEFACE = Typeface.create("sans-serif", Typeface.NORMAL);
private Set<Long> selectedThreads;
private long chatId;
private int msgId;
private TextView subjectView;
private FromTextView fromView;
private TextView dateView;
private TextView archivedBadgeView;
private TextView requestBadgeView;
private Set<Long> selectedThreads;
private long chatId;
private int msgId;
private TextView subjectView;
private FromTextView fromView;
private TextView dateView;
private TextView archivedBadgeView;
private TextView requestBadgeView;
private DeliveryStatusView deliveryStatusIndicator;
private ImageView unreadIndicator;
private ImageView unreadIndicator;
private AvatarView avatar;
@@ -86,72 +82,74 @@ public class ConversationListItem extends RelativeLayout
@Override
protected void onFinishInflate() {
super.onFinishInflate();
this.subjectView = findViewById(R.id.subject);
this.fromView = findViewById(R.id.from_text);
this.dateView = findViewById(R.id.date);
this.subjectView = findViewById(R.id.subject);
this.fromView = findViewById(R.id.from_text);
this.dateView = findViewById(R.id.date);
this.deliveryStatusIndicator = new DeliveryStatusView(findViewById(R.id.delivery_indicator));
this.avatar = findViewById(R.id.avatar);
this.archivedBadgeView = findViewById(R.id.archived_badge);
this.requestBadgeView = findViewById(R.id.request_badge);
this.unreadIndicator = findViewById(R.id.unread_indicator);
this.avatar = findViewById(R.id.avatar);
this.archivedBadgeView = findViewById(R.id.archived_badge);
this.requestBadgeView = findViewById(R.id.request_badge);
this.unreadIndicator = findViewById(R.id.unread_indicator);
ViewUtil.setTextViewGravityStart(this.fromView, getContext());
ViewUtil.setTextViewGravityStart(this.subjectView, getContext());
}
@Override
public void bind(@NonNull ThreadRecord thread,
int msgId,
@NonNull DcLot dcSummary,
@NonNull GlideRequests glideRequests,
@NonNull Set<Long> selectedThreads,
boolean batchMode)
{
public void bind(
@NonNull ThreadRecord thread,
int msgId,
@NonNull DcLot dcSummary,
@NonNull GlideRequests glideRequests,
@NonNull Set<Long> selectedThreads,
boolean batchMode) {
bind(thread, msgId, dcSummary, glideRequests, selectedThreads, batchMode, null);
}
public void bind(@NonNull ThreadRecord thread,
int msgId,
@NonNull DcLot dcSummary,
@NonNull GlideRequests glideRequests,
@NonNull Set<Long> selectedThreads,
boolean batchMode,
@Nullable String highlightSubstring)
{
this.selectedThreads = selectedThreads;
Recipient recipient = thread.getRecipient();
this.chatId = thread.getThreadId();
this.msgId = msgId;
public void bind(
@NonNull ThreadRecord thread,
int msgId,
@NonNull DcLot dcSummary,
@NonNull GlideRequests glideRequests,
@NonNull Set<Long> selectedThreads,
boolean batchMode,
@Nullable String highlightSubstring) {
this.selectedThreads = selectedThreads;
Recipient recipient = thread.getRecipient();
this.chatId = thread.getThreadId();
this.msgId = msgId;
int state = dcSummary.getState();
int unreadCount = thread.getUnreadCount();
int state = dcSummary.getState();
if (highlightSubstring != null) {
this.fromView.setText(getHighlightedSpan(recipient.getName(), highlightSubstring));
} else {
this.fromView.setText(recipient, state!=DcMsg.DC_STATE_IN_FRESH);
this.fromView.setText(recipient, state != DcMsg.DC_STATE_IN_FRESH);
}
subjectView.setVisibility(chatId == DcChat.DC_CHAT_ID_ARCHIVED_LINK? GONE : VISIBLE);
subjectView.setVisibility(chatId == DcChat.DC_CHAT_ID_ARCHIVED_LINK ? GONE : VISIBLE);
this.subjectView.setText(thread.getDisplayBody());
this.subjectView.setTypeface(state==DcMsg.DC_STATE_IN_FRESH ? BOLD_TYPEFACE : LIGHT_TYPEFACE);
this.subjectView.setTextColor(state==DcMsg.DC_STATE_IN_FRESH ? ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_unread_color)
: ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_subject_color));
this.subjectView.setTypeface(state == DcMsg.DC_STATE_IN_FRESH ? BOLD_TYPEFACE : LIGHT_TYPEFACE);
this.subjectView.setTextColor(
state == DcMsg.DC_STATE_IN_FRESH
? ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_unread_color)
: ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_subject_color));
if (thread.getDate() > 0) {
CharSequence date = DateUtils.getBriefRelativeTimeSpanString(getContext(), thread.getDate());
dateView.setText(date);
}
else {
} else {
dateView.setText("");
}
dateView.setCompoundDrawablesWithIntrinsicBounds(
thread.isSendingLocations()? R.drawable.ic_location_chatlist : 0, 0,
thread.getVisibility()==DcChat.DC_CHAT_VISIBILITY_PINNED? R.drawable.ic_pinned_chatlist : 0, 0
);
thread.isSendingLocations() ? R.drawable.ic_location_chatlist : 0, 0,
thread.getVisibility() == DcChat.DC_CHAT_VISIBILITY_PINNED
? R.drawable.ic_pinned_chatlist
: 0,
0);
setStatusIcons(thread.getVisibility(), state, unreadCount, thread.isContactRequest(), thread.isMuted() || chatId == DcChat.DC_CHAT_ID_ARCHIVED_LINK);
setStatusIcons(thread, state);
setBatchState(batchMode);
setBgColor(thread);
@@ -160,22 +158,22 @@ public class ConversationListItem extends RelativeLayout
DcContact contact = recipient.getDcContact();
avatar.setSeenRecently(contact != null && contact.wasSeenRecently());
DcChat dcChat = DcHelper.getContext(getContext()).getChat((int)chatId);
DcChat dcChat = DcHelper.getContext(getContext()).getChat((int) chatId);
boolean isProtected = dcChat.isDeviceTalk() || dcChat.isSelfTalk();
fromView.setCompoundDrawablesWithIntrinsicBounds(
thread.isMuted()? R.drawable.ic_volume_off_grey600_18dp : 0,
thread.isMuted() ? R.drawable.ic_volume_off_grey600_18dp : 0,
0,
isProtected? R.drawable.ic_verified : 0,
isProtected ? R.drawable.ic_verified : 0,
0);
}
public void bind(@NonNull DcContact contact,
@NonNull GlideRequests glideRequests,
@Nullable String highlightSubstring)
{
public void bind(
@NonNull DcContact contact,
@NonNull GlideRequests glideRequests,
@Nullable String highlightSubstring) {
this.selectedThreads = Collections.emptySet();
Recipient recipient = new Recipient(getContext(), contact);
Recipient recipient = new Recipient(getContext(), contact);
fromView.setText(getHighlightedSpan(contact.getDisplayName(), highlightSubstring));
fromView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
@@ -192,14 +190,14 @@ public class ConversationListItem extends RelativeLayout
avatar.setSeenRecently(contact.wasSeenRecently());
}
public void bind(@NonNull DcMsg messageResult,
@NonNull GlideRequests glideRequests,
@Nullable String highlightSubstring)
{
public void bind(
@NonNull DcMsg messageResult,
@NonNull GlideRequests glideRequests,
@Nullable String highlightSubstring) {
DcContext dcContext = DcHelper.getContext(getContext());
DcContact sender = dcContext.getContact(messageResult.getFromId());
this.selectedThreads = Collections.emptySet();
Recipient recipient = new Recipient(getContext(), sender);
Recipient recipient = new Recipient(getContext(), sender);
fromView.setText(recipient, true);
fromView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
@@ -207,10 +205,10 @@ public class ConversationListItem extends RelativeLayout
subjectView.setText(getHighlightedSpan(messageResult.getSummarytext(512), highlightSubstring));
long timestamp = messageResult.getTimestamp();
if(timestamp>0) {
dateView.setText(DateUtils.getBriefRelativeTimeSpanString(getContext(), messageResult.getTimestamp()));
}
else {
if (timestamp > 0) {
dateView.setText(
DateUtils.getBriefRelativeTimeSpanString(getContext(), messageResult.getTimestamp()));
} else {
dateView.setText("");
}
dateView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
@@ -225,8 +223,7 @@ public class ConversationListItem extends RelativeLayout
}
@Override
public void unbind() {
}
public void unbind() {}
private void setBatchState(boolean batch) {
setSelected(batch && selectedThreads.contains(chatId));
@@ -240,81 +237,81 @@ public class ConversationListItem extends RelativeLayout
return msgId;
}
private void setStatusIcons(int visibility, int state, int unreadCount, boolean isContactRequest, boolean isMuted) {
if (visibility==DcChat.DC_CHAT_VISIBILITY_ARCHIVED)
{
private void setStatusIcons(ThreadRecord thread, int state) {
if (thread.getVisibility() == DcChat.DC_CHAT_VISIBILITY_ARCHIVED) {
archivedBadgeView.setVisibility(View.VISIBLE);
requestBadgeView.setVisibility(isContactRequest ? View.VISIBLE : View.GONE);
requestBadgeView.setVisibility(thread.isContactRequest() ? View.VISIBLE : View.GONE);
deliveryStatusIndicator.setNone();
}
else if (isContactRequest) {
} else if (thread.isContactRequest()) {
requestBadgeView.setVisibility(View.VISIBLE);
archivedBadgeView.setVisibility(View.GONE);
deliveryStatusIndicator.setNone();
}
else
{
} else {
requestBadgeView.setVisibility(View.GONE);
archivedBadgeView.setVisibility(View.GONE);
if (state == DcMsg.DC_STATE_OUT_FAILED) {
deliveryStatusIndicator.setFailed();
} else if (state == DcMsg.DC_STATE_OUT_MDN_RCVD) {
deliveryStatusIndicator.setRead();
} else if (state == DcMsg.DC_STATE_OUT_DELIVERED) {
deliveryStatusIndicator.setSent();
} else if (state == DcMsg.DC_STATE_OUT_PREPARING) {
deliveryStatusIndicator.setPreparing();
} else if (state == DcMsg.DC_STATE_OUT_PENDING) {
deliveryStatusIndicator.setPending();
} else if (thread.isOutChannel()) {
deliveryStatusIndicator.setNone();
} else if (state == DcMsg.DC_STATE_OUT_MDN_RCVD) {
deliveryStatusIndicator.setRead();
} else if (state == DcMsg.DC_STATE_OUT_DELIVERED) {
deliveryStatusIndicator.setSent();
} else {
deliveryStatusIndicator.setNone();
}
if (state == DcMsg.DC_STATE_OUT_FAILED) {
deliveryStatusIndicator.setTint(Color.RED);
} else {
deliveryStatusIndicator.resetTint();
}
}
if(unreadCount==0 || isContactRequest) {
int unreadCount = thread.getUnreadCount();
if (unreadCount == 0 || thread.isContactRequest()) {
unreadIndicator.setVisibility(View.GONE);
} else {
final int color;
if(isMuted){
color = getResources().getColor(ThemeUtil.isDarkTheme(getContext()) ? R.color.unread_count_muted_dark : R.color.unread_count_muted);
if (thread.isMuted() || chatId == DcChat.DC_CHAT_ID_ARCHIVED_LINK) {
color =
getResources()
.getColor(
ThemeUtil.isDarkTheme(getContext())
? R.color.unread_count_muted_dark
: R.color.unread_count_muted);
} else {
final TypedArray attrs = getContext().obtainStyledAttributes(new int[] {
R.attr.conversation_list_item_unreadcount_color,
});
final TypedArray attrs =
getContext()
.obtainStyledAttributes(
new int[] {
R.attr.conversation_list_item_unreadcount_color,
});
color = attrs.getColor(0, Color.BLACK);
}
unreadIndicator.setImageDrawable(TextDrawable.builder()
.beginConfig()
.width(ViewUtil.dpToPx(getContext(), 24))
.height(ViewUtil.dpToPx(getContext(), 24))
.textColor(Color.WHITE)
.bold()
.endConfig()
.buildRound(String.valueOf(unreadCount), color));
unreadIndicator.setImageDrawable(
TextDrawable.builder()
.beginConfig()
.width(ViewUtil.dpToPx(getContext(), 24))
.height(ViewUtil.dpToPx(getContext(), 24))
.textColor(Color.WHITE)
.bold()
.endConfig()
.buildRound(String.valueOf(unreadCount), color));
unreadIndicator.setVisibility(View.VISIBLE);
}
}
private void setBgColor(ThreadRecord thread) {
int bg = R.attr.conversation_list_item_background;
if (thread!=null && thread.getVisibility()==DcChat.DC_CHAT_VISIBILITY_PINNED) {
bg = R.attr.pinned_list_item_background;
if (thread != null && thread.getVisibility() == DcChat.DC_CHAT_VISIBILITY_PINNED) {
bg = R.attr.pinned_list_item_background;
}
try (TypedArray ta = getContext().obtainStyledAttributes(new int[]{bg})) {
try (TypedArray ta = getContext().obtainStyledAttributes(new int[] {bg})) {
ViewUtil.setBackground(this, ta.getDrawable(0));
}
}
private Spanned getHighlightedSpan(@Nullable String value,
@Nullable String highlight)
{
private Spanned getHighlightedSpan(@Nullable String value, @Nullable String highlight) {
if (TextUtils.isEmpty(value)) {
return new SpannableString("");
}
@@ -325,11 +322,11 @@ public class ConversationListItem extends RelativeLayout
return new SpannableString(value);
}
String normalizedValue = value.toLowerCase(Util.getLocale());
String normalizedTest = highlight.toLowerCase(Util.getLocale());
String normalizedValue = value.toLowerCase(Util.getLocale());
String normalizedTest = highlight.toLowerCase(Util.getLocale());
Spannable spanned = new SpannableString(value);
int searchStartIndex = 0;
Spannable spanned = new SpannableString(value);
int searchStartIndex = 0;
for (String token : normalizedTest.split(" ")) {
if (token.trim().isEmpty()) continue;
@@ -341,7 +338,8 @@ public class ConversationListItem extends RelativeLayout
if (start >= 0) {
int end = Math.min(start + token.length(), spanned.length());
spanned.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
spanned.setSpan(
new StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
searchStartIndex = end;
}
}
@@ -1,21 +1,17 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.b44t.messenger.DcLot;
import java.util.Set;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.mms.GlideRequests;
import java.util.Set;
public class ConversationListItemInboxZero extends LinearLayout implements BindableConversationListItem{
public class ConversationListItemInboxZero extends LinearLayout
implements BindableConversationListItem {
public ConversationListItemInboxZero(Context context) {
super(context);
}
@@ -24,21 +20,25 @@ public class ConversationListItemInboxZero extends LinearLayout implements Binda
super(context, attrs);
}
public ConversationListItemInboxZero(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
public ConversationListItemInboxZero(
Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ConversationListItemInboxZero(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
public ConversationListItemInboxZero(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void unbind() {
}
public void unbind() {}
@Override
public void bind(@NonNull ThreadRecord thread, int msgId, @NonNull DcLot dcSummary, @NonNull GlideRequests glideRequests, @NonNull Set<Long> selectedThreads, boolean batchMode) {
}
public void bind(
@NonNull ThreadRecord thread,
int msgId,
@NonNull DcLot dcSummary,
@NonNull GlideRequests glideRequests,
@NonNull Set<Long> selectedThreads,
boolean batchMode) {}
}
@@ -4,27 +4,24 @@ import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import java.lang.ref.WeakReference;
/**
* "Relaying" means "Forwarding or Sharing".
*
* When forwarding or sharing, we show the ConversationListActivity to the user.
* However, ConversationListActivity has `launchMode="singleTask"`, which means that this will
* destroy the existing ConversationListActivity.
* <p>When forwarding or sharing, we show the ConversationListActivity to the user. However,
* ConversationListActivity has `launchMode="singleTask"`, which means that this will destroy the
* existing ConversationListActivity.
*
* In API 20-29, `startActivityForResult()` could be used instead of `startActivity()`
* to override this behavior and get two instances of ConversationListActivity.
* <p>In API 20-29, `startActivityForResult()` could be used instead of `startActivity()` to
* override this behavior and get two instances of ConversationListActivity.
*
* As this is not possible anymore starting with API 30, we needed another solution, and created
* <p>As this is not possible anymore starting with API 30, we needed another solution, and created
* this activity here.
*
* See https://github.com/deltachat/deltachat-android/issues/1704.
* <p>See https://github.com/deltachat/deltachat-android/issues/1704.
*/
public class ConversationListRelayingActivity extends ConversationListActivity {
static WeakReference<ConversationListRelayingActivity> INSTANCE = null;
@@ -42,7 +39,8 @@ public class ConversationListRelayingActivity extends ConversationListActivity {
// =================== Static Methods ===================
public static void start(Fragment fragment, Intent intent) {
intent.setComponent(new ComponentName(fragment.getContext(), ConversationListRelayingActivity.class));
intent.setComponent(
new ComponentName(fragment.getContext(), ConversationListRelayingActivity.class));
fragment.startActivity(intent);
}
@@ -4,30 +4,32 @@ import android.animation.ValueAnimator;
import android.content.res.Resources;
import android.view.View;
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.util.Util;
final class ConversationSwipeAnimationHelper {
static final float TRIGGER_DX = dpToPx(64);
static final float MAX_DX = dpToPx(96);
static final float MAX_DX = dpToPx(96);
private static final float REPLY_SCALE_OVERSHOOT = 1.8f;
private static final float REPLY_SCALE_MAX = 1.2f;
private static final float REPLY_SCALE_MIN = 1f;
private static final long REPLY_SCALE_OVERSHOOT_DURATION = 200;
private static final float REPLY_SCALE_OVERSHOOT = 1.8f;
private static final float REPLY_SCALE_MAX = 1.2f;
private static final float REPLY_SCALE_MIN = 1f;
private static final long REPLY_SCALE_OVERSHOOT_DURATION = 200;
private static final Interpolator BUBBLE_INTERPOLATOR = new BubblePositionInterpolator(0f, TRIGGER_DX, MAX_DX);
private static final Interpolator REPLY_ALPHA_INTERPOLATOR = new ClampingLinearInterpolator(0f, 1f, 1f);
private static final Interpolator REPLY_TRANSITION_INTERPOLATOR = new ClampingLinearInterpolator(0f, dpToPx(10));
private static final Interpolator AVATAR_INTERPOLATOR = new ClampingLinearInterpolator(0f, dpToPx(8));
private static final Interpolator REPLY_SCALE_INTERPOLATOR = new ClampingLinearInterpolator(REPLY_SCALE_MIN, REPLY_SCALE_MAX);
private static final Interpolator BUBBLE_INTERPOLATOR =
new BubblePositionInterpolator(0f, TRIGGER_DX, MAX_DX);
private static final Interpolator REPLY_ALPHA_INTERPOLATOR =
new ClampingLinearInterpolator(0f, 1f, 1f);
private static final Interpolator REPLY_TRANSITION_INTERPOLATOR =
new ClampingLinearInterpolator(0f, dpToPx(10));
private static final Interpolator AVATAR_INTERPOLATOR =
new ClampingLinearInterpolator(0f, dpToPx(8));
private static final Interpolator REPLY_SCALE_INTERPOLATOR =
new ClampingLinearInterpolator(REPLY_SCALE_MIN, REPLY_SCALE_MAX);
private ConversationSwipeAnimationHelper() {
}
private ConversationSwipeAnimationHelper() {}
public static void update(@NonNull ConversationItem conversationItem, float dx, float sign) {
float progress = dx / TRIGGER_DX;
@@ -46,11 +48,13 @@ final class ConversationSwipeAnimationHelper {
bodyBubble.setTranslationX(BUBBLE_INTERPOLATOR.getInterpolation(dx) * sign);
}
private static void updateReactionsTransition(@NonNull View reactionsContainer, float dx, float sign) {
private static void updateReactionsTransition(
@NonNull View reactionsContainer, float dx, float sign) {
reactionsContainer.setTranslationX(BUBBLE_INTERPOLATOR.getInterpolation(dx) * sign);
}
private static void updateReplyIconTransition(@NonNull View replyIcon, float dx, float progress, float sign) {
private static void updateReplyIconTransition(
@NonNull View replyIcon, float dx, float progress, float sign) {
if (progress > 0.05f) {
replyIcon.setAlpha(REPLY_ALPHA_INTERPOLATOR.getInterpolation(progress));
} else replyIcon.setAlpha(0f);
@@ -64,21 +68,21 @@ final class ConversationSwipeAnimationHelper {
}
}
private static void updateContactPhotoHolderTransition(@Nullable View contactPhotoHolder,
float progress,
float sign)
{
private static void updateContactPhotoHolderTransition(
@Nullable View contactPhotoHolder, float progress, float sign) {
if (contactPhotoHolder == null) return;
contactPhotoHolder.setTranslationX(AVATAR_INTERPOLATOR.getInterpolation(progress) * sign);
}
private static void triggerReplyIcon(@NonNull View replyIcon) {
ValueAnimator animator = ValueAnimator.ofFloat(REPLY_SCALE_MAX, REPLY_SCALE_OVERSHOOT, REPLY_SCALE_MAX);
ValueAnimator animator =
ValueAnimator.ofFloat(REPLY_SCALE_MAX, REPLY_SCALE_OVERSHOOT, REPLY_SCALE_MAX);
animator.setDuration(REPLY_SCALE_OVERSHOOT_DURATION);
animator.addUpdateListener(animation -> {
replyIcon.setScaleX((float) animation.getAnimatedValue());
replyIcon.setScaleY((float) animation.getAnimatedValue());
});
animator.addUpdateListener(
animation -> {
replyIcon.setScaleX((float) animation.getAnimatedValue());
replyIcon.setScaleY((float) animation.getAnimatedValue());
});
animator.start();
}
@@ -93,9 +97,9 @@ final class ConversationSwipeAnimationHelper {
private final float end;
private BubblePositionInterpolator(float start, float middle, float end) {
this.start = start;
this.start = start;
this.middle = middle;
this.end = end;
this.end = end;
}
@Override
@@ -105,11 +109,11 @@ final class ConversationSwipeAnimationHelper {
} else if (input < middle) {
return input;
} else {
float segmentLength = end - middle;
float segmentTraveled = input - middle;
float segmentLength = end - middle;
float segmentTraveled = input - middle;
float segmentCompletion = segmentTraveled / segmentLength;
float scaleDownFactor = middle / (input * 2);
float output = middle + (segmentLength * segmentCompletion * scaleDownFactor);
float scaleDownFactor = middle / (input * 2);
float output = middle + (segmentLength * segmentCompletion * scaleDownFactor);
return Math.min(output, end);
}
@@ -128,10 +132,10 @@ final class ConversationSwipeAnimationHelper {
}
ClampingLinearInterpolator(float start, float end, float scale) {
slope = (end - start) * scale;
slope = (end - start) * scale;
yIntercept = start;
max = Math.max(start, end);
min = Math.min(start, end);
max = Math.max(start, end);
min = Math.min(start, end);
}
@Override
@@ -139,5 +143,4 @@ final class ConversationSwipeAnimationHelper {
return Util.clamp(slope * input + yIntercept, min, max);
}
}
}
@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms;
import android.app.Activity;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -8,14 +7,11 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcContext;
import org.thoughtcrime.securesms.components.AvatarView;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.mms.GlideRequests;
@@ -24,16 +20,14 @@ import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Locale;
public class ConversationTitleView extends RelativeLayout {
private View content;
private ImageView back;
private AvatarView avatar;
private TextView title;
private TextView subtitle;
private ImageView ephemeralIcon;
private View content;
private ImageView back;
private AvatarView avatar;
private TextView title;
private TextView subtitle;
private ImageView ephemeralIcon;
public ConversationTitleView(Context context) {
this(context, null);
@@ -41,18 +35,17 @@ public class ConversationTitleView extends RelativeLayout {
public ConversationTitleView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
this.back = ViewUtil.findById(this, R.id.up_button);
this.content = ViewUtil.findById(this, R.id.content);
this.title = ViewUtil.findById(this, R.id.title);
this.subtitle = ViewUtil.findById(this, R.id.subtitle);
this.avatar = ViewUtil.findById(this, R.id.avatar);
this.back = ViewUtil.findById(this, R.id.up_button);
this.content = ViewUtil.findById(this, R.id.content);
this.title = ViewUtil.findById(this, R.id.title);
this.subtitle = ViewUtil.findById(this, R.id.subtitle);
this.avatar = ViewUtil.findById(this, R.id.avatar);
this.ephemeralIcon = ViewUtil.findById(this, R.id.ephemeral_icon);
ViewUtil.setTextViewGravityStart(this.title, getContext());
@@ -75,21 +68,25 @@ public class ConversationTitleView extends RelativeLayout {
} else if (dcChat.isInBroadcast()) {
subtitleStr = context.getString(R.string.channel);
} else if (dcChat.isOutBroadcast()) {
subtitleStr = context.getResources().getQuantityString(R.plurals.n_recipients, chatContacts.length, chatContacts.length);
} else if( dcChat.isMultiUser() ) {
subtitleStr =
context
.getResources()
.getQuantityString(R.plurals.n_recipients, chatContacts.length, chatContacts.length);
} else if (dcChat.isMultiUser()) {
if (chatContacts.length > 1 || Util.contains(chatContacts, DcContact.DC_CONTACT_ID_SELF)) {
subtitleStr = context.getResources().getQuantityString(R.plurals.n_members, chatContacts.length, chatContacts.length);
subtitleStr =
context
.getResources()
.getQuantityString(R.plurals.n_members, chatContacts.length, chatContacts.length);
} else {
subtitleStr = "…";
}
} else if( chatContacts.length>=1 ) {
if( dcChat.isSelfTalk() ) {
} else if (chatContacts.length >= 1) {
if (dcChat.isSelfTalk()) {
subtitleStr = context.getString(R.string.chat_self_talk_subtitle);
}
else if( dcChat.isDeviceTalk() ) {
} else if (dcChat.isDeviceTalk()) {
subtitleStr = context.getString(R.string.device_talk_subtitle);
}
else {
} else {
DcContact dcContact = dcContext.getContact(chatContacts[0]);
isOnline = dcContact.wasSeenRecently();
if (!dcChat.isEncrypted()) {
@@ -101,7 +98,9 @@ public class ConversationTitleView extends RelativeLayout {
} else {
long timestamp = dcContact.getLastSeen();
if (timestamp >= 0) {
subtitleStr = context.getString(R.string.last_seen_at, DateUtils.getExtendedTimeSpanString(context, timestamp));
subtitleStr =
context.getString(
R.string.last_seen_at, DateUtils.getExtendedTimeSpanString(context, timestamp));
}
}
}
@@ -109,8 +108,8 @@ public class ConversationTitleView extends RelativeLayout {
avatar.setAvatar(glideRequests, new Recipient(getContext(), dcChat), false);
avatar.setSeenRecently(isOnline);
int imgLeft = dcChat.isMuted()? R.drawable.ic_volume_off_white_18dp : 0;
int imgRight = dcChat.isSelfTalk() || dcChat.isDeviceTalk()? R.drawable.ic_verified : 0;
int imgLeft = dcChat.isMuted() ? R.drawable.ic_volume_off_white_18dp : 0;
int imgRight = dcChat.isSelfTalk() || dcChat.isDeviceTalk() ? R.drawable.ic_verified : 0;
title.setCompoundDrawablesWithIntrinsicBounds(imgLeft, 0, imgRight, 0);
if (!TextUtils.isEmpty(subtitleStr)) {
subtitle.setText(subtitleStr);
@@ -119,7 +118,7 @@ public class ConversationTitleView extends RelativeLayout {
subtitle.setVisibility(View.GONE);
}
boolean isEphemeral = dcContext.getChatEphemeralTimer(chatId) != 0;
ephemeralIcon.setVisibility(isEphemeral? View.VISIBLE : View.GONE);
ephemeralIcon.setVisibility(isEphemeral ? View.VISIBLE : View.GONE);
}
public void setTitle(@NonNull GlideRequests glideRequests, @NonNull DcContact contact) {
@@ -5,28 +5,25 @@ import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcMsg;
import java.io.ByteArrayInputStream;
import java.util.Set;
import org.json.JSONObject;
import org.thoughtcrime.securesms.components.DeliveryStatusView;
import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
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;
import java.io.ByteArrayInputStream;
import java.util.Set;
public class ConversationUpdateItem extends BaseConversationItem
{
private DeliveryStatusView deliveryStatusView;
private AppCompatImageView appIcon;
private int textColor;
public class ConversationUpdateItem extends BaseConversationItem {
private DeliveryStatusView deliveryStatusView;
private AppCompatImageView appIcon;
private int textColor;
public ConversationUpdateItem(Context context) {
this(context, null);
@@ -42,10 +39,9 @@ public class ConversationUpdateItem extends BaseConversationItem
initializeAttributes();
bodyText = findViewById(R.id.conversation_update_body);
bodyText = findViewById(R.id.conversation_update_body);
deliveryStatusView = new DeliveryStatusView(findViewById(R.id.delivery_indicator));
appIcon = findViewById(R.id.app_icon);
appIcon = findViewById(R.id.app_icon);
bodyText.setOnLongClickListener(passthroughClickListener);
bodyText.setOnClickListener(passthroughClickListener);
@@ -56,22 +52,25 @@ public class ConversationUpdateItem extends BaseConversationItem
}
@Override
public void bind(@NonNull DcMsg messageRecord,
@NonNull DcChat dcChat,
@NonNull GlideRequests glideRequests,
@NonNull Set<DcMsg> batchSelected,
@NonNull Recipient conversationRecipient,
boolean pulseUpdate)
{
bind(messageRecord, dcChat, batchSelected, pulseUpdate, conversationRecipient);
public void bind(
@NonNull DcMsg messageRecord,
@NonNull DcChat dcChat,
@NonNull GlideRequests glideRequests,
@NonNull Set<DcMsg> batchSelected,
@NonNull Recipient conversationRecipient,
boolean pulseUpdate,
@Nullable AudioPlaybackViewModel playbackViewModel,
AudioView.OnActionListener audioPlayPauseListener) {
bindPartial(messageRecord, dcChat, batchSelected, pulseUpdate, conversationRecipient);
setGenericInfoRecord(messageRecord);
}
private void initializeAttributes() {
final int[] attributes = new int[] {
R.attr.conversation_item_update_text_color,
};
final TypedArray attrs = context.obtainStyledAttributes(attributes);
final int[] attributes =
new int[] {
R.attr.conversation_item_update_text_color,
};
final TypedArray attrs = context.obtainStyledAttributes(attributes);
textColor = attrs.getColor(0, Color.WHITE);
attrs.recycle();
@@ -114,20 +113,13 @@ public class ConversationUpdateItem extends BaseConversationItem
bodyText.setText(messageRecord.getDisplayBody());
bodyText.setVisibility(VISIBLE);
if (messageRecord.isFailed()) deliveryStatusView.setFailed();
else if (!messageRecord.isOutgoing()) deliveryStatusView.setNone();
else if (messageRecord.isPreparing()) deliveryStatusView.setPreparing();
else if (messageRecord.isPending()) deliveryStatusView.setPending();
else deliveryStatusView.setNone();
if (messageRecord.isFailed()) {
deliveryStatusView.setTint(Color.RED);
} else {
deliveryStatusView.setTint(textColor);
}
if (messageRecord.isFailed()) deliveryStatusView.setFailed();
else if (!messageRecord.isOutgoing()) deliveryStatusView.setNone();
else if (messageRecord.isPreparing()) deliveryStatusView.setPreparing();
else if (messageRecord.isPending()) deliveryStatusView.setPending();
else deliveryStatusView.setNone();
}
@Override
public void unbind() {
}
public void unbind() {}
}
@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
@@ -15,21 +14,19 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.loader.app.LoaderManager;
import com.b44t.messenger.DcContext;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.transition.Transition;
import com.google.android.material.textfield.TextInputLayout;
import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
import org.thoughtcrime.securesms.components.AvatarSelector;
import org.thoughtcrime.securesms.components.InputAwareLayout;
import org.thoughtcrime.securesms.connect.DcHelper;
@@ -42,42 +39,32 @@ import org.thoughtcrime.securesms.scribbles.ScribbleActivity;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
@SuppressLint("StaticFieldLeak")
public class CreateProfileActivity extends BaseActionBarActivity {
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;
private ImageView avatar;
private EditText name;
private EditText statusView;
private InputAwareLayout container;
private ImageView avatar;
private EditText name;
private EditText statusView;
private boolean fromWelcome;
private boolean avatarChanged;
private boolean imageLoaded;
private Bitmap avatarBmp;
private AttachmentManager attachmentManager;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
this.fromWelcome = getIntent().getBooleanExtra(FROM_WELCOME, false);
setContentView(R.layout.profile_create_activity);
getSupportActionBar().setTitle(R.string.pref_profile_info_headline);
getSupportActionBar().setDisplayHomeAsUpEnabled(!this.fromWelcome);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp);
attachmentManager = new AttachmentManager(this, () -> {});
@@ -86,6 +73,21 @@ public class CreateProfileActivity extends BaseActionBarActivity {
initializeProfileName();
initializeProfileAvatar();
initializeStatusText();
getOnBackPressedDispatcher()
.addCallback(
this,
new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (container.isInputOpen()) {
container.hideCurrentInput(name);
} else {
setEnabled(false);
getOnBackPressedDispatcher().onBackPressed();
}
}
});
}
@Override
@@ -100,7 +102,7 @@ public class CreateProfileActivity extends BaseActionBarActivity {
super.onOptionsItemSelected(item);
int itemId = item.getItemId();
if (itemId == android.R.id.home) {
onBackPressed();
getOnBackPressedDispatcher().onBackPressed();
return true;
} else if (itemId == R.id.menu_create_profile) {
updateProfile();
@@ -110,18 +112,8 @@ public class CreateProfileActivity extends BaseActionBarActivity {
}
@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) {
public void onRequestPermissionsResult(
int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
@@ -130,12 +122,12 @@ public class CreateProfileActivity extends BaseActionBarActivity {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != Activity.RESULT_OK) {
return;
return;
}
switch (requestCode) {
case REQUEST_CODE_AVATAR:
Uri inputFile = (data != null ? data.getData() : null);
Uri inputFile = (data != null ? data.getData() : null);
onFileSelected(inputFile);
break;
@@ -147,26 +139,28 @@ public class CreateProfileActivity extends BaseActionBarActivity {
private void setAvatarView(Uri output) {
GlideApp.with(this)
.asBitmap()
.load(output)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.override(AvatarHelper.AVATAR_SIZE, AvatarHelper.AVATAR_SIZE)
.into(new SimpleTarget<Bitmap>() {
.asBitmap()
.load(output)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.override(AvatarHelper.AVATAR_SIZE, AvatarHelper.AVATAR_SIZE)
.into(
new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
public void onResourceReady(
@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
avatarChanged = true;
imageLoaded = true;
avatarBmp = resource;
}
});
GlideApp.with(this)
.load(output)
.circleCrop()
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(avatar);
.load(output)
.circleCrop()
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(avatar);
}
private void onFileSelected(Uri inputFile) {
@@ -178,26 +172,20 @@ public class CreateProfileActivity extends BaseActionBarActivity {
}
private void initializeResources() {
TextView loginSuccessText = ViewUtil.findById(this, R.id.login_success_text);
this.avatar = ViewUtil.findById(this, R.id.avatar);
this.name = ViewUtil.findById(this, R.id.name_text);
this.container = ViewUtil.findById(this, R.id.container);
this.statusView = ViewUtil.findById(this, R.id.status_text);
TextView loginSuccessText = ViewUtil.findById(this, R.id.login_success_text);
this.avatar = ViewUtil.findById(this, R.id.avatar);
this.name = ViewUtil.findById(this, R.id.name_text);
this.container = ViewUtil.findById(this, R.id.container);
this.statusView = ViewUtil.findById(this, R.id.status_text);
// add padding to avoid content hidden behind system bars
ViewUtil.applyWindowInsets(container);
if (fromWelcome) {
loginSuccessText.setText(R.string.set_name_and_avatar_explain);
ViewUtil.findById(this, R.id.status_text_layout).setVisibility(View.GONE);
ViewUtil.findById(this, R.id.information_label).setVisibility(View.GONE);
} else {
loginSuccessText.setVisibility(View.GONE);
}
loginSuccessText.setVisibility(View.GONE);
}
private void initializeProfileName() {
String profileName = DcHelper.get(this, DcHelper.CONFIG_DISPLAY_NAME);
String profileName = DcHelper.get(this, DcHelper.CONFIG_DISPLAY_NAME);
if (!TextUtils.isEmpty(profileName)) {
name.setText(profileName);
name.setSelection(profileName.length(), profileName.length());
@@ -208,18 +196,21 @@ public class CreateProfileActivity extends BaseActionBarActivity {
File avatarFile = AvatarHelper.getSelfAvatarFile(this);
if (avatarFile.exists() && avatarFile.length() > 0) {
imageLoaded = true;
GlideApp.with(this)
.load(avatarFile)
.circleCrop()
.into(avatar);
GlideApp.with(this).load(avatarFile).circleCrop().into(avatar);
} else {
imageLoaded = false;
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp).asDrawable(this, getResources().getColor(R.color.grey_400)));
avatar.setImageDrawable(
new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp)
.asDrawable(this, getResources().getColor(R.color.grey_400)));
}
avatar.setOnClickListener(view ->
new AvatarSelector(this, LoaderManager.getInstance(this), new AvatarSelectedListener(), imageLoaded)
.show(this, avatar)
);
avatar.setOnClickListener(
view ->
new AvatarSelector(
this,
LoaderManager.getInstance(this),
new AvatarSelectedListener(),
imageLoaded)
.show(this, avatar));
}
private void initializeStatusText() {
@@ -237,7 +228,7 @@ public class CreateProfileActivity extends BaseActionBarActivity {
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
Context context = CreateProfileActivity.this;
Context context = CreateProfileActivity.this;
DcHelper.set(context, DcHelper.CONFIG_DISPLAY_NAME, name);
setStatusText();
@@ -260,13 +251,8 @@ 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 {
} else {
Toast.makeText(CreateProfileActivity.this, R.string.error, Toast.LENGTH_LONG).show();
}
}
@@ -289,7 +275,10 @@ public class CreateProfileActivity extends BaseActionBarActivity {
avatarBmp = null;
imageLoaded = false;
avatarChanged = true;
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp).asDrawable(CreateProfileActivity.this, getResources().getColor(R.color.grey_400)));
avatar.setImageDrawable(
new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp)
.asDrawable(
CreateProfileActivity.this, getResources().getColor(R.color.grey_400)));
break;
case AvatarSelector.TAKE_PHOTO:
attachmentManager.capturePhoto(CreateProfileActivity.this, REQUEST_CODE_AVATAR);
@@ -4,7 +4,8 @@ import android.app.Activity;
import android.os.Bundle;
// dummy activity that just pushes the app to foreground when fired.
// can also be used to work around android bug https://code.google.com/p/android/issues/detail?id=53313
// can also be used to work around android bug
// https://code.google.com/p/android/issues/detail?id=53313
public class DummyActivity extends Activity {
@Override
public void onCreate(Bundle bundle) {
@@ -6,99 +6,120 @@ import android.view.ViewGroup;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.core.widget.TextViewCompat;
import java.util.concurrent.TimeUnit;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.concurrent.TimeUnit;
public class EphemeralMessagesDialog {
private final static String TAG = EphemeralMessagesDialog.class.getSimpleName();
private static final String TAG = EphemeralMessagesDialog.class.getSimpleName();
public static void show(final Context context, int currentSelectedTime, final @NonNull EphemeralMessagesInterface listener) {
CharSequence[] choices = context.getResources().getStringArray(R.array.ephemeral_message_durations);
int preselected = getPreselection(currentSelectedTime);
final int[] selectedChoice = new int[]{preselected};
public static void show(
final Context context,
int currentSelectedTime,
final @NonNull EphemeralMessagesInterface listener) {
CharSequence[] choices =
context.getResources().getStringArray(R.array.ephemeral_message_durations);
int preselected = getPreselection(currentSelectedTime);
final int[] selectedChoice = new int[] {preselected};
View dialogView = View.inflate(context, R.layout.dialog_extended_options, null);
RadioGroup container = dialogView.findViewById(R.id.optionsContainer);
for (CharSequence choice : choices) {
View dialogView = View.inflate(context, R.layout.dialog_extended_options, null);
RadioGroup container = dialogView.findViewById(R.id.optionsContainer);
for (CharSequence choice : choices) {
RadioButton radioButton = new RadioButton(context);
radioButton.setText(choice);
TextViewCompat.setTextAppearance(radioButton, android.R.style.TextAppearance_Medium);
RadioButton radioButton = new RadioButton(context);
radioButton.setText(choice);
TextViewCompat.setTextAppearance(radioButton, android.R.style.TextAppearance_Medium);
RadioGroup.LayoutParams params = new RadioGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(0, 0, 0, ViewUtil.dpToPx(context, 8));
radioButton.setLayoutParams(params);
container.addView(radioButton);
}
RadioGroup.LayoutParams params =
new RadioGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(0, 0, 0, ViewUtil.dpToPx(context, 8));
radioButton.setLayoutParams(params);
container.addView(radioButton);
}
container.setOnCheckedChangeListener((group, checkedId) -> {
int childCount = group.getChildCount();
for (int x = 0; x < childCount; x++) {
RadioButton btn = (RadioButton) group.getChildAt(x);
if (btn.getId() == checkedId) {
selectedChoice[0] = x;
}
container.setOnCheckedChangeListener(
(group, checkedId) -> {
int childCount = group.getChildCount();
for (int x = 0; x < childCount; x++) {
RadioButton btn = (RadioButton) group.getChildAt(x);
if (btn.getId() == checkedId) {
selectedChoice[0] = x;
}
}
});
container.check(container.getChildAt(preselected).getId());
container.check(container.getChildAt(preselected).getId());
TextView messageView = dialogView.findViewById(R.id.description);
messageView.setText(context.getString(R.string.ephemeral_messages_hint));
TextView messageView = dialogView.findViewById(R.id.description);
messageView.setText(context.getString(R.string.ephemeral_messages_hint));
AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setTitle(R.string.ephemeral_messages)
.setView(dialogView)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, (dialog, which) -> {
final long burnAfter;
switch (selectedChoice[0]) {
case 1: burnAfter = TimeUnit.MINUTES.toSeconds(5); break;
case 2: burnAfter = TimeUnit.HOURS.toSeconds(1); break;
case 3: burnAfter = TimeUnit.DAYS.toSeconds(1); break;
case 4: burnAfter = TimeUnit.DAYS.toSeconds(7); break;
case 5: burnAfter = TimeUnit.DAYS.toSeconds(35); break;
case 6: burnAfter = TimeUnit.DAYS.toSeconds(365); break;
default: burnAfter = 0; break;
}
listener.onTimeSelected(burnAfter);
AlertDialog.Builder builder =
new AlertDialog.Builder(context)
.setTitle(R.string.ephemeral_messages)
.setView(dialogView)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(
R.string.ok,
(dialog, which) -> {
final long burnAfter;
switch (selectedChoice[0]) {
case 1:
burnAfter = TimeUnit.MINUTES.toSeconds(5);
break;
case 2:
burnAfter = TimeUnit.HOURS.toSeconds(1);
break;
case 3:
burnAfter = TimeUnit.DAYS.toSeconds(1);
break;
case 4:
burnAfter = TimeUnit.DAYS.toSeconds(7);
break;
case 5:
burnAfter = TimeUnit.DAYS.toSeconds(35);
break;
case 6:
burnAfter = TimeUnit.DAYS.toSeconds(365);
break;
default:
burnAfter = 0;
break;
}
listener.onTimeSelected(burnAfter);
})
.setNeutralButton(R.string.learn_more, (d, w) -> DcHelper.openHelp(context, "#ephemeralmsgs"));
builder.show();
}
.setNeutralButton(
R.string.learn_more, (d, w) -> DcHelper.openHelp(context, "#ephemeralmsgs"));
builder.show();
}
public interface EphemeralMessagesInterface {
void onTimeSelected(long duration);
}
public interface EphemeralMessagesInterface {
void onTimeSelected(long duration);
}
private static int getPreselection(int timespan) {
if (timespan == 0) {
return 0; // off
}
// Choose timespan close to the current one out of available options.
if (timespan < TimeUnit.HOURS.toSeconds(1)) {
return 1; // 5 minutes
}
if (timespan < TimeUnit.DAYS.toSeconds(1)) {
return 2; // 1 hour
}
if (timespan < TimeUnit.DAYS.toSeconds(7)) {
return 3; // 1 day
}
if (timespan < TimeUnit.DAYS.toSeconds(35)) {
return 4; // 1 week
}
if (timespan < TimeUnit.DAYS.toSeconds(365)) {
return 5; // 5 weeks
}
return 6; // 1 year
private static int getPreselection(int timespan) {
if (timespan == 0) {
return 0; // off
}
// Choose timespan close to the current one out of available options.
if (timespan < TimeUnit.HOURS.toSeconds(1)) {
return 1; // 5 minutes
}
if (timespan < TimeUnit.DAYS.toSeconds(1)) {
return 2; // 1 hour
}
if (timespan < TimeUnit.DAYS.toSeconds(7)) {
return 3; // 1 day
}
if (timespan < TimeUnit.DAYS.toSeconds(35)) {
return 4; // 1 week
}
if (timespan < TimeUnit.DAYS.toSeconds(365)) {
return 5; // 5 weeks
}
return 6; // 1 year
}
}

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